shanonvl has asked for the wisdom of the Perl Monks concerning the following question:

I want any user to be able to run a script that has logging capabilities. The way that I'm implementing it now is that the script accesses a Logger module that does the actual output to the log file. I don't want the log file rw by all users however, only root. So I was thinking that I'd use setuid in the Logger module to write to the log as root. I've set the uid bit on the module, but that doesn't seem to work, probably b/c it's getting accessed directly from another script. Can anyone shed some light on this for me? Thanks in advance!

Replies are listed 'Best First'.
(tye)Re: How to use setuid with modules?
by tye (Sage) on Nov 06, 2001 at 02:24 UTC

    set-UID is a feature of the Unix kernel when a program is exec()'d. Since Perl modules aren't exec()'d, the set-UID bit on a Perl module file is pretty useless.

    You could have your module open a pipe to a set-UID Perl script if you have your kernel and/or Perl configured to support that (an increasingly uncommon situation).

    You could also have a privileged daemon that drains data from a named pipe and writes those to the log file and have the module write to the named pipe. Or you could have a privileged TCP/IP server (probably an HTTP server) that does something similar.

            - tye (but my friends call me "Tye")
Re: How to use setuid with modules?
by Fastolfe (Vicar) on Nov 06, 2001 at 02:29 UTC

    Things like setuid privileges occur at the process level. Since the script and the module it loads during its execution is one logical process, nothing your module can do by this time can elevate the process's privileges.

    You have three options that I can think of:

    1. You can make all of your scripts that need access to this privileged log file setuid (a scary proposition)
    2. If your filesystem supports special file attributes, you may be able to set up the log file so that it can only be appended to. In Linux, under ext2, this can be accomplished by running "chattr +a filename". You can then set the file's permissions so that it's world-writable, but only root-readable. Now, any Joe User can add a line to this file (which means your scripts can), but he can't read what's in the file, delete it, or make changes to it.
    3. The recommended way is to set up an external process, a daemon, that listens for log requests from a client and writes those log requests to the file in question. This daemon would run as the privileged user and would be the only thing capable of reading/writing to this log file. You'd just then have to code some IPC logic in your script (through this module) to send the data to the daemon to be logged

    Note that 'syslog' is everything option (3) above is. I would highly recommend it if you could adapt syslog to your needs (or your needs to what syslog provides) before you re-invent syslog.

    Hope this helps..

Re: How to use setuid with modules?
by Fletch (Bishop) on Nov 06, 2001 at 02:24 UTC

    Setuid is a property of a process, not a module. It's an OS level thing, not a perl level thing. If an executable is marked as setuid, the process' effective uid will be that of the owner of the file (not necessarily root, just whomever owns it). It's an all or nothing proposition that you can't really enable for just some code.

    If you can't use something like syslog, you could write your own logging process which listens on a UNIX domain socket (or a TCP socket if you want to work from multiple hosts) and let your module send messages to that (of course then you have to worry about authentication et al yourself).

    Another posibility would be to use a wrapper which opens a write only filehandle to the logfile in question as a priveledged user, changes uid to the target user's, and then execs the end script. Set something in the environment to the filedescriptor they should use and then dup it into a handle in your module.

    # in wrapper . . . use Fcntl qw( F_GETFD F_SETFD FD_CLOEXEC ); open( LOG, "/path/to/log" ) or die "open log: $!\n"; $ENV{LOG_FD} = fileno( LOG ); my $flags = fcntl( LOG, F_GETFD ) or warn "getfd: $!\n"; fcntl( LOG, F_SETFD, $flags & ~FD_CLOEXEC ) or warn "setfd: $!\n"; $< = $> = uid_to_run_as( ... ); exec "/path/to/real/program", @prog_args or die "Can't exec real program: $!\n"; # in your module sub log_it { local( *LOG ); open( LOG, "<&=" . $ENV{LOG_FD} ) or die "Can't dup log fd: $!\n"; print LOG, "Ack: ", @_, "\n"; }

    Update: Added in code to clear close-on-exec flag so that the descriptor will survive the exec.

Re: How to use setuid with modules?
by bluto (Curate) on Nov 06, 2001 at 01:36 UTC
    Avoid setuid if at all possible since it is easy to screw up and give root permissions away. You generally don't need this for logging secretly anyway. I suggest you create your log file with 0600 permissions so only you (and root) can read it. Another way -- use syslog instead and setup your syslogd to send your entries to your own special file that only root can get to.

    bluto Update: Fixed Permissions

      Thanks for the advice, however I don't think that will work. Syslogd doesn't give me the control that I desire on the message style and your first suggestion 0600 wouldn't work because I have a ton of users that will be running a bunch of scripts and all of them need to have logging functionality. So my original question still stands, do you know how to use setuid with modules? Thanks!