162306a36Sopenharmony_ci/* Upcall routine, designed to work as a key type and working through
262306a36Sopenharmony_ci * /sbin/request-key to contact userspace when handling DNS queries.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * See Documentation/networking/dns_resolver.rst
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *   Copyright (c) 2007 Igor Mammedov
762306a36Sopenharmony_ci *   Author(s): Igor Mammedov (niallain@gmail.com)
862306a36Sopenharmony_ci *              Steve French (sfrench@us.ibm.com)
962306a36Sopenharmony_ci *              Wang Lei (wang840925@gmail.com)
1062306a36Sopenharmony_ci *		David Howells (dhowells@redhat.com)
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci *   The upcall wrapper used to make an arbitrary DNS query.
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci *   This function requires the appropriate userspace tool dns.upcall to be
1562306a36Sopenharmony_ci *   installed and something like the following lines should be added to the
1662306a36Sopenharmony_ci *   /etc/request-key.conf file:
1762306a36Sopenharmony_ci *
1862306a36Sopenharmony_ci *	create dns_resolver * * /sbin/dns.upcall %k
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci *   For example to use this module to query AFSDB RR:
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci *	create dns_resolver afsdb:* * /sbin/dns.afsdb %k
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci *   This library is free software; you can redistribute it and/or modify
2562306a36Sopenharmony_ci *   it under the terms of the GNU Lesser General Public License as published
2662306a36Sopenharmony_ci *   by the Free Software Foundation; either version 2.1 of the License, or
2762306a36Sopenharmony_ci *   (at your option) any later version.
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci *   This library is distributed in the hope that it will be useful,
3062306a36Sopenharmony_ci *   but WITHOUT ANY WARRANTY; without even the implied warranty of
3162306a36Sopenharmony_ci *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
3262306a36Sopenharmony_ci *   the GNU Lesser General Public License for more details.
3362306a36Sopenharmony_ci *
3462306a36Sopenharmony_ci *   You should have received a copy of the GNU Lesser General Public License
3562306a36Sopenharmony_ci *   along with this library; if not, see <http://www.gnu.org/licenses/>.
3662306a36Sopenharmony_ci */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#include <linux/module.h>
3962306a36Sopenharmony_ci#include <linux/slab.h>
4062306a36Sopenharmony_ci#include <linux/cred.h>
4162306a36Sopenharmony_ci#include <linux/dns_resolver.h>
4262306a36Sopenharmony_ci#include <linux/err.h>
4362306a36Sopenharmony_ci#include <net/net_namespace.h>
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#include <keys/dns_resolver-type.h>
4662306a36Sopenharmony_ci#include <keys/user-type.h>
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci#include "internal.h"
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci/**
5162306a36Sopenharmony_ci * dns_query - Query the DNS
5262306a36Sopenharmony_ci * @net: The network namespace to operate in.
5362306a36Sopenharmony_ci * @type: Query type (or NULL for straight host->IP lookup)
5462306a36Sopenharmony_ci * @name: Name to look up
5562306a36Sopenharmony_ci * @namelen: Length of name
5662306a36Sopenharmony_ci * @options: Request options (or NULL if no options)
5762306a36Sopenharmony_ci * @_result: Where to place the returned data (or NULL)
5862306a36Sopenharmony_ci * @_expiry: Where to store the result expiry time (or NULL)
5962306a36Sopenharmony_ci * @invalidate: Always invalidate the key after use
6062306a36Sopenharmony_ci *
6162306a36Sopenharmony_ci * The data will be returned in the pointer at *result, if provided, and the
6262306a36Sopenharmony_ci * caller is responsible for freeing it.
6362306a36Sopenharmony_ci *
6462306a36Sopenharmony_ci * The description should be of the form "[<query_type>:]<domain_name>", and
6562306a36Sopenharmony_ci * the options need to be appropriate for the query type requested.  If no
6662306a36Sopenharmony_ci * query_type is given, then the query is a straight hostname to IP address
6762306a36Sopenharmony_ci * lookup.
6862306a36Sopenharmony_ci *
6962306a36Sopenharmony_ci * The DNS resolution lookup is performed by upcalling to userspace by way of
7062306a36Sopenharmony_ci * requesting a key of type dns_resolver.
7162306a36Sopenharmony_ci *
7262306a36Sopenharmony_ci * Returns the size of the result on success, -ve error code otherwise.
7362306a36Sopenharmony_ci */
7462306a36Sopenharmony_ciint dns_query(struct net *net,
7562306a36Sopenharmony_ci	      const char *type, const char *name, size_t namelen,
7662306a36Sopenharmony_ci	      const char *options, char **_result, time64_t *_expiry,
7762306a36Sopenharmony_ci	      bool invalidate)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	struct key *rkey;
8062306a36Sopenharmony_ci	struct user_key_payload *upayload;
8162306a36Sopenharmony_ci	const struct cred *saved_cred;
8262306a36Sopenharmony_ci	size_t typelen, desclen;
8362306a36Sopenharmony_ci	char *desc, *cp;
8462306a36Sopenharmony_ci	int ret, len;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	kenter("%s,%*.*s,%zu,%s",
8762306a36Sopenharmony_ci	       type, (int)namelen, (int)namelen, name, namelen, options);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (!name || namelen == 0)
9062306a36Sopenharmony_ci		return -EINVAL;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* construct the query key description as "[<type>:]<name>" */
9362306a36Sopenharmony_ci	typelen = 0;
9462306a36Sopenharmony_ci	desclen = 0;
9562306a36Sopenharmony_ci	if (type) {
9662306a36Sopenharmony_ci		typelen = strlen(type);
9762306a36Sopenharmony_ci		if (typelen < 1)
9862306a36Sopenharmony_ci			return -EINVAL;
9962306a36Sopenharmony_ci		desclen += typelen + 1;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	if (namelen < 3 || namelen > 255)
10362306a36Sopenharmony_ci		return -EINVAL;
10462306a36Sopenharmony_ci	desclen += namelen + 1;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	desc = kmalloc(desclen, GFP_KERNEL);
10762306a36Sopenharmony_ci	if (!desc)
10862306a36Sopenharmony_ci		return -ENOMEM;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	cp = desc;
11162306a36Sopenharmony_ci	if (type) {
11262306a36Sopenharmony_ci		memcpy(cp, type, typelen);
11362306a36Sopenharmony_ci		cp += typelen;
11462306a36Sopenharmony_ci		*cp++ = ':';
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci	memcpy(cp, name, namelen);
11762306a36Sopenharmony_ci	cp += namelen;
11862306a36Sopenharmony_ci	*cp = '\0';
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	if (!options)
12162306a36Sopenharmony_ci		options = "";
12262306a36Sopenharmony_ci	kdebug("call request_key(,%s,%s)", desc, options);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* make the upcall, using special credentials to prevent the use of
12562306a36Sopenharmony_ci	 * add_key() to preinstall malicious redirections
12662306a36Sopenharmony_ci	 */
12762306a36Sopenharmony_ci	saved_cred = override_creds(dns_resolver_cache);
12862306a36Sopenharmony_ci	rkey = request_key_net(&key_type_dns_resolver, desc, net, options);
12962306a36Sopenharmony_ci	revert_creds(saved_cred);
13062306a36Sopenharmony_ci	kfree(desc);
13162306a36Sopenharmony_ci	if (IS_ERR(rkey)) {
13262306a36Sopenharmony_ci		ret = PTR_ERR(rkey);
13362306a36Sopenharmony_ci		goto out;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	down_read(&rkey->sem);
13762306a36Sopenharmony_ci	set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags);
13862306a36Sopenharmony_ci	rkey->perm |= KEY_USR_VIEW;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	ret = key_validate(rkey);
14162306a36Sopenharmony_ci	if (ret < 0)
14262306a36Sopenharmony_ci		goto put;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* If the DNS server gave an error, return that to the caller */
14562306a36Sopenharmony_ci	ret = PTR_ERR(rkey->payload.data[dns_key_error]);
14662306a36Sopenharmony_ci	if (ret)
14762306a36Sopenharmony_ci		goto put;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	upayload = user_key_payload_locked(rkey);
15062306a36Sopenharmony_ci	len = upayload->datalen;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (_result) {
15362306a36Sopenharmony_ci		ret = -ENOMEM;
15462306a36Sopenharmony_ci		*_result = kmemdup_nul(upayload->data, len, GFP_KERNEL);
15562306a36Sopenharmony_ci		if (!*_result)
15662306a36Sopenharmony_ci			goto put;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (_expiry)
16062306a36Sopenharmony_ci		*_expiry = rkey->expiry;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	ret = len;
16362306a36Sopenharmony_ciput:
16462306a36Sopenharmony_ci	up_read(&rkey->sem);
16562306a36Sopenharmony_ci	if (invalidate)
16662306a36Sopenharmony_ci		key_invalidate(rkey);
16762306a36Sopenharmony_ci	key_put(rkey);
16862306a36Sopenharmony_ciout:
16962306a36Sopenharmony_ci	kleave(" = %d", ret);
17062306a36Sopenharmony_ci	return ret;
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ciEXPORT_SYMBOL(dns_query);
173