I’ve spent some time this week implementing user authentication over UNIX domain sockets for CUPS. The basic problem is that there are some operations (cancelling a job or submitting a new job, for example) that ought to require user authentication but do not, simply for the sake of convenience. This is a lightweight method of providing authentication for those operations.
CUPS performs all authentication at the server end, with the server very often being on the local machine even for remote jobs (which are forwarded). The “cancel” command just connects to the server process and passes it an IPP request. It fills in the “requesting-user” attribute accordingly, and this can be set to any user name using the -U option.
Whether the user name give in the IPP request is taken at face value depends on the server policy: the default is not to question it for cancelling jobs. This is because the alternative is to require authentication each time a job is cancelled, and all the available authentication schemes in CUPS currently require a password. The CUPS maintainer thinks it would be too annoying to require a password whenever a job is cancelled, and I think he has a point.
A similar situation exists for submitting new print jobs: the “requesting-user” attribute is trusted, and so the default policy allows users to submit jobs as though they were submitted by a different user. However, we surely don’t want File->Print->OK to immediately ask for a password to authenticate the user trying to print!
Luckily there is a fairly simple solution that will work in the majority of cases, and it hinges on the fact that CUPS 1.2 uses a UNIX domain socket (/var/run/cups/cups.sock) for communication between the server and the local print clients by default, rather than a local TCP connection as in CUPS 1.1.
UNIX domain sockets allow a method of verifying the client’s user identity. The client program — “cancel” or “lp” in this case, or any program that uses libcups for their print dialog such as the Evince document viewer — can pass its UID (numeric ID corresponding to the user name) to the server over the domain socket. The kernel guarantees that credentials passed in this way cannot be forged. The server just has to look up the user name from the UID.
Getting it to work was quite troublesome because of an apparent quirk in the way Linux handles the SCM_CREDENTIALS message. I just wanted to pass the user credentials across on their own, but it doesn’t seem to work unless I also put at least one byte of data in the message. Unfortunately this gets into the HTTP data stream and starts to mess things up, unless the server knows it’s coming. In the end I had to get the server to send a single byte back to the client to say “go ahead and send me your credentials”, so that the client could do that (along with another single byte), and make it into a full handshake.
Unfortunately the unix(7) man page doesn’t mention this at all, so it took longer to get it all working than it needed to have done.