how do i claim a low-numbered port as non-root the "right way"
I have a script that I want to run as a daemon listening on a low-numbered port (< 1024)
Script is in python, though answers in perl are also acceptable.
The script is being daemonized using start-stop-daemon in a startup script, which may complicate the answer
What I really (think) don't want is to type ps -few and see this process running with a "root" on it's line.
How do I do it?
( from my less-than-fully-educated-about-system-calls perspective, I can see 3 avenues,
- Run the script as root (no --user/--group/--chuid to start-stop-daemon), and have it de-escalate it's user after it claims the port
- Setuid root on the script (chmod u+s), and run the script as the running user, (via --user/--group/--chuid to start-stop-daemon, the startup script still has to be called as root), in the script, acquire root privileges, claim the port, and then revert back to normal user
- something else i'm unaware of
The "something you don't know about" is "capabilities", but as mentioned elsewhere capabilities don't play very well with scripts using the shebang method, so it's not much of an answer here. I would go with the "bind the port, then drop privileges" method.
This page had some code that led me to it, http://antonym.org/2005/12/dropping-privileges-in-python.html
I suppose setting the umask is necessary to finish the job, that's the only thing this code seems to do that my attempts weren't (i'm not sure what setting the umask really does, in the context of applying it to a process)
I tore his example up a bit, and besides, that page is from 2005, so I'm reposting my working solution here,
def drop_privileges(uid_name='nobody', gid_name='nogroup'): # Get the uid/gid from the name running_uid = pwd.getpwnam(uid_name) running_gid = grp.getgrnam(gid_name) # Try setting the new uid/gid try: os.setgid(running_gid) except OSError, e: logging.error('Could not set effective group id: %s' % e) exit() try: os.setuid(running_uid) except OSError, e: logging.error('Could not set effective user id: %s' % e) exit() # Ensure a very convervative umask new_umask = 077 old_umask = os.umask(new_umask) logging.info('drop_privileges: Old umask: %s, new umask: %s' % \ (oct(old_umask), oct(new_umask))) final_uid = os.getuid() final_gid = os.getgid() logging.info('drop_privileges: running as %s/%s' % \ (pwd.getpwuid(final_uid), grp.getgrgid(final_gid)))
Option 1 is the route Apache httpd takes. And if it's good enough for the world's most popular web server then it's worth serious consideration for ones own daemons.
You can install a library which, if LD_PRELOAD-ed, will fake root permissions for any executable (script or otherwise): http://fakeroot.alioth.debian.org/
Setting sticky bit permissions on a script will (at least in my experience) have no effect; it's the ELF binary interpreter (/usr/bin/python, /bin/sh, /usr/bin/perl, etc.) that needs the sticky permissions in order for it to work. What you could do is to copy the binary to another location, set the sticky bit on it, then point the script's bang line to it.
If you don't have root access on the system you're executing on, though, you may be able to modify the router settings to direct the <1024 external port to a >=1024 internal port.
EDIT: I forgot to mention this little tool: redir. It's a local port redirection service. It's even inetd-friendly.