]> granicus.if.org Git - fcron/commitdiff
Merge branch 'fcron-3.0'
authorThibault Godouet <fcron@free.fr>
Fri, 24 Sep 2010 15:09:36 +0000 (16:09 +0100)
committerThibault Godouet <fcron@free.fr>
Fri, 24 Sep 2010 15:09:36 +0000 (16:09 +0100)
Fixed some typos related to env var in job.c

Conflicts:
configure
configure.in
doc/en/todo.sgml
fcrondyn.c
fcrontab.c
read_string.c
subs.c

12 files changed:
1  2 
Makefile.in
doc/Makefile.in
doc/en/changes.sgml
doc/fr/todo.sgml
fcron.c
fcrondyn.c
fcrontab.c
fileconf.c
job.c
save.c
subs.c
subs.h

diff --cc Makefile.in
Simple merge
diff --cc doc/Makefile.in
Simple merge
index fb44b26a0bb01a69fb9d3a05db3dc5ebe7f745a4,cfbaaea982ce657bddd44df629713c05335a36c5..2e59c2336b5b2e8066d177d8f336a6225ad087cc
@@@ -13,18 -13,14 +13,24 @@@ A copy of the license is included in gf
     <sect1 id="changes">
        <title>Changes</title>
  
 +      <itemizedlist>
 +       <title>From version 3.0.5 to 3.1.0</title>
 +       <listitem>
 +          <para>Pass fcrondyn client credentials through the socket when possible so as the user doesn't need to type his password when using fcrondyn.</para>
 +         </listitem>
 +       <listitem>
 +          <para>Code clean-up: Implemented generic unordred list for lavgq / exeq.</para>
 +         </listitem>
 +      </itemizedlist>
 +
        <itemizedlist>
         <title>From version 3.0.4 to 3.0.5</title>
+        <listitem>
+           <para>Security issue: fixed security issue that allowed malicious user to read any file readable by group 'fcron' (in particular fcron's config files and the fcrontabs of non-root users). On systems without seteuid() (i.e. quite exotic systems) then the bug is harder to exploit but any file could be read.</para>
+        </listitem>
+        <listitem>
+           <para>Improved general security of fcrontab and fcrondyn by dropping more privileges.</para>
+        </listitem>
         <listitem>
            <para>Work on portability (aix, hpux, irix, tru64 unix, solaris). Thanks Peter O'Gorman for his patch!</para>
         </listitem>
index 4182be6578ab46a43ac6a614ec4a4184b74aafc4,f302ff76d9e60e2f294a26f936d3fa8deb062023..507d5e77c86efd7f60c8b430e88c3a46ee2bd9f6
@@@ -126,4 -126,4 +126,3 @@@ mode: sgm
  sgml-parent-document:("fcron-doc.sgml" "book" "chapter" "sect1" "")
  End:
  -->
--   
diff --cc fcron.c
Simple merge
diff --cc fcrondyn.c
Simple merge
diff --cc fcrontab.c
index 3645137bf02a7e03cfb9af1ed00de52e0a294b4a,cb265bdefa106fccb4d2f77ca40c49c26c1291e8..fb905eb16682eced9d32dc7f94025d66685ecbfb
@@@ -645,11 -655,14 +649,14 @@@ install_stdin(void
  
      while ( (c = getc(stdin)) != EOF )
        putc(c, tmp_file);
 -    debug("Copied stdin to %s, about to parse file %s...", tmp_str, tmp_str); 
+     /* // */
++    debug("Copied stdin to %s, about to parse file %s...", tmp_str, tmp_str);
  
-     /* the following closes tmp_fd as well because it was fdopen()ed: */
-     fclose(tmp_file);
+     /* don't closes tmp_fd as it will be used for make_file(): */
+     if ( fflush(tmp_file) != 0 )
+         die_e("Could not fflush(%s)", tmp_file);
  
-     if ( make_file(tmp_str) == ERR )
+     if ( make_file(tmp_str, tmp_fd) == ERR )
        goto exiterr;
      else
        goto exit;
@@@ -991,7 -1004,14 +998,14 @@@ main(int argc, char **argv
      if (strrchr(argv[0],'/')==NULL) prog_name = argv[0];
      else prog_name = strrchr(argv[0],'/')+1;
      
-     uid = getuid();
+     useruid = getuid();
+     usergid = getgid();
+ #ifdef USE_SETE_ID
+     /* drop any suid privilege (that we use to write files) but keep sgid
+      * one for now: we need it for read_conf() and is_allowed() */
 -    seteuid_safe(useruid); 
++    seteuid_safe(useruid);
+ #endif
  
      errno = 0;
      if ( ! (pass = getpwnam(USERNAME)) )
      parseopt(argc, argv);
  
  #ifdef USE_SETE_ID
 -    seteuid_safe(useruid); 
 -    setegid_safe(usergid); 
+     /* drop any privilege we may have: we will only get them back
+      * temporarily every time we need it. */
++    seteuid_safe(useruid);
++    setegid_safe(usergid);
+ #endif
  
  #ifdef HAVE_LIBPAM
      /* Open PAM session for the user and obtain any security
diff --cc fileconf.c
index 062f39c337a9d0a1a764fc3d7e58e9ba05668fb4,574fdc80159b968dc6045a1b93def7be60fc047e..fa2d2334fe85863994dd19fa88b2a994de4006d0
@@@ -150,21 -147,8 +150,21 @@@ get_line(char *str, size_t size, FILE *
  
  }
  
 +void
 +init_default_line(cl_t *cl, cf_t *cf)
 +/* clear all context/options from cl */
 +{
 +    bzero(cl, sizeof(cl_t));
 +    Set(cl->cl_runas, runas);
 +    Set(cl->cl_mailto, runas);
 +    free_safe(cl->cl_tz);
 +    set_default_opt(cl->cl_option);
 +    cl->cl_file = cf;
 +}
 +
 +
  int
- read_file(char *filename)
+ read_file(char *filename, int fd)
      /* read file "name" and append cf_t list */
  {
      cf_t *cf = NULL;
        return ERR;
      }
  
+     /* Rewind, just in case */
+     rewind(file);
      Alloc(cf, cf_t);
 +    cf->cf_env_list = env_list_init();
      cf->cf_user = strdup2(user);
 -    default_line.cl_file = cf;
 -    default_line.cl_runas = strdup2(runas);
 -    default_line.cl_mailto = strdup2(runas);
 -    default_line.cl_tz = NULL;
 -    set_default_opt(default_line.cl_option);
 +    init_default_line(&default_line, cf);
  
      if ( debug_opt )
        fprintf(stderr, "FILE %s\n", file_name);
      cf->cf_next = file_base;
      file_base = cf;
  
-     fclose(file);
+     /* don't close as underlying fd may still be used by calling function */
+     if (fflush(file) != 0)
+         error_e("could not fflush() file_name");
      
 -    free(default_line.cl_runas);
 -    free(default_line.cl_mailto);
 -    free(default_line.cl_tz);
 +    free_safe(default_line.cl_runas);
 +    free_safe(default_line.cl_mailto);
 +    free_safe(default_line.cl_tz);
  
      if ( ! need_correction )
        return OK;
diff --cc job.c
index 5815609e7e4d6f7800924dbaf5d63b6783de43f8,b748b49f20e4cf7ce3f3a31f80f2fe52d9471d85..e7f1b8fe46c81eb182b804e4c97ea3b675954dde
--- 1/job.c
--- 2/job.c
+++ b/job.c
  #include "fcron.h"
  
  #include "job.h"
 +#include "temp_file.h"
  
  void sig_dfl(void);
 -void end_job(cl_t *line, int status, FILE *mailf, short mailpos);
 +void end_job(cl_t *line, int status, FILE *mailf, short mailpos, char **sendmailenv);
  void end_mailer(cl_t *line, int status);
  #ifdef HAVE_LIBPAM
--void die_mail_pame(cl_t *cl, int pamerrno, struct passwd *pas, char *str);
++void die_mail_pame(cl_t *cl, int pamerrno, struct passwd *pas, char *str, env_list_t *env);
  #endif
  #define PIPE_READ 0
  #define PIPE_WRITE 1
  int read_write_pipe(int fd, void *buf, size_t size, int action);
  int read_pipe(int fd, void *to, size_t size);
  int write_pipe(int fd, void *buf, size_t size);
 -
 -#ifndef HAVE_SETENV
 -char env_user[PATH_LEN];
 -char env_logname[PATH_LEN];
 -char env_home[PATH_LEN];
 -char env_shell[PATH_LEN];
 -char env_tz[PATH_LEN];
 -#endif
++void become_user(struct cl_t *cl, struct passwd *pas, char *home);
  
  #ifdef WITH_SELINUX
  extern char **environ;
@@@ -51,11 -58,12 +52,11 @@@ die_mail_pame(cl_t *cl, int pamerrno, s
  {
      char buf[MAX_MSG];
  
 -    strncpy(buf, str, sizeof(buf)-1);
 -    strncat(buf, " for '%s'", sizeof(buf)-strlen(buf)-1);
 -    buf[sizeof(buf)-1]='\0';
 +    snprintf(buf, sizeof(buf), "%s for user '%s'", str, pas->pw_name);
  
      if (is_mail(cl->cl_option)) {
 -      FILE *mailf = create_mail(cl, "Could not run fcron job");
 +        char **envp = env_list_export_envp(env);
-       FILE *mailf = create_mail(cl, "Could not run fcron job", NULL, envp);
++      FILE *mailf = create_mail(cl, "Could not run fcron job", envp);
  
        /* print the error in both syslog and a file, in order to mail it to user */
        if (dup2(fileno(mailf), 1) != 1 || dup2(1, 2) != 2)
  
        pam_end(pamh, pamerrno);  
  
-         become_user(cl, pas, "/")
 -      /* Change running state to the user in question : it's safer to run the mail 
 -       * as user, not root */
 -      if (initgroups(pas->pw_name, pas->pw_gid) < 0)
 -          die_e("initgroups failed: %s", pas->pw_name);
 -      if (setgid(pas->pw_gid) < 0) 
 -          die("setgid failed: %s %d", pas->pw_name, pas->pw_gid);
 -      if (setuid(pas->pw_uid) < 0) 
 -          die("setuid failed: %s %d", pas->pw_name, pas->pw_uid);
++        become_user(cl, pas, "/");
  
 -      launch_mailer(cl, mailf);
 +      launch_mailer(cl, mailf, envp);
        /* launch_mailer() does not return : we never get here */
      }
      else
  }
  #endif
  
 -int
 -change_user(struct cl_t *cl)
 +void
 +become_user(struct cl_t *cl, struct passwd *pas, char *home)
 +/* Become the user who owns the job: change privileges, check PAM authorization,
 + * and change dir to HOME. */
  {
 -    struct passwd *pas;
 +
 +#ifndef RUN_NON_PRIVILEGED
 +    if (pas == NULL)
 +        die("become_user() called with a NULL struct passwd");
 +
 +   /* Change running state to the user in question */
 +    if (initgroups(pas->pw_name, pas->pw_gid) < 0)
 +        die_e("initgroups failed: %s", pas->pw_name);
 +
 +    if (setgid(pas->pw_gid) < 0)
 +        die("setgid failed: %s %d", pas->pw_name, pas->pw_gid);
 +
 +    if (setuid(pas->pw_uid) < 0)
 +        die("setuid failed: %s %d", pas->pw_name, pas->pw_uid);
 +#endif /* not RUN_NON_PRIVILEGED */
 +
 +    /* make sure HOME is defined and change dir to it */
 +    if (chdir(home) != 0) {
 +        error_e("Could not chdir to HOME dir '%s'. Trying to chdir to '/'.", home);
 +        if (chdir("/") < 0)
 +            die_e("Could not chdir to HOME dir /");
 +    }
 +
 +}
 +
 +void
 +setup_user_and_env(struct cl_t *cl, struct passwd *pas,
 +                   char ***sendmailenv, char ***jobenv, char **curshell, char **curhome)
 +/* Check PAM authorization, and setup the environment variables
 + * to run sendmail and to run the job itself. Change dir to HOME and check if SHELL is ok */
 +/* (*curshell) and (*curhome) will be allocated and should thus be freed
 + * if curshell and curhome are not NULL. */
 +/* Return the the two env var sets, the shell to use to execle() commands and the home dir */
 +
 +{
 +    env_list_t *env_list = env_list_init();
 +    env_t *e = NULL;
 +    char *path = NULL;
 +    char *myshell = NULL;
  #ifdef HAVE_LIBPAM
      int    retcode = 0;
--    const char * const * env;
++    char **env;
  #endif
  
 -    /* Obtain password entry and change privileges */
 +    if (pas == NULL)
 +        die("setup_user_and_env() called with a NULL struct passwd");
 +
 +    env_list_setenv(env_list, "USER", pas->pw_name, 1);
 +    env_list_setenv(env_list, "LOGNAME", pas->pw_name, 1);
 +    env_list_setenv(env_list, "HOME", pas->pw_dir, 1);
 +    /* inherit fcron's PATH for sendmail. We will later change it to DEFAULT_JOB_PATH
 +     * or a user defined PATH for the job itself */
 +    path = getenv("PATH");
 +    env_list_setenv(env_list, "PATH", ( path != NULL ) ? path : DEFAULT_JOB_PATH, 1);
  
 -    errno = 0;
 -    if ((pas = getpwnam(cl->cl_runas)) == NULL) 
 -        die_e("failed to get passwd fields for user \"%s\"", cl->cl_runas);
 -    
 -#ifdef HAVE_SETENV
 -    setenv("USER", pas->pw_name, 1);
 -    setenv("LOGNAME", pas->pw_name, 1);
 -    setenv("HOME", pas->pw_dir, 1);
      if (cl->cl_tz != NULL)
 -      setenv("TZ", cl->cl_tz, 1);
 +        env_list_setenv(env_list, "TZ", cl->cl_tz, 1);
      /* To ensure compatibility with Vixie cron, we don't use the shell defined
       * in /etc/passwd by default, but the default value from fcron.conf instead: */
 -    if ( *shell == '\0' )
 -      /* shell is empty, ie. not defined: use value from /etc/passwd */
 -      setenv("SHELL", pas->pw_shell, 1);
 +    if ( shell != NULL && shell[0] != '\0' )
 +        /* default: use value from fcron.conf */
 +        env_list_setenv(env_list, "SHELL", shell, 1);
      else
 -      /* default: use value from fcron.conf */
 -      setenv("SHELL", shell, 1);
 -#else
 -    {
 -      strcpy(env_user, "USER=");
 -      strncat(env_user, pas->pw_name, sizeof(env_user)-5-1);
 -      env_user[sizeof(env_user)-1]='\0';
 -      putenv( env_user ); 
 -
 -      strcpy(env_logname, "LOGNAME=");
 -      strncat(env_logname, pas->pw_name, sizeof(env_logname)-8-1);
 -      env_logname[sizeof(env_logname)-1]='\0';
 -      putenv( env_logname ); 
 -
 -      strcpy(env_home, "HOME=");
 -      strncat(env_home, pas->pw_dir, sizeof(env_home)-5-1);
 -      env_home[sizeof(env_home)-1]='\0';
 -      putenv( env_home );
 -
 -      if (cl->cl_tz != NULL) {
 -          strcpy(env_tz, "TZ=");
 -          strncat(env_tz, pas->pw_dir, sizeof(env_tz)-3-1);
 -          env_tz[sizeof(env_tz)-1]='\0';
 -          putenv( env_tz );
 -      }
 +        /* shell is empty, ie. not defined: fail back to /etc/passwd's value */
 +        env_list_setenv(env_list, "SHELL", pas->pw_shell, 1);
  
 -      strcpy(env_shell, "SHELL=");
 -      /* To ensure compatibility with Vixie cron, we don't use the shell defined
 -       * in /etc/passwd by default, but the default value from fcron.conf instead: */
 -      if ( *shell == '\0' )
 -          /* shell is empty, ie. not defined: use value from /etc/passwd */
 -          strncat(env_shell, pas->pw_shell, sizeof(env_shell)-6-1);
 -      else
 -          /* default: use value from fcron.conf */
 -          strncat(env_shell, shell, sizeof(env_shell)-6-1);
 -      env_shell[sizeof(env_shell)-1]='\0';
 -      putenv( env_shell );
 -    }
 -#endif /* HAVE_SETENV */
 -
 -#ifdef HAVE_LIBPAM
 +#if ( ! defined(RUN_NON_PRIVILEGED)) && defined(HAVE_LIBPAM)
      /* Open PAM session for the user and obtain any security
         credentials we might need */
  
       * we must set auth to pam_permit. */
      retcode = pam_authenticate(pamh, PAM_SILENT);
      if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas,
 -                                            "Could not authenticate PAM user");
 +                                            "Could not authenticate PAM user", env_list);
      retcode = pam_acct_mgmt(pamh, PAM_SILENT); /* permitted access? */
      if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas,
 -                                            "Could not init PAM account management");
 +                                            "Could not init PAM account management", env_list);
      retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT);
      if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas, 
 -                                            "Could not set PAM credentials");
 +                                            "Could not set PAM credentials", env_list);
      retcode = pam_open_session(pamh, PAM_SILENT);
      if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas,
 -                                            "Could not open PAM session");
 +                                            "Could not open PAM session", env_list);
  
-     for (env = (const char * const *)pam_getenvlist(pamh); env && *env; env++) {
 -    env = (const char * const *) pam_getenvlist(pamh);
 -    while (env && *env) {
 -      if (putenv((char*) *env)) die_e("Could not copy PAM environment");
 -      env++;
++    for (env = pam_getenvlist(pamh); env && *env; env++) {
 +        env_list_putenv(env_list, *env, 1);
      }
  
      /* Close the log here, because PAM calls openlog(3) and
diff --cc save.c
Simple merge
diff --cc subs.c
index 4a79dd2a4dada82d2d2bf0f3411ca237feafd90c,83949e6c08062752f1f1ecd41b27d7b04835e04c..151bd51c5b2ecc3f92691108e8908f2a22138f7b
--- 1/subs.c
--- 2/subs.c
+++ b/subs.c
@@@ -59,6 -77,242 +59,242 @@@ get_group_gid_safe(char *groupname
  
  }
  
 -     * make it as it would be if we had seteuid() 
+ #ifdef USE_SETE_ID
+ void
+ seteuid_safe(uid_t euid)
+ /* set the euid if different from the current one, and die on error */
+ {
+     /* on BSD, one can only seteuid() to the real UID or saved UID,
+      * and NOT the euid, so we don't call seteuid(geteuid()),
+      * which is why we need to check if a change is needed */
+     if (geteuid() != euid && seteuid(euid) != 0)
+         die_e("could not change euid to %d", euid);
+ }
+ void
+ setegid_safe(gid_t egid)
+ /* set the egid if different from the current one, and die on error */
+ {
+     /* on BSD, one can only setegid() to the real GID or saved GID,
+      * and NOT the egid, so we don't call setegid(getegid()),
+      * which is why we need to check if a change is needed */
+     if (getegid() != egid && setegid(egid) != 0)
+         die_e("could not change egid to %d", egid);
+ }
+ #endif /* def USE_SETE_ID */
+ #ifdef USE_SETE_ID
+ int
+ open_as_user(const char *pathname, uid_t openuid, gid_t opengid, int flags, ...)
+ /* Become user and call open(), then revert back to who we were.
+  * NOTE: when flags & O_CREAT, the 5th argument is mode_t and must be set
+  *       -- it is ignored otherwise */
+ {
+     uid_t orig_euid = geteuid();
+     gid_t orig_egid = getegid();
+     struct stat s;
+     int fd = -1;
+     va_list ap;
+     mode_t mode = (mode_t) 0;
+     if (flags & O_CREAT) {
+         va_start(ap, flags);
+         mode = va_arg(ap, mode_t);
+         va_end(ap);
+     }
+     seteuid_safe(openuid);
+     setegid_safe(opengid);
+     if (flags & O_CREAT) {
+         fd = open(pathname, flags, mode);
+     }
+     else
+         fd = open(pathname, flags);
+     /* change the effective uid/gid back to original values */
+     seteuid_safe(orig_euid);
+     setegid_safe(orig_egid);
+     /* if open() didn't fail make sure we opened a 'normal' file */
+     if ( fd >= 0 ) {
+         if ( fstat(fd, &s) < 0 ) {
+             error_e("open_as_user(): could not fstat %s", pathname);
+             if ( close(fd) < 0 )
+                 error_e("open_as_user: could not close() %s", pathname);
+             fd = -1;
+         }
+         if ( ! S_ISREG(s.st_mode) || s.st_nlink != 1 ) {
+             error_e("open_as_user(): file %s is not a regular file", pathname);
+             if ( close(fd) < 0 )
+                 error_e("open_as_user: could not close() %s", pathname);
+             errno = 0;
+             fd = -1;
+         }
+     }
+     return fd;
+ }
+ #else /* def USE_SETE_ID */
+ int
+ open_as_user(const char *pathname, uid_t openuid, gid_t opengid, int flags, ...)
+ /* Become user and call open(), then revert back to who we were.
+  * As seteuid() is not available on this system attempt to similate that behavior
+  * as closely as possible.
+  * NOTE: when flags & O_CREAT, the 5th argument is mode_t and must be set
+  *       -- it is ignored otherwise */
+ {
+     int fd = -1;
+     struct stat s;
+     va_list ap;
+     mode_t mode = (mode_t) 0;
+     if (flags & O_CREAT) {
+         va_start(ap, flags);
+         mode = va_arg(ap, mode_t);
+         va_end(ap);
+     }
+     /* In case a flag as O_TRUNC is set, we should test if the user
+      * is allowed to open the file before we open it.
+      * There will always be a risk of race-condition between the test
+      * and the open but that's the best we can realistically do
+      * without seteuid()... */
+     if ( stat(pathname, &s) == 0 ) {
+         if ( ! ( s.st_mode & S_IROTH
+                     || ( s.st_uid == openuid && s.st_mode & S_IRUSR )
+                     || ( s.st_gid == opengid && s.st_mode & S_IRGRP ) ) ) {
+             error("open_as_user(): file %s does not pass the security test: "
+                     "uid=%d gid=%d mode=%lo openuid=%d opengid=%d",
+                     pathname, s.st_uid, s.st_gid, s.st_mode, openuid, opengid);
+             errno = EACCES;
+             return -1;
+         }
+     }
+     else if ( errno == ENOENT ) {
+         /* the file doesn't exist so no risk to truncate the wrong file! */
+         ;
+     }
+     else {
+         error_e("open_as_user(): could not stat %s", pathname);
+         return -1;
+     }
+     if (flags & O_CREAT) {
+         fd = open(pathname, flags, mode);
+     }
+     else
+         fd = open(pathname, flags);
+     if ( fd < 0 )
+         /* we couldn't open the file */
+         return fd;
+     /* if open() didn't fail make sure we opened a 'normal' file */
+     if ( fstat(fd, &s) < 0 ) {
+         error_e("open_as_user(): could not fstat %s", pathname);
+         goto err;
+     }
+     if ( ! S_ISREG(s.st_mode) || s.st_nlink != 1 ) {
+         error_e("open_as_user(): file %s is not a regular file", pathname);
+         goto err;
+     }
+     /* we couldn't become openuid/opengid, so check manually if the user
+      * is allowed to read that file
+      * We do that again as a malicious user could have replaced the file
+      * by another one (e.g. a link) between the stat() and the open() earlier */
+     if ( ! ( s.st_mode & S_IROTH
+             || ( s.st_uid == openuid && s.st_mode & S_IRUSR )
+             || ( s.st_gid == opengid && s.st_mode & S_IRGRP ) ) ) {
+         error("open_as_user(): file %s does not pass the security test: "
+                 "uid=%d gid=%d mode=%lo openuid=%d opengid=%d",
+                 pathname, s.st_uid, s.st_gid, s.st_mode, openuid, opengid);
+         errno = EACCES;
+         goto err;
+     }
+     /* if we created a new file, change the file ownership:
++     * make it as it would be if we had seteuid()
+      * NOTE: if O_CREAT was set without O_EXCL and the file existed before
+      *       then we will end up changing the ownership even if the seteuid()
+      *       version of that function wouldn't have. That shouldn't break
+      *       anything though. */
+     if ( (flags & O_CREAT) && fchown(fd, openuid, opengid) != 0) {
+         error_e("Could not fchown %s to uid:%d gid:%d", pathname, openuid, opengid);
+         if ( close(fd) < 0 )
+             error_e("open_as_user: could not close() %s", pathname);
+         return -1;
+     }
+     /* everything went ok: return the file descriptor */
+     return fd;
+ err:
+     if ( fd >= 0 && close(fd) < 0 )
+         error_e("open_as_user: could not close() %s", pathname);
+     return -1;
+ }
+ #endif /* def USE_SETE_ID */
+ int
+ remove_as_user(const char *pathname, uid_t removeuid, gid_t removegid)
+ /* Become user and call remove(), then revert back to who we were */
+ {
+     int rval = -1;
+ #ifdef USE_SETE_ID
+     uid_t orig_euid = geteuid();
+     gid_t orig_egid = getegid();
+     seteuid_safe(removeuid);
+     setegid_safe(removegid);
+ #endif /* def USE_SETE_ID */
+     rval = remove(pathname);
+ #ifdef USE_SETE_ID
+     seteuid_safe(orig_euid);
+     setegid_safe(orig_egid);
+ #endif /* def USE_SETE_ID */
+     return rval;
+ }
+ int
+ rename_as_user(const char *oldpath, const char *newpath, uid_t renameuid, gid_t renamegid)
+ /* Become user and call rename(), then revert back to who we were */
+ {
+     int rval = -1;
+ #ifdef USE_SETE_ID
+     uid_t orig_euid = geteuid();
+     gid_t orig_egid = getegid();
+     seteuid_safe(renameuid);
+     setegid_safe(renamegid);
+ #endif /* def USE_SETE_ID */
+     rval = rename(oldpath, newpath);
+ #ifdef USE_SETE_ID
+     seteuid_safe(orig_euid);
+     setegid_safe(orig_egid);
+ #endif /* def USE_SETE_ID */
+     return rval;
+ }
  int
  remove_blanks(char *str)
      /* remove blanks at the the end of str */
diff --cc subs.h
index d94ca34c4055624d7714ab83fb5441957dbe5033,15e6961793e5fa47b95cc80b5cdcdb7a6ef699e5..e35eb425c0f453192353f852a226fd709e10972c
--- 1/subs.h
--- 2/subs.h
+++ b/subs.h
  /* functions prototypes */
  extern uid_t get_user_uid_safe(char *username);
  extern gid_t get_group_gid_safe(char *groupname);
+ extern void seteuid_safe(uid_t euid);
+ extern void setegid_safe(uid_t egid);
+ extern int remove_as_user(const char *pathname, uid_t removeuid, gid_t removegid);
+ extern int open_as_user(const char *pathname, uid_t openuid, gid_t opengid, int flags, ...);
+ extern int rename_as_user(const char *oldpath, const char *newpath, uid_t renameuid, gid_t renamegid);
  extern int remove_blanks(char *str);
 +extern int strcmp_until(const char *left, const char *right, char until);
  extern char *strdup2(const char *);
 +extern void *alloc_safe(size_t len, const char * desc);
 +extern void *realloc_safe(void *ptr, size_t len, const char * desc);
 +extern void free_safe(void *ptr);
  extern int get_word(char **str);
 -extern int temp_file(char **name);
 -extern void read_conf(void);
 -extern void free_conf(void);
 +extern void my_unsetenv(const char *name);
 +extern void my_setenv_overwrite(const char *name, const char *value);
  
  #endif /* __SUBS_H__ */