Search code examples
c++linuxforksuexecvp

Load user environment when executing process with execvp();


i have a daemon running which might spawn a Process on incomming connections. This is done using execvp() and fork().

The Problem is that the process should not run as root and the process is dependend on a correct user environment. So im seeking a way to load the user environment.

Currently the process is executed as another user by setting gid and uid. What i have already tried is using as command su -l user -c command, which did not work for some reason.

Does somebody have an idea how i can load the user environment (especially the $HOME variable)?

Thanks in advance.


Solution

  • You might do it like this:

    #include <cstring>
    #include <iostream>
    #include <vector>
    
    #include <stdio.h>
    #include <pwd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    // Version 0 or 1
    #ifndef VERSION
    #define VERSION 1
    #endif
    
    // Please compile twise TEST_PROCESS = 0 and TEST_PROCESS = 1
    #ifndef TEST_PROCESS
    #define TEST_PROCESS 0
    #endif
    
    // User for the process started.
    #ifndef USER
    #error Please define a user (just a name, no string).
    #endif
    
    #define MAKE_STR(x) DO_MAKE_STR(x)
    #define DO_MAKE_STR(x) #x
    #define USER_NAME MAKE_STR(USER)
    
    class UserEnvironment
    {
        public:
        typedef std::vector<char*> Values;
    
        UserEnvironment(const char* user) {
            auto cmd = std::string("su - ") + user + " -c '. ~/.profile; env'";
            FILE* fp = popen(cmd.c_str(), "r");
            if(fp == 0)  perror(0);
            else {
                char* line = 0;
                size_t len = 0;
                ssize_t n = 0;
                while(1 < (n = getline(&line, &len, fp))) {
                    line[n - 1] = 0;
                    m_values.push_back(line);
                    line = 0;
                }
                pclose(fp);
            }
            m_values.push_back(0);
        };
    
        ~UserEnvironment() {
            m_values.pop_back();
            for(char* p: m_values) free(p);
        }
    
        const Values& values() const { return m_values; }
        Values& values() { return m_values; }
        operator const Values& () const { return m_values; }
        operator Values& () { return m_values; }
        char* const * data() const { return m_values.data(); }
        char** data() { return m_values.data(); }
    
        private:
        Values m_values;
    };
    
    inline const char* get_home() {
        const char* home = getenv("HOME");
        if(home == 0) home = "[Missing]";
        return home;
    }
    
    #if ! TEST_PROCESS
    // Debug/Test
    int main() {
    
        const char* user = USER_NAME;
    
        int pid = fork();
        if(pid < 0) return 1;
        else if(pid) {
            const char* home = get_home();
            std::cout << "Running Demon: Home = " << home
                << ", User ID = " << getuid() << std::endl;
            wait(NULL);
        }
        else {
            struct passwd* pw = getpwnam(user);
            if( ! pw) {
                std::cout << "Invalid User [" << user << ']'<< std::endl;
            }
            else {
                char* process = strdup("Debug/TestProcess");
                char *const argv[] = {
                    process,
                    NULL
                };
            #if VERSION == 0
                // Oberwrite environment variables.
                // You might call clearenv();
                if(setgid(pw->pw_gid) == 0 && setuid(pw->pw_uid) == 0) {
                    setenv("HOME", pw->pw_dir, 1);
                    execvp(process, argv);
                }
            #else
                // Use the user environt.
                UserEnvironment environment(user);
                if(setgid(pw->pw_gid) == 0 && setuid(pw->pw_uid) == 0) {
                    execvpe(process, argv, environment.data());
                }
            #endif
                free(process);
            }
        }
    
        const char* home = get_home();
        std::cout << "Exit: Home = " << home << ", User ID = " << getuid() << std::endl;
        return 0;
    }
    
    #else
    
    // Debug/TestProcess
    int main() {
        const char* home = get_home();
        std::cout
             << "Running Process: Home = " << home
             << ", User ID = " << getuid()
             << std::endl;
        system("env");
        exit(0);
    }
    
    #endif
    

    Note: Please compile twice to generate Debug/Test and Debug/TestProcess

    Result:

    Running Demon: Home = /root, User ID = 0
    Running Process: Home = /home/..., User ID = 1000
    ... // The Environment
    Exit: Home = /root, User ID = 0