COMP421
Unix Environment for Programmers
Lecture 14: Daemons__________________________________________

Jeff Wiegley, Ph.D.
Computer Science
jeffw@csun.edu

10/17/2005

     

‘‘Maxwell’s daemon was an imaginary agent which helped sort molecules of different speeds and worked tirelessly in the background.’’

–Fernando J. Corbato, 1963

1


Services________________________________________________

Unix is traditionally a server operating system.

To provide “services”, Unix uses processes termed daemons.

2


Daemons______________________________________________

A Daemon process is not really any different from a normal process. A few programming procedures are simply done to make the process behave in a certain manner:

3


Insecure privileges:___________________________________

Unix processes have a umask associated with them that determine the default permissions that will be created for new files.

The umask is a negation of privileges.

A umask of 0777 for instance would create files that nobody (expect) root could do anything with.

0077 On the other hand would create files/directories with permissions set to “-wrx——”.

The umask is inherited from the parent process when the fork() is performed.

It will be necessary for the daemon process to change its umask to insure least-privileges.

umask(0077); will do the trick. This umask should be as strict as possible without preventing the daemon from establishing permissions that should be allowed.

4


Eliminating the Controlling terminal:_____________

The controlling terminal is the terminal device attached to a process.

It’s a bit different than just stdin and stdout. Closing stdin and stdout does not detach the process from the controlling terminal. This has to be done manually.

    pid = fork();  
    if (pid<0)  exit(1); // fork failed. exit with an error of some kind.  
    if (pid!=0) exit(0); // parent exits, child is adopted by init  
    // child continues and has no controlling TTY attached.

But that’s not quite enough...

5


Process group leader:________________________________

The child still keeps the inherited process group from the fork and this is not acceptable.

    setsid();

creates a new session with this process as the session leader.

Now the problem is that a future open() could deliver a controlling TTY. So we need to take care of that possibility.

6


Future Controlling TTYs:__________________________

We fork, yet again, to guarantee that no future TTY can be a controlling TTY.

      sa.sa_handler = SIG_IGN;  
    sigemptyset(&sa.sa_mask);  
    sa.sa_flags = 0;  
    if (sigaction(SIGHUP, &sa, NULL) < 0)  
       exit(2);  // ARGH! HUP is not ignorable, fatal error.  
    pid = fork();  
    if (pid<0)  exit(1); // fork failed. exit with an error of some kind.  
    if (pid!=0) exit(0); // parent exits, child is again adopted by init  
    // child continues can never have a controlling TTY attached.

7


Current Working Directory:________________________

All process entries have a current working directory. Relative pathnames for open() or other path aware functions function relative to the current working directory.

Servers and daemons stay up for very, very long times. (One of mine has been up for 293 consecutive days.)

But directories, like files, can change.

Therefore daemons should establish a directory as the current working directory that is known not to change.

    chdir("/");

Other, more specific, directory selections would be acceptable. “/var/www” for an HTTP daemon, for example.

8


Close all unused file descriptors:___________________

We don’t need any files (stdin, stdout and stderr) don’t really have a terminal to send or receive anything from.

The following loop insures that all file descriptors are closed. (including stdin, stdout and stderr.

    for (fd=getdtablesize()-1;fd>=0;fd--)  
      close(fd);  
    

Not all Unix-like systems have getdtablesize() functions.

Other functions exist or you can manually just close 0, 1 and 2 since they will probably be the only open file descriptors.

9


A good idea for stdin, stdout and stderr...:_______

Your daemon may later fork (like a shell) and execute other commands that would die if there was no stdin, stdout or stderr attached.

You can use /dev/null as a handy file to attach these to:

The following will handle this nicely.

    int fd = open("/dev/null",O_RDWR);  
    if (fd<0)  
       exit(3); //OOOPS!  
    if (fd!=0)  
      {  
        dup2(fd, 0);  
        close(fd);  
      }  
    dup(0,1);  
    dup(0,2);

10


Logging:_______________________________________________

Daemons run in the background but provide essential services.

It is important to keep track of what they are doing.

There are two basic methods of handling logging:

  1. The daemon handles it all itself.
    Pros: complete control of log location and information.
    Cons: You have to handle everything yourself. Logs can fill up.
  2. The daemon utilizes the syslog daemon to handle the logging.
    Pros: File descriptor/read/writes are handled for you.
    log locations can be changed easily.
    various information can be ignored based on service class.
    Cons: Limited logging facilities.
    Requires a seperate daemon to handle to logging.

11


handling logging yourself:___________________________

Straight forward:

    FILE *log = fopen("/var/log/mydaemon","r");

Now just simply use fprintf(log,"...",...) to print out whatever information you wish to log.

12


Handling logging through syslog:__________________

    #include <syslog.h>  
 
    void openlog(const char *ident, int option, int facility);  
    void closelog();  
    void syslog(int priority, const char *format, va_list ap);

openlog() and closelog() are optional but should be used if for no other reason than without openlog() the ident of the logged messages will be NULL.

Syslog shuttles messages to various system log files based on two parameters: The facility and the priority.

Facility is a general item like: LOG AUTH, LOG CRON, LOG DAEMON, LOG FTP, etc.

The priority is a disjunction of LOG EMERG, LOG ALERT, LOG CRIT, LOG ERR, LOG WARNING, LOG NOTICE, LOG INFO or LOG DEBUG.

13


Syslog Behavior:_____________________________________

a syslog.conf configuration file dictates what facilities and priorities should be logged or ignored and to what files they should be appended.

The configuration file also determines the location of files and which files ceratin facility/priority messages should be appended.

Logging can even be configured to be sent out to be collected by a different machine entirely. (So for instance, all logging information for all of IBM’s servers could be collected and stored on a single machine.)

This allows a daemon to issue copious information and the system administrator can configure syslog to log or ignore certain classes of information.

This makes debugging both:

14


Least privileges:______________________________________

Processes have three user(group) ids associated with them:

  1. The real user id, which is the ID of the user that executed the program.
  2. The effective user id, which is the user id that should be considered in effect (sort of). This user id is established by the setuid bit belonging to the program.2
  3. saved set-user id. which is a copy of what the effective UID was to start with.

You need elevated privileges for certain things such as opening privileges network ports (like port 80) or for creating log files in the /var/log/ directory.

But you don’t want elevated privileges all the time because you don’t want virus to gain these privileges if they take over your program.

15


Dropping privileges (when you are root):_________

Programs like apache start life with a real, effective and saved set user id of “root” and have god-level privileges.

When you are done setting up stuff that requires such power you should relinquish control:

    struct passwd pwbuf;  
    struct passwd *pwbufp;  
    getpwnam_r("nobody", &pwbuf, buf, 1024, &pwbufp);  
    setuid(pwbuf.pw_uid);

The same thing would have to be done with the group id as well.

Since the process was the superuser, after the call to setuid() all three user ids are set to the user “nobody” and can never be switched back to root ever again.

16


Dropping privileges (when you are not root):____

When the process begins life as a user other than root the code is the same for changing effective user and group IDs but the rules change a bit:

Therefore, a normal user can only switch the effective UID between his real user id and the user id of the owner of the setuid program in question.

Because it didn’t start as user root, the program is free to drop and regain its privileges as many times as it want and whenever it wants.

But an advantage to starting life as root is that you can drop privileges to any other user. So for instance, apache2 can run as the user “www” or “nobody”.

17


Putting it all together:______________________________

 
int daemonize(const char *ident)  
{  
  int fd;  
  struct sigaction sa;  
  struct passwd pwbuf;  
  struct passwd *pwbufp;  
  struct group grbuf;  
  struct group *grbufp;  
  char buf[1024];  
 
  // set a decent umask.  
  umask(0); // for more security use 0700 or whatever you need.  
 
 
  // switch to a reasonable current working directory  
  chdir("/");  
 
  // close all file descriptors  
  for (fd=getdtablesize()-1;fd>=0;fd--)  
    close(fd);  
 
  // eliminate the controlling terminal  
  pid = fork();  
  if (pid<0)  return(1); // fork failed. return from daemon that failed.  
  if (pid!=0) exit(0); // parent exits, child is adopted by init  
 
  // establish self as new seesion group leader.  
  setsid();  
 
  //Make sure we can never have a controlling terminal  
  sa.sa_handler = SIG_IGN;  
  sigemptyset(&sa.sa_mask);  
  sa.sa_flags = 0;  
  if (sigaction(SIGHUP, &sa, NULL) < 0)  
       return(2);  // ARGH! HUP is not ignorable, fatal error.  
  pid = fork();  
  if (pid<0)  return(1); // fork failed. return from daemon that failed.  
  if (pid!=0) exit(0); // parent exits, child is again adopted by init  
 
  //optional: attach stdin, stdout, stderr to /dev/null  
  fd = open("/dev/null",O_RDWR);  
  if (fd<0)  return(3); //OOOPS!  
  if (fd!=0) dup2(fd, 0), close(fd);  
  dup(0,1);  
  dup(0,2);  
 
  openlog(ident, 0, LOG_DAEMON);  
  // now the program can call  
  // syslog(LOG_INFO|LOG_DEBUG, "hey I want to show you something");  
  // whenever it wants to tell us something.  
 
  //This should be done elsewhere/later once elevated privileges are  
  // no longer required but is shown here for completeness:  
  if (getpwnam_r("nobody", &pwbuf, buf, 1024, &pwbufp)==NULL)  
    return 4;  
  setuid(pwbuf.pw_uid);  
 
  if getgrnam_r("nobody", &grbuf, buf, 1024, &grbufp)==NULL)  
    return 5;  
  setgid(grbuf.pw_uid);  
}

18