views:

248

answers:

4

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,

  1. 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
  2. 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
  3. something else i'm unaware of

)

+1  A: 

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.

Ignacio Vazquez-Abrams
s/browser/server
Mehrdad Afshari
Right, fixed (need more sleep).
Ignacio Vazquez-Abrams
+2  A: 

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.

hobbs
thanks, i'm having trouble figuring out how to drop privileges in python properly, do you have any code?
qbxk
found it, thanks though, see my answer
qbxk
A: 

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.

amphetamachine
Err.. having a setuid-root perl or python binary lying around is emphatically **not** a good idea.
caf
@caf - I agree whole-heartedly; it was only a suggestion. But if the directory the binary resides in is only accessible by the user account that executes the script, it's not too big a security hole (excepting, of course, the event that the account is taken over).
amphetamachine
Isn't that the very eventuality that we're trying to prevent by dropping privileges? If taking over the service is as good as getting root anyway (because the user the service runs as has access to a setuid-root script interpreter), then you might as well just run the service as root and cross your fingers.
caf
+2  A: 

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)[2]
    running_gid = grp.getgrnam(gid_name)[2]

    # 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)[0],
              grp.getgrgid(final_gid)[0]))    
qbxk