Original Question
I’m trying to create a Lisp library that can, among other things, edit my system’s /etc/hosts
file and nginx configurations. The problem I’m facing is that, because my Lisp image operates as an unprivileged user, my library can’t do these things. Ideally, when root powers were needed I’d have the ability to provide a password to my library so it could temporarily bump up its access rights in order to get things done. Alas, I haven’t been able to find any Common Lisp equivalent of sudo
. Is there one? Am I approaching this the wrong way? How might I go about solving this?
In code, what I want to be able to do is basically this:
(with-sudo (:username "root" :password (securely-read-line)) (with-open-file (f "/etc/hosts" :direction :output :if-exists :append) (format f "127.0.0.1 mywebsite.local~%")))
Clarifications
I’m using SBCL on OS X. I’m trying to create a library which is basically a specialization of quickproject for websites. Currently, every time I set up a new web project on my local computer I have to edit configuration files strewn across my system. I would like to automate as much of that as possible, and I would like to be able to do this from within the SLIME session that I usually have open and connected to a single instance of SBCL.
Here are some other considerations:
- I don’t want my main instance of SBCL to be
setuid
‘d to root - I don’t mind spawning a new process, whether that’s a small C program or a bare instance of SBCL that loads a few lines of code then exits.
- I would like to use keep the Lisp to C ratio as high as possible.
- Some text editors (such as TextMate) bring up a prompt asking for a username and password when visiting files that require heightened privileges to view, providing access accordingly. I wonder how I could get my Lisp library to do the same?
At first I couldn’t get
sudo
(the actual program itself) to work from within SBCL:(inferior-shell:run/ss '("sudo" "ls" ".")) ;; how can I pass sudo a password?
Now it looks like that will be possible using
sudo
‘s-S
option (thank you JustAnotherCurious). I think I may just go this route; it’s definitely what I’m leaning towards now.
Anyhow, thank you everyone! I’m learning a lot from all of you.
Advertisement
Answer
If you need root access privileges in your process, you need to start your Lisp process as root initially. It is not generally possible to make a non-root process run as in retrospect.
Fortunately, Unix has a mechanism that allows a process to switch between root and non-root privileges at run time. That mechanism is called effective user id. A process that runs as root can switch to a non-root effective uid using the seteuid
system call, and it can also switch back to “being” root that way.
Certainly, if you start your Lisp process as root, that process has full control over the machine, and depending on what data and machine you’re dealing with, you need to be considerate as to what possible security holes you open by that. Fortunately, buffer overflows are hard to produce in Lisp, so from that perspective, you’re on the safer side 🙂
Access to the system call interface is not standardized in Common Lisp, but most implementations have a native interface to the system, and you can also use CFFI if you plan for your program to be portable across Linux/Unix based Lisps.
Here is a transcript of SBCL running as root demonstrating the use of seteuid:
CL-USER> (defun write-file-in-filesystem-root () (handler-case (with-open-file (f "/only-root-may-write-to-root" :direction :output :if-exists :supersede) (write "hello" :stream f)) (error (e) (format t "error: ~A~%" e)))) WRITE-FILE-IN-FILESYSTEM-ROOT CL-USER> (sb-posix:seteuid 0) 0 CL-USER> (write-file-in-filesystem-root) "hello" CL-USER> (sb-posix:seteuid 1000) 0 CL-USER> (write-file-in-filesystem-root) error: error opening #P"/only-root-may-write-to-root": Permission denied NIL CL-USER> (sb-posix:seteuid 0) 0 CL-USER> (write-file-in-filesystem-root) "hello" CL-USER> (delete-file "/only-root-may-write-to-root") T
If all you need is access to protected files, if staying OSX specific is acceptable and if you want the user to authenticate using the standard authentication requester, you can use the authopen command which is specific to OSX.