/* * Abstracts os interface * * Copyright (c) 2006 Ron Pedde (rpedde@users.sourceforge.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #ifdef HAVE_SYS_WAIT_H # include #endif #include #include #include "daapd.h" #include "conf.h" #include "err.h" #include "os.h" /** You say po-tay-to, I say po-tat-o */ #ifndef SIGCLD # define SIGCLD SIGCHLD #endif /** Where to dump the pidfile */ #ifndef PIDFILE #define PIDFILE "/var/run/mt-daapd.pid" #endif /* Forwards */ static int _os_daemon_start(void); /* Globals */ char *_os_pidfile = PIDFILE; /** * this initializes the platform... sets up signal handlers, forks to the * background, etc * * @param foreground whether to run in fg or fork to bg * @returns TRUE on success, FALSE otherwise */ int os_init(int foreground, char *runas) { int pid_fd; FILE *pid_fp=NULL; /* open the pidfile, so it can be written once we detach */ if(!foreground) { if(-1 == (pid_fd = open(_os_pidfile,O_CREAT | O_WRONLY | O_TRUNC, 0644))) { DPRINTF(E_LOG,L_MAIN,"Error opening pidfile (%s): %s\n", _os_pidfile,strerror(errno)); } else { if(0 == (pid_fp = fdopen(pid_fd, "w"))) DPRINTF(E_LOG,L_MAIN,"fdopen: %s\n",strerror(errno)); } _os_daemon_start(); fprintf(pid_fp,"%d\n",getpid()); fclose(pid_fp); DPRINTF(E_DBG,L_MAIN,"Pid: %d\n",getpid()); } // Drop privs here if(os_drop_privs(runas)) { DPRINTF(E_FATAL,L_MAIN,"Error in drop_privs: %s\n",strerror(errno)); } return TRUE; } /** * start syslogging * * @returns TRUE on success, FALSE otherwise */ int os_opensyslog(void) { openlog(PACKAGE,LOG_PID,LOG_DAEMON); return TRUE; } /** * stop syslogging * * @returns TRUE on success, FALSE otherwise */ int os_closesyslog(void) { closelog(); return TRUE; } /** * log a syslog message * * @param level log level (1-9: 1=fatal, 9=debug) * @param msg message to log to the syslog * @returns TRUE on success, FALSE otherwise */ int os_syslog(int level, char *msg) { int priority; switch(level) { case 0: case 1: priority = LOG_ALERT; break; case 2: case 3: case 4: priority = LOG_NOTICE; break; case 5: case 6: case 7: case 8: priority = LOG_INFO; break; case 9: default: priority = LOG_DEBUG; break; } syslog(priority,"%s",msg); return TRUE; } /** * os-specific chown * * */ extern int os_chown(char *path, char *user) { struct passwd *pw=NULL; DPRINTF(E_DBG,L_MISC,"Chowning %s to %s\n",path,user); /* drop privs */ if(getuid() == (uid_t)0) { if(atoi(user)) { pw=getpwuid((uid_t)atoi(user)); /* doh! */ } else { pw=getpwnam(user); } if(pw) { if(initgroups(user,pw->pw_gid) != 0 || chown(path, pw->pw_uid, pw->pw_gid) != 0) { DPRINTF(E_LOG,L_MISC,"Couldn't chown %s, gid=%d, uid=%d\n", user,pw->pw_gid, pw->pw_uid); return FALSE; } } else { DPRINTF(E_LOG,L_MISC,"Couldn't lookup user %s for chown\n",user); return FALSE; } } DPRINTF(E_DBG,L_MISC,"Success!\n"); return TRUE; } /** * find the old service pid and send it a SIGTERM */ int os_signal_server(int what) { FILE *pid_fp; int pid; int result = TRUE; int signal = 0; if(NULL == (pid_fp = fopen(_os_pidfile, "r"))) { DPRINTF(E_LOG,L_MAIN,"fdopen: %s\n",strerror(errno)); return FALSE; } if(fscanf(pid_fp,"%d\n",&pid)) { kill(pid,SIGTERM); } else { DPRINTF(E_LOG,L_MAIN,"os_service_kill: can't get pid from pidfile\n"); result = FALSE; } fclose(pid_fp); switch(what) { case S_SCAN: signal=SIGUSR1; break; case S_FULL: signal=SIGUSR2; break; case S_STOP: signal=SIGTERM; default: break; } if(kill(pid,signal)) { perror("kill"); result = FALSE; } return result; } /** * Fork and exit. Stolen pretty much straight from Stevens. * * @returns 0 on success, -1 with errno set on error */ int _os_daemon_start(void) { int childpid, fd; signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTSTP, SIG_IGN); // Fork and exit if ((childpid = fork()) < 0) { fprintf(stderr, "Can't fork!\n"); return -1; } else if (childpid > 0) exit(0); #ifdef SETPGRP_VOID setpgrp(); #else setpgrp(0,0); #endif #ifdef TIOCNOTTY if ((fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(fd, TIOCNOTTY, (char *) NULL); close(fd); } #endif if((fd = open("/dev/null", O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > 2) close(fd); } /* for (fd = 0; fd < FOPEN_MAX; fd++) close(fd); */ errno = 0; chdir("/"); umask(0); return 0; } /** * Drop privs. This allows mt-daapd to run as a non-privileged user. * Hopefully this will limit the damage it could do if exploited * remotely. Note that only the user need be specified. GID * is set to the primary group of the user. * * \param user user to run as (or UID) */ int os_drop_privs(char *user) { int err; struct passwd *pw=NULL; /* drop privs */ if(getuid() == (uid_t)0) { if(atoi(user)) { pw=getpwuid((uid_t)atoi(user)); /* doh! */ } else { pw=getpwnam(user); } if(pw) { if(initgroups(user,pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0) { err=errno; fprintf(stderr,"Couldn't change to %s, gid=%d, uid=%d\n", user,pw->pw_gid, pw->pw_uid); errno=err; return -1; } } else { err=errno; fprintf(stderr,"Couldn't lookup user %s\n",user); errno=err; return -1; } } return 0; } /** * set the pidfile to a non-default value * * @param file file to use as pidfile */ void os_set_pidfile(char *file) { _os_pidfile = file; } /** * load a shared library * * @param */ void *os_loadlib(char **pe, char *path) { void *retval; if(!(retval = dlopen(path,RTLD_NOW))) *pe = strdup(dlerror()); return retval; } void *os_libfunc(char **pe, void *handle, char *function) { void *retval; if((!(retval = dlsym(handle,function))) && (pe)) *pe = strdup(dlerror()); return retval; } int os_unload(void *handle) { return dlclose(handle); } /** * Determine if an address is local or not * * @param hostaddr the address to test for locality */ int os_islocaladdr(char *hostaddr) { /* how can we check interfaces without something like libnet? */ if(strncmp(hostaddr,"127.",4) == 0) return TRUE; return FALSE; } char *os_apppath(char *parm) { char path[PATH_MAX]; realpath(parm,path); if(strrchr(path,'/')) { *strrchr(path,'/') = '\0'; } return strdup(path); } /** * stat wrapper */ int os_stat(const char *path, struct stat *sb) { return stat(path, sb); } int os_lstat(const char *path, struct stat *sb) { return lstat(path, sb); }