266

Is it possible to add a list of hosts that are only specific to a certain user? Perhaps a user-specific hosts file?

This mechanism should also complement the entries in the /etc/hosts file.

redspike
  • 2,763
  • 3
    well, you might instead run own nameservers, and have the user use different nameservers per user-specific resolv.conf - except creating user-specific resolv.conf appears to be exactly as difficult as making user-specific /etc/hosts. – SF. Dec 10 '13 at 11:07
  • 4
    If the server is remote, you might try the ~/.ssh/config file: this post. – aaiezza Jul 08 '16 at 15:47

9 Answers9

186

The functionality you are looking for is implemented in glibc. You can define a custom hosts file by setting the HOSTALIASES environment variable. The names in this file will be picked up by gethostbyname (see documentation).

Example (tested on Ubuntu 13.10):

$ echo 'g www.google.com' >> ~/.hosts
$ export HOSTALIASES=~/.hosts
$ wget g -O /dev/null

Some limitations:

  • HOSTALIASES only works for applications using getaddrinfo(3) or gethostbyname(3)
  • For setuid/setgid/setcap applications, libc sanitizes the environment, which means that the HOSTALIASES setting is lost. ping is setuid root or is given the net_raw capability upon execution (because it needs to listen for ICMP packets), so HOSTALIASES will not work with ping unless you're already root before you call ping.
pwuertz
  • 1,969
  • 18
    Note that it doesn't work if you're using nscd and is limited to hostnames without a dot. – Stéphane Chazelas May 29 '14 at 12:45
  • 3
    This doesn't seem to work on CentOS 6 – kbolino Dec 01 '15 at 18:51
  • 8
    Late to the party, but this is the inverse of what is desired, isn't it?

    I think OP is looking for a similar solution to adding host-resolving entries to /etc/hosts, but one that can be done in userland without escalated privileges.

    (i.e. 127.0.0.1 somedomain.com)

    – Nuri Hodges Nov 10 '16 at 18:53
  • I don't remember then but these days ping isn't a suid binary in Linux; it uses capabilities. If you run getcap /usr/sbin/ping you might see something like: /usr/bin/ping = cap_net_admin,cap_net_raw+p. And technically it's that it needs to open a raw socket rather than ICMP (but I suppose you could argue that's just semantics). – Pryftan Mar 11 '18 at 00:07
  • 1
    Despite the man says "the alias file pointed to by HOSTALIASES will first be searched for name" the priority is random and if run this wget example a dozen of times it goes wrong. – Nakilon Jan 14 '19 at 10:27
  • 3
    Does not work on MacOS :( – Peter Dotchev Jul 15 '19 at 13:18
  • 3
    HOSTALIASES only works for names without any dots -- see __res_context_search() – Jonathon Reinhart Jun 28 '22 at 03:58
51

Beside the LD_PRELOAD tricks. A simple alternative that may work on a few systems would be to binary-edit a copy of the system library that handles hostname resolution to replace /etc/hosts with a path of your own.

For instance, on Linux:

If you're not using nscd, copy libnss_files.so to some location of your own like:

mkdir -p -- ~/lib &&
cp /lib/x86_64-linux-gnu/libnss_files.so.2 ~/lib

(the shared library may be located elsewhere, e.g. /lib/libnss_files.so.2)

Now, binary-edit the copy to replace /etc/hosts in there to something the same length like /tmp/hosts.

perl -pi -e 's:/etc/hosts:/tmp/hosts:g' ~/lib/libnss_files.so.2

Edit /tmp/hosts to add the entry you want. And use

export LD_LIBRARY_PATH=~/lib

for nss_files to look in /tmp/hosts instead of /etc/hosts.

Instead of /tmp/hosts, you could also make it /dev/fd//3 (here using two slashes so that the length of /dev/fd//3 is the same as that of /etc/hosts), and do

exec 3< ~/hosts

For instance which would allow different commands to use different hosts files.

If nscd is installed and running, you can bypass it by doing the same trick, but this time for libc.so.6 and replace the path to the nscd socket (something like /var/run/nscd/socket) with some nonexistent path.

einpoklum
  • 9,515
  • 40
    +1 for audacity, -1 for the shock value – fche Sep 27 '13 at 12:37
  • 13
    +1 for binary patching, -1 for security implications – Parthian Shot Jun 26 '15 at 20:28
  • 2
    @ParthianShot, what security implications? – Stéphane Chazelas Jun 27 '15 at 11:05
  • 1
    @StéphaneChazelas Changing LD_LIBRARY_PATH to point to a directory owned by the user means any other process run by the user can use that directory to co-opt any new processes spawned by replacing libraries. And updates to libnss_files.so through the package manager (including security updates) won't be reflected in the patched version. Modifying LD_LIBRARY_PATH is generally a bad thing to recommend for other reasons, but it's also unwise because of those issues. – Parthian Shot Jun 28 '15 at 03:26
  • 15
    @ParthianShot, your point about missing updates is a fair point. However, for your other point, if a rogue software is running in your name, it having write access to an area in $LD_LIBRARY_PATH would be the least of your worries as it's already got write access to a lot worse and more reliable areas like your .bash*, crontab, .forward, and all configuration files by all the software you use (where it can for instance set LD_{PRELOAD,LIBRARY_PATH} but would typically do a lot worse) – Stéphane Chazelas Jun 28 '15 at 07:09
  • @StéphaneChazelas Any idea why it would work with LD_PRELOAD but not with LD_LIBRARY_PATH? Here's what I am running on shell: https://paste.ee/r/KE1Hl – Berkant İpek Apr 29 '22 at 12:21
  • 1
    @BerkantIpek, it's likely a libnss_files.so.2 file getent is looking for, not libnss_files.so. Check with LD_DEBUG=libs getent hosts – Stéphane Chazelas Apr 29 '22 at 12:30
39

Private mountspaces created with the unshare command can be used to provide a private /etc/hosts file to a shell process and any subsequent child processes started from that shell.

# Start by creating your custom /etc/hosts file
[user] cd ~
[user] cat >my_hosts <<EOF
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
127.0.0.1 news.bbc.co.uk
EOF

[user] sudo unshare --mount
# We're now running as root in a private mountspace. 
# Any filesystem mounts performed in this private mountspace
# are private to this shell process and its children

# Use a bind mount to install our custom hosts file over /etc/hosts
[root] mount my_hosts /etc/hosts --bind

[root] cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
127.0.0.1 news.bbc.co.uk

[root] exec su - appuser

[appuser] # Run your app here that needs a custom /etc/hosts file

[appuser] ping news.bbc.co.uk
PING news.bbc.co.uk (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.062 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.026 ms
^C
--- news.bbc.co.uk ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.026/0.044/0.062/0.018 ms
frielp
  • 480
  • 5
    Wait. I thought mount just mounted filesystems onto directories (mount points). I didn't know that a file could be mounted onto another file. Does that really work? (I am asking that seriously. That isn't sarcasm.) – killermist Jan 01 '16 at 22:49
  • 9
    Yep, it works, you can mount a file over another file with --bind. – frielp Jan 04 '16 at 08:12
  • 2
    Just as a note to those curious: this is to do with namespaces; there are the syscalls unshare(2) and clone(2) that is part of the magic here. See also namespaces(7) and user_namespaces(7). – Pryftan Mar 11 '18 at 00:02
  • 1
    This is really cool! – micahscopes Feb 19 '20 at 21:31
  • 1
    While it can be really helpful, user namespaces are also a big security risks as numerous exploits in the last years have shown. This is not used above (ie you need sudo rights) but should be mentioned. Debian has disabled them on default. One other thing that should also be considered is that to use unshare you have to execute/fork another process. Thus it is not completely trivial to implant it in a shell script for automation. One way is to pass the whole code into sh -c as argument of unshare as explained here: https://piware.de/2012/12/running-a-script-with-unshared-mount-namespace/ – stefanct Apr 25 '20 at 16:02
8

I faced the same need, so I tried libnss-userhosts, but it fails at multithreaded applications. Therefore I have written libnss-homehosts. It's very new and tested only by me. You may give a chance for it! It supports some options in /etc/host.conf, multiple alias names, and reverse resolving (address to name).

bandie
  • 373
  • 1
    This seems like a good idea to pitch to the libnss maintainers and/or to distribution maintainers. But before that happens, users without root themselves will not be able to use it. Still, +1 – einpoklum Aug 14 '17 at 09:19
  • @bandie your tool works perfect on my Linux. Do you know any similar solution for MacOS? – Nicolai Aug 07 '20 at 10:05
  • 1
    @Nicolai, unfortunately no. does not MacOS have libnss? if so, libnss-homehost can be ported to. – bandie Aug 09 '20 at 14:42
6

One solution is to have each user in a separate chroot, so they can each have a separate /etc/hosts to themselves.

Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233
  • 3
    This could be an answer, but as stated with little explanation it is more suited as a comment – Anthon Dec 10 '13 at 10:49
  • 2
    Well... yeah, it's doable. Although chrooting is a pretty heavy-duty solution for this kind of thing. And brings with it its own set of issues. – Parthian Shot Jun 26 '15 at 20:31
4

Placing the following in ~/.bashrc is working for me in bash. It converts the hostname in the command into an address based on entries in ~/.hosts. If ~/.hosts doesn't exist or if the hostname can't be found in ~/.hosts, the command executes as normal. This should work with the original flags of the relevant functions and regarless of where the hostname is placed relative to the flags, e.g. ping -i 0.5 host1 -c 3, works. The ~/.hosts file takes preference over any other location for finding hostnames, so if there are any dupicate hostnames, the address in ~/.hosts will be used.

$ cat ~/.bashrc 
function resolve {
        hostfile=~/.hosts
        if [[ -f "$hostfile" ]]; then
                for arg in $(seq 1 $#); do
                        if [[ "${!arg:0:1}" != "-" ]]; then
                                ip=$(sed -n -e "/^\s*\(\#.*\|\)$/d" -e "/\<${!arg}\>/{s;^\s*\(\S*\)\s*.*$;\1;p;q}" "$hostfile")
                                if [[ -n "$ip" ]]; then
                                        command "${FUNCNAME[1]}" "${@:1:$(($arg-1))}" "$ip" "${@:$(($arg+1)):$#}"
                                        return
                                fi
                        fi
                done
        fi
        command "${FUNCNAME[1]}" "$@"
}

function ping {
        resolve "$@"
}

function traceroute {
        resolve "$@"
}

An example of ~/.hosts is given below. It follows the same format as /etc/hosts. Comments and whitespace are handled correctly.

$ cat ~/.hosts 
# addresses and hostnames
stackexchange.com se

192.168.0.1 host1 # this is host1's address
login-node.inst.ac.uk login
3

Not sure if this would help you, but I came here looking for a way to add saved "hosts" somewhere that was easily accessible to only my user.

I basically needed to be able to ssh into certain boxes on our work network, which only has one entry point.

What I did was add aliases to my .bashrc file.

For example, if you added:

alias jrfbox='ssh jason@192.168.6.6' 

at the bottom of your ~/.bashrc (~ is your home directory). Then after you logout and login again, you can type jrfbox, hit Enter, and it will connect.

Jason
  • 1,059
1

It is possible to override dns resolution in google chrome in windows, mac and linux without root/admin access. All we need to do is to start google chrome process with a switch and supply the required DNS entries there.

May be you can append the switch to the shortcut of google chrome in windows or softlink of google chrome in linux so that it varies for each user.

https://devopslife.io/resolve-dns-locally-using-google-chrome/

0

Here is a way to run the answer by frielp in a script

#!/bin/sh                                                                                                                                                                                     
set -e
# call this file through unshare in a way that keeps normal stdin, $0, and CLI args                                                                                                           
sudo unshare -uim sh -c "$(tail -n +7 $0)" "$0" "$@"
exit $?

unshared program starts here

set -e echo "mounting my_hosts file as /etc/hosts" sudo mount my_hosts /etc/hosts --bind su - khalfani-tmp bash -c "ping new.host.com"
echo "done"

KhalfaniW
  • 113