// Berkeley Open Infrastructure for Network Computing
// http://boinc.berkeley.edu
// Copyright (C) 2006 University of California
//
// This is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
//
// This software 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 Lesser General Public License for more details.
//
// To view the GNU Lesser General Public License visit
// http://www.gnu.org/copyleft/lesser.html
// or write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

// SetupSecurity.cpp

#include <Security/Authorization.h>
#include <Security/AuthorizationTags.h>

#include <grp.h>	// getgrname, getgrgid
#include <pwd.h>	// getpwnam, getpwuid, getuid
#include <unistd.h>     // usleep
#include <sys/param.h>  // for MAXPATHLEN
#include <sys/stat.h>
#include <dirent.h>

#include <Carbon/Carbon.h>

#include "file_names.h"
#include "SetupSecurity.h"

static OSStatus UpdateNestedDirectories(char * basepath);
static OSStatus GetAuthorization(void);
OSStatus DoPrivilegedExec(const char *pathToTool, char *arg1, char *arg2, char *arg3, char *arg4, char *arg5);
static pascal Boolean ErrorDlgFilterProc(DialogPtr theDialog, EventRecord *theEvent, short *theItemHit);
static void SleepTicks(UInt32 ticksToSleep);
#ifdef _DEBUG
static OSStatus SetFakeMasterNames(void);
#endif
static OSStatus CreateUserAndGroup(char * user_name, char * group_name);
static OSStatus ResynchSystem(void);

static AuthorizationRef        gOurAuthRef = NULL;

#define DELAY_TICKS 3
#define DELAY_TICKS_R 10


#define REAL_BOINC_MASTER_NAME "boinc_master"
#define REAL_BOINC_PROJECT_NAME "boinc_project"

#ifdef _DEBUG
// GDB can't attach to applications which are running as a diferent user or group so 
//  it ignores the S_ISUID and S_ISGID permisison bits when launching an application.
// To work around this, the _DEBUG version uses the current user and group.
//
// NOTE: The Manager and Client call these routines only "#ifdef _DEBUG" (i.e., 
// only from the DEVELOPMENT BUILD), never from the Deployment build.
//
static char boinc_master_user_name[64];
static char boinc_master_group_name[64];
static char boinc_project_user_name[64];
static char boinc_project_group_name[64];
#else
#define boinc_master_user_name REAL_BOINC_MASTER_NAME
#define boinc_master_group_name REAL_BOINC_MASTER_NAME
#define boinc_project_user_name REAL_BOINC_PROJECT_NAME
#define boinc_project_group_name REAL_BOINC_PROJECT_NAME
#endif

#define MIN_ID 25   /* Minimum user ID / Group ID to create */

static char                    dsclPath[] = "/usr/bin/dscl";
static char                    chmodPath[] = "/bin/chmod";
static char                    chownPath[] = "/usr/sbin/chown";
#define RIGHTS_COUNT 3          /* Count of the 3 above items */

int CreateBOINCUsersAndGroups() {
    OSStatus        err = noErr;

    err = CreateUserAndGroup(REAL_BOINC_MASTER_NAME, REAL_BOINC_MASTER_NAME);
    if (err != noErr)
        return err;

    err = CreateUserAndGroup(REAL_BOINC_PROJECT_NAME, REAL_BOINC_PROJECT_NAME);
    if (err != noErr)
        return err;
    
    err = ResynchSystem();
    if (err != noErr)
        return err;
    
    return noErr;
}


// Pass NULL for path when calling this routine from within BOINC Manager
int SetBOINCAppOwnersGroupsAndPermissions(char *path) {
    char                    fullpath[MAXPATHLEN];
    char                    dir_path[MAXPATHLEN];
    char                    buf1[80];
    ProcessSerialNumber     ourPSN;
    FSRef                   ourFSRef;
    char                    *p;
    OSStatus                err = noErr;
    
#ifdef _DEBUG
    err = SetFakeMasterNames();
    if (err)
        return err;
#endif

    if (path == NULL) {        // NULL means we were called from within BOINC Manager
        // Get the full path to this application's bundle (BOINC Manager's bundle)
        err = GetCurrentProcess (&ourPSN);
        if (err)
            return err;          // Should never happen

        err = GetProcessBundleLocation(&ourPSN, &ourFSRef);
        if (err)
            return err;          // Should never happen
        
        err = FSRefMakePath (&ourFSRef, (UInt8*)dir_path, sizeof(dir_path));
        if (err)
            return err;          // Should never happen
    } else
        strlcpy(dir_path, path, MAXPATHLEN);    // Path to BOINC Manager's bundle was passed as argument
        
    if (strlen(fullpath) >= (MAXPATHLEN-1)) {
        ShowSecurityError("SetBOINCAppOwnersGroupsAndPermissions: path to Manager is too long");
        return -1;
    }

    strlcpy(fullpath, dir_path, sizeof(fullpath));

#ifdef _DEBUG
    // chmod -R u+rw,g+rw,o+r-w path/BOINCManager.app
    // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
    //  read, write permission for user;  read and execute permission for group and others
    err = DoPrivilegedExec(chmodPath, "-R", "u+rw,g+rw,o+r-w", fullpath, NULL, NULL);
#else
    // chmod -R u+r-w,g+r-w,o+r-w path/BOINCManager.app
    // 0555 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
    //  read, write permission for user;  read and execute permission for group and others
    err = DoPrivilegedExec(chmodPath, "-R", "u+r-w,g+r-w,o+r-w", fullpath, NULL, NULL);
#endif
    if (err)
        return err;

    // Get the full path to BOINC Manager executable inside this application's bundle
    strlcat(fullpath, "/Contents/MacOS/", sizeof(fullpath));
    // To allow for branding, assume name of executable inside bundle is same as name of bundle
    p = strrchr(dir_path, '/');         // Assume name of executable inside bundle is same as name of bundle
    if (p == NULL)
        p = dir_path - 1;
    strlcat(fullpath, p+1, sizeof(fullpath));
    p = strrchr(fullpath, '.');         // Strip off  bundle extension (".app")
    if (p)
        *p = '\0'; 

    sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
    // chown boinc_master:boinc_master path/BOINCManager.app/Contents/MacOS/BOINCManager
    err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
    if (err)
        return err;

#ifdef _DEBUG
        // chmod u=rwx,g=rwsx,o=rx path/BOINCManager.app/Contents/MacOS/BOINCManager
        // 02775 = S_ISGID | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
        //  setgid-on-execution plus read, write and execute permission for user, group & others
        err = DoPrivilegedExec(chmodPath, "u=rwx,g=rwsx,o=rx", fullpath, NULL, NULL, NULL);
#else
        // chmod u=rx,g=rsx,o=rx path/BOINCManager.app/Contents/MacOS/BOINCManager
        // 02555 = S_ISGID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
        //  setgid-on-execution plus read and execute permission for user, group & others
        err = DoPrivilegedExec(chmodPath, "u=rx,g=rsx,o=rx", fullpath, NULL, NULL, NULL);
#endif
    if (err)
        return err;

    // Get the full path to BOINC Clients inside this application's bundle
    strlcpy(fullpath, dir_path, sizeof(fullpath));
    strlcat(fullpath, "/Contents/Resources/boinc", sizeof(fullpath));
    if (strlen(fullpath) >= (MAXPATHLEN-1)) {
        ShowSecurityError("SetBOINCAppOwnersGroupsAndPermissions: path to client is too long");
        return -1;
    }
    
    sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
    // chown boinc_master:boinc_master path/BOINCManager.app/Contents/Resources/boinc
    err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
    if (err)
        return err;

#ifdef _DEBUG
        // chmod u=rwsx,g=rwsx,o=rx path/BOINCManager.app/Contents/Resources/boinc
        // 06775 = S_ISUID | S_ISGID | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
        //  setuid-on-execution, setgid-on-execution plus read, write and execute permission for user, group & others
        err = DoPrivilegedExec(chmodPath, "u=rwsx,g=rwsx,o=rx", fullpath, NULL, NULL, NULL);
#else
        // chmod u=rsx,g=rsx,o=rx path/BOINCManager.app/Contents/Resources/boinc
        // 06555 = S_ISUID | S_ISGID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
        //  setuid-on-execution, setgid-on-execution plus read and execute permission for user, group & others
        err = DoPrivilegedExec(chmodPath, "u=rsx,g=rsx,o=rx", fullpath, NULL, NULL, NULL);
#endif
    if (err)
        return err;

    return noErr;
}


int SetBOINCDataOwnersGroupsAndPermissions() {
    FSRef           ref;
    Boolean         isDirectory;
    char            fullpath[MAXPATHLEN];
    char            buf1[80];
    OSStatus        err = noErr;
    OSStatus        result;
    char            *BOINCDataDirPath = "/Library/Application Support/BOINC Data";

#ifdef _DEBUG
    err = SetFakeMasterNames();
    if (err)
        return err;
#endif
    
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);

    // Does BOINC Data directory exist?
    result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result != noErr) || (! isDirectory))
        return dirNFErr;                    // BOINC Data Directory does not exist

    // Set owner and group of BOINC Data directory's contents
    sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
    // chown -R boinc_master:boinc_master "/Library/Application Support/BOINC Data"
    err = DoPrivilegedExec(chownPath, "-R", buf1, fullpath, NULL, NULL);
    if (err)
        return err;

    // Set permissions of BOINC Data directory's contents
    // chmod -R u+rw,g+rw,o-rw "/Library/Application Support/BOINC Data"
    // 0664 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
    // set read and write permission for user and group, no access for others (leaves execute bits unchanged)
    err = DoPrivilegedExec(chmodPath, "-R", "u+rw,g+rw,o+r-w", fullpath, NULL, NULL);
    if (err)
        return err;

#if 0   // Redundant if we already set BOINC Data directory to boinc_master:boinc_master
    // Set owner and group of BOINC Data directory itself
    sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
    // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data"
    err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
    if (err)
        return err;
#endif

    // Set permissions of BOINC Data directory itself
    // chmod u=rwx,g=rwx,o=rx "/Library/Application Support/BOINC Data"
    // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
    //  read, write and execute permission for user & group;  read and execute permission for others
    err = DoPrivilegedExec(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL);
    if (err)
        return err;

    // Does gui_rpc_auth.cfg file exist?
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
    strlcat(fullpath, "/gui_rpc_auth.cfg", MAXPATHLEN);

    result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (! isDirectory)) {
        // Make gui_rpc_auth.cfg file readable and writable only by user boinc_master and group boinc_master

        // Set owner and group of gui_rpc_auth.cfg file
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
        // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data/gui_rpc_auth.cfg"
        err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // chmod u=rw,g=rw,o= "/Library/Application Support/BOINC Data/gui_rpc_auth.cfg"
        // 0660 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
        //  read, write and execute permission for user, group & others
        err = DoPrivilegedExec(chmodPath, "u=rw,g=rw,o=", fullpath, NULL, NULL, NULL);
        if (err)
            return err;
    }           // gui_rpc_auth.cfg

    // Does projects directory exist?
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, PROJECTS_DIR, MAXPATHLEN);

    result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (isDirectory)) {
        // Set owner and group of projects directory's contents
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
        // chown -R boinc_master:boinc_project "/Library/Application Support/BOINC Data/projects"
        err = DoPrivilegedExec(chownPath, "-R", buf1, fullpath, NULL, NULL);
        if (err)
            return err;

        // Set owner and group of projects directory itself
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
        // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data/projects"
        err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // Set permissions for projects directory itself (not its contents)
        // chmod u=rwx,g=rwx,o=rx "/Library/Application Support/BOINC Data/projects"
        // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
        //  read, write and execute permission for user & group;  read and execute permission for others
        err = DoPrivilegedExec(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // Set execute permissions for project subdirectories
        err = UpdateNestedDirectories(fullpath);    // Sets execute for user, group and others
        if (err)
            return err;
    }       // projects directory
    
    // Does slots directory exist?
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, SLOTS_DIR, MAXPATHLEN);

    result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (isDirectory)) {
        // Set owner and group of slots directory's contents
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
        // chown -R boinc_master:boinc_project "/Library/Application Support/BOINC Data/slots"
        err = DoPrivilegedExec(chownPath, "-R", buf1, fullpath, NULL, NULL);
        if (err)
            return err;

        // Set owner and group of slots directory itself
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
        // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data/slots"
        err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // Set permissions for slots directory itself (not its contents)
        // chmod u=rwx,g=rwx,o=rx "/Library/Application Support/BOINC Data/slots"
        // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
        //  read, write and execute permission for user & group;  read and execute permission for others
        err = DoPrivilegedExec(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // Set execute permissions for slot subdirectories
        err = UpdateNestedDirectories(fullpath);    // Sets execute for user, group and others
        if (err)
            return err;
    }       // slots directory

    // Does locale directory exist?
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
    strlcat(fullpath, "/locale", MAXPATHLEN);

    result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (isDirectory)) {
#if 0   // Redundant if we already set contents of BOINC Data directory to boinc_master:boinc_master
        // Set owner and group of locale directory and all its contents
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
        // chown -R boinc_master:boinc_master "/Library/Application Support/BOINC Data/locale"
        err = DoPrivilegedExec(chownPath, "-R", buf1, fullpath, NULL, NULL);
        if (err)
            return err;
#endif

        // chmod -R u+r-w,g+r-w,o= "/Library/Application Support/BOINC Data/locale"
        // 0550 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXUSR
        // set read and execute only permission for user, group, no access for others
        err = DoPrivilegedExec(chmodPath, "-R", "u+r-w,g+r-w,o=", fullpath, NULL, NULL);
        if (err)
            return err;
    }       // locale directory
    
    // Does switcher directory exist?
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, SWITCHER_DIR, MAXPATHLEN);

    result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (isDirectory)) {
#if 0   // Redundant if we already set contents of BOINC Data directory to boinc_master:boinc_master
        // Set owner and group of switcher directory
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
        // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data/switcher"
        err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
        if (err)
            return err;
#endif

        // chmod u=rwx,g=rwx,o=x "/Library/Application Support/BOINC Data/switcher"
        // 0550 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP
        // set read and execute permission for user and group, no access for others
        err = DoPrivilegedExec(chmodPath, "u=rx,g=rx,o=", fullpath, NULL, NULL, NULL);
        if (err)
            return err;
    }       // switcher directory

    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, SWITCHER_FILE_NAME, MAXPATHLEN);
        result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (! isDirectory)) {
        // Set owner and group of switcher application
        sprintf(buf1, "root:%s", boinc_master_group_name);
        // chown root:boinc_master "/Library/Application Support/BOINC Data/switcher/switcher"
        err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // Set permissions of switcher application
        // chmod u=s,g=rx,o= "/Library/Application Support/BOINC Data/switcher/switcher"
        // 04050 = S_ISUID | S_IRGRP | S_IXGRP
        //  setuid-on-execution, setgid-on-execution plus read and execute permission for group boinc_master only
        err = DoPrivilegedExec(chmodPath, "u=s,g=rx,o=", fullpath, NULL, NULL, NULL);
        if (err)
            return err;
    }       // switcher application
    
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, SWITCHER_DIR, MAXPATHLEN);
    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, SETPROJECTGRP_FILE_NAME, MAXPATHLEN);
        result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (! isDirectory)) {
        // Set owner and group of setprojectgrp application
        sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
        // chown boinc_master:boinc_project "/Library/Application Support/BOINC Data/switcher/setprojectgrp"
        err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // Set permissions of setprojectgrp application
        // chmod u=rx,g=rsx,o= "/Library/Application Support/BOINC Data/switcher/setprojectgrp"
        // 02500 = S_ISGID | S_IRUSR | S_IXUSR
        //  setgid-on-execution plus read and execute permission for user only
        err = DoPrivilegedExec(chmodPath, "u=rx,g=s,o=", fullpath, NULL, NULL, NULL);
        if (err)
            return err;
    }       // setprojectgrp application

#ifdef __APPLE__
#if 0       // AppStats is deprecated as of version 5.8.15
    strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, SWITCHER_DIR, MAXPATHLEN);
    strlcat(fullpath, "/", MAXPATHLEN);
    strlcat(fullpath, APP_STATS_FILE_NAME, MAXPATHLEN);
        result = FSPathMakeRef((StringPtr)fullpath, &ref, &isDirectory);
    if ((result == noErr) && (! isDirectory)) {
        // Set owner and group of AppStats application (must be setuid root)
        sprintf(buf1, "root:%s", boinc_master_group_name);
        // chown root:boinc_project "/Library/Application Support/BOINC Data/switcher/setprojectgrp"
        err = DoPrivilegedExec(chownPath, buf1, fullpath, NULL, NULL, NULL);
        if (err)
            return err;

        // Set permissions of AppStats application
        // chmod u=rsx,g=rx,o= "/Library/Application Support/BOINC Data/switcher/setprojectgrp"
        // 04550 = S_ISUID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP
        //  setuid-on-execution plus read and execute permission for user and group
        err = DoPrivilegedExec(chmodPath, "u=rsx,g=rx,o=", fullpath, NULL, NULL, NULL);
        if (err)
            return err;
    }       // setprojectgrp application
#endif
#endif  // __APPLE__

    return noErr;
}


static OSStatus UpdateNestedDirectories(char * basepath) {
    Boolean         isDirectory;
    char            fullpath[MAXPATHLEN];
    struct stat     sbuf;
    OSStatus        retval = 0;
    DIR             *dirp;
    dirent          *dp;

    dirp = opendir(basepath);
    if (dirp == NULL)           // Should never happen
        return -1;
        
    while (true) {
        dp = readdir(dirp);
        if (dp == NULL)
            break;                  // End of list
            
        if (dp->d_name[0] == '.')
            continue;               // Ignore names beginning with '.'

        strlcpy(fullpath, basepath, sizeof(fullpath));
        strlcat(fullpath, "/", sizeof(fullpath));
        strlcat(fullpath, dp->d_name, sizeof(fullpath));

        retval = stat(fullpath, &sbuf);
        if (retval)
            break;              // Should never happen
            
        isDirectory = S_ISDIR(sbuf.st_mode);

        if (isDirectory) {
            // chmod u+x,g+x,o+x fullpath
            // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
            //  read, write and execute permission for user & group;  read and execute permission for others
            retval = DoPrivilegedExec(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL);
            if (retval)
                break;

            retval = UpdateNestedDirectories(fullpath);
            if (retval)
                break;
        }
            
    }       // End while (true)
    
    closedir(dirp);

    return retval;
}


static OSStatus CreateUserAndGroup(char * user_name, char * group_name) {
    OSStatus        err = noErr;
    passwd          *pw = NULL;
    group           *grp = NULL;
    uid_t           userid = 0;
    gid_t           groupid = 0;
    gid_t           usergid = 0;
    Boolean         userExists = false;
    Boolean         groupExists = false;
    short           i;
    static short    start_id = MIN_ID;
    char            buf1[80];
    char            buf2[80];
    char            buf3[80];
    
    pw = getpwnam(user_name);
    if (pw) {
        userid = pw->pw_uid;
        userExists = true;
    }

    grp = getgrnam(group_name);
    if (grp) {
        groupid = grp->gr_gid;
        groupExists = true;
    }
    
    if ( userExists && groupExists )
        return noErr;       // User and group already exist

    // If only user or only group exists, try to use the same ID for the one we create
    if (userExists) {      // User exists but group does not
        usergid = pw->pw_gid;
        if (usergid) {
            grp = getgrgid(usergid);
            if (grp == NULL)    // Set the group ID = users existing group if this group ID is available
                groupid = usergid;
        }
        if (groupid == 0) {
            grp = getgrgid(userid);
            if (grp == NULL)    // Set the group ID = user ID if this group ID is available
                groupid = userid;
        }
    } else {
        if (groupExists) {      // Group exists but user does not
           pw = getpwuid(groupid);
            if (pw == NULL)    // Set the user ID = group ID if this user ID is available
                userid = groupid;
        }
    }
    
    // We need to find an available user ID, group ID, or both.  Find a value that is currently 
    // neither a user ID or a group ID.
    // If we need both a new user ID and a new group ID, finds a value that can be used for both.
    if ( (userid == 0) || (groupid == 0) ) {
        for(i=start_id; ; i++) {
           if ((uid_t)i != userid) {
                pw = getpwuid((uid_t)i);
                if (pw)
                    continue;               // Already exists as a user ID of a different user
            }
            
            if ((gid_t)i != groupid) {
                grp = getgrgid((gid_t)i);
                if (grp)
                    continue;               // Already exists as a group ID of a different group
            }
            
            if (! userExists)
                userid = (uid_t)i;
            if (! groupExists)
                groupid = (gid_t)i;

            start_id = i + 1;               // Start with next higher value next time
                
            break;                          // Success!
        }
    }
    
    sprintf(buf1, "/groups/%s", group_name);
    sprintf(buf2, "%d", groupid);

    if (! groupExists) {             // If we need to create group
        // Something like "dscl . -create /groups/boinc_master"
        err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, NULL, NULL);
        if (err)
            return err;
 
        // Something like "dscl . -create /groups/boinc_master gid 33"
        err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, "gid", buf2);
        if (err)
            return err;
    }           // if (! groupExists)

    sprintf(buf1, "/users/%s", user_name);
    sprintf(buf2, "%d", groupid);
    sprintf(buf3, "%d", userid);
        
    if (! userExists) {             // If we need to create user
        // Something like "dscl . -create /users/boinc_master"
        err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, NULL, NULL);
        if (err)
            return err;

        // Something like "dscl . -create /users/boinc_master uid 33"
        err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, "uid", buf3);
        if (err)
            return err;

        // Prevent a security hole by not allowing a login from this user
        // Something like "dscl . -create /users/boinc_master shell /usr/bin/false"
        err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, "shell", "/usr/bin/false");
        if (err)
            return err;

        // Something like "dscl . -create /users/boinc_master home /var/empty"
        err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, "home", "/var/empty");
        if (err)
            return err;
    }           // if (! userExists)

    // Always set the user gid if we created either the user or the group or both
    // Something like "dscl . -create /users/boinc_master gid 33"
    err = DoPrivilegedExec(dsclPath, ".", "-create", buf1, "gid", buf2);
    if (err)
        return err;

    err = ResynchSystem();
    if (err != noErr)
        return err;

    SleepTicks(120);

    return noErr;
}


int AddAdminUserToGroups(char *user_name) {        
#ifndef _DEBUG
    char            buf1[80];
    OSStatus        err = noErr;

    sprintf(buf1, "/groups/%s", boinc_master_group_name);

    // "dscl . -merge /groups/boinc_master users user_name"
    err = DoPrivilegedExec(dsclPath, ".", "-merge", buf1, "users", user_name);
    if (err)
        return err;

    sprintf(buf1, "/groups/%s", boinc_project_group_name);

    // "dscl . -merge /groups/boinc_project users user_name"
    err = DoPrivilegedExec(dsclPath, ".", "-merge", buf1, "users", user_name);
    if (err)
        return err;

    err = ResynchSystem();
    if (err != noErr)
        return err;

#endif          // ! _DEBUG    
    return noErr;
}


static OSStatus ResynchSystem() {
    long            response;
    OSStatus        err = noErr;
   
    err = Gestalt(gestaltSystemVersion, &response);
    if (err) return err;
    
    if (response >= 0x1050) {
        // OS 10.5
        err = system("dscacheutil -flushcache");
        err = system("dsmemberutil flushcache");
        return noErr;
    }
    
    err = system("lookupd -flushcache");

    err = Gestalt(gestaltSystemVersion, &response);
    if ((err == noErr) && (response >= 0x1040))
        err = system("memberd -r");           // Available only in OS 10.4

    return noErr;
}


#ifdef _DEBUG
// GDB can't attach to applications which are running as a diferent user or group so 
//  it ignores the S_ISUID and S_ISGID permisison bits when launching an application.
// To work around this, the _DEBUG version uses the current user and group.
static OSStatus SetFakeMasterNames() {
    passwd              *pw;
    group               *grp;
    gid_t               boinc_master_gid;
    uid_t               boinc_master_uid;
    long                response;
    OSStatus            err = noErr;

    boinc_master_uid = geteuid();
    pw = getpwuid(boinc_master_uid);
    if (pw == NULL)
        return -1;      // Should never happen
    strlcpy(boinc_master_user_name, pw->pw_name, sizeof(boinc_master_user_name));

    boinc_master_gid = getegid();
    grp = getgrgid(boinc_master_gid);
    if (grp == NULL)
        return -1;
    strlcpy(boinc_master_group_name, grp->gr_name, sizeof(boinc_master_group_name));
    
    err = Gestalt(gestaltSystemVersion, &response);
#ifndef DEBUG_WITH_FAKE_PROJECT_USER_AND_GROUP
    if ((err == noErr) && (response >= 0x1040)) {
        // For better debugging of SANDBOX permissions logic
        strlcpy(boinc_project_user_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_user_name));
        strlcpy(boinc_project_group_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_group_name));
    } else 
#endif
    {
        // For easier debugging of project applications; required under OS 10.3.x
        strlcpy(boinc_project_user_name, pw->pw_name, sizeof(boinc_project_user_name));
        strlcpy(boinc_project_group_name, grp->gr_name, sizeof(boinc_project_group_name));
     }
     
    return noErr;
}
#endif


static OSStatus GetAuthorization (void) {
    static Boolean              sIsAuthorized = false;
    AuthorizationRights         ourAuthRights;
    AuthorizationFlags          ourAuthFlags;
    AuthorizationItem           ourAuthRightsItem[RIGHTS_COUNT];
    AuthorizationEnvironment    ourAuthEnvironment;
    AuthorizationItem           ourAuthEnvItem[1];
    char                        prompt[] = "BOINC needs to have certain permissions set up.\n\n";
    OSStatus                    err = noErr;

    if (sIsAuthorized)
        return noErr;
        
    ourAuthRights.count = 0;
    ourAuthRights.items = NULL;

    err = AuthorizationCreate (&ourAuthRights, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &gOurAuthRef);
    if (err != noErr) {
        ShowSecurityError("AuthorizationCreate returned error %d", err);
        return err;
    }
     
    ourAuthRightsItem[0].name = kAuthorizationRightExecute;
    ourAuthRightsItem[0].value = dsclPath;
    ourAuthRightsItem[0].valueLength = strlen (dsclPath);
    ourAuthRightsItem[0].flags = 0;

    ourAuthRightsItem[1].name = kAuthorizationRightExecute;
    ourAuthRightsItem[1].value = chmodPath;
    ourAuthRightsItem[1].valueLength = strlen (chmodPath);
    ourAuthRightsItem[1].flags = 0;

    ourAuthRightsItem[2].name = kAuthorizationRightExecute;
    ourAuthRightsItem[2].value = chownPath;
    ourAuthRightsItem[2].valueLength = strlen (chownPath);
    ourAuthRightsItem[2].flags = 0;

#if AUTHORIZE_LOOKUPD_MEMBERD
    ourAuthRightsItem[3].name = kAuthorizationRightExecute;
    ourAuthRightsItem[3].value = lookupdPath;
    ourAuthRightsItem[3].valueLength = strlen (lookupdPath);
    ourAuthRightsItem[3].flags = 0;

    ourAuthRightsItem[4].name = kAuthorizationRightExecute;
    ourAuthRightsItem[4].value = memberdPath;
    ourAuthRightsItem[4].valueLength = strlen (memberdPath);
    ourAuthRightsItem[4].flags = 0;
#endif

    ourAuthRights.count = RIGHTS_COUNT;
    ourAuthRights.items = ourAuthRightsItem;

    ourAuthEnvItem[0].name = kAuthorizationEnvironmentPrompt;
    ourAuthEnvItem[0].value = prompt;
    ourAuthEnvItem[0].valueLength = strlen (prompt);
    ourAuthEnvItem[0].flags = 0;

    ourAuthEnvironment.count = 1;
    ourAuthEnvironment.items = ourAuthEnvItem;

    
    ourAuthFlags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
    
    // When this is called from the installer, the installer has already authenticated.  
    // In that case we are already running with full root privileges so AuthorizationCopyRights() 
    // does not request a password from the user again.
    err = AuthorizationCopyRights (gOurAuthRef, &ourAuthRights, &ourAuthEnvironment, ourAuthFlags, NULL);
    
    if (err == noErr)
        sIsAuthorized = true;
    
    return err;
}

OSStatus DoPrivilegedExec(const char *pathToTool, char *arg1, char *arg2, char *arg3, char *arg4, char *arg5) {
    short               i;
    char                *args[8];
    OSStatus            err;
    FILE                *ioPipe;
    char                *p, junk[256];

    err = GetAuthorization();
    if (err != noErr) {
        if (err == errAuthorizationCanceled)
            return err;
        ShowSecurityError("GetAuthorization returned error %d", err);
    } else {
        for (i=0; i<5; i++) {       // Retry 5 times if error
            args[0] = arg1;
            args[1] = arg2;
            args[2] = arg3;
            args[3] = arg4;
            args[4] = arg5;
            args[5] = NULL;

            err = AuthorizationExecuteWithPrivileges (gOurAuthRef, pathToTool, 0, args, &ioPipe);
            if (err == noErr) {
                if (ioPipe) {
                    // We use the pipe to signal us when the command has completed
                    do {
                        p = fgets(junk, sizeof(junk), ioPipe);
                    } while (p);
                    
                    fclose (ioPipe);
                }
            }
#if 0
            if (strcmp(arg2, "-R") == 0)
                SleepTicks(DELAY_TICKS_R);
            else
                SleepTicks(DELAY_TICKS);
#endif
            if (err == noErr)
                break;
        }
    }
    if (err != noErr)
        ShowSecurityError("\"%s %s %s %s %s %s\" returned error %d", pathToTool, 
                            arg1 ? arg1 : "", arg2 ? arg2 : "", arg3 ? arg3 : "", 
                            arg4 ? arg4 : "", arg5 ? arg5 : "", err);

       return err;
}



void ShowSecurityError(const char *format, ...) {
    va_list                 args;
    char                    s[1024];
    short                   itemHit;
    AlertStdAlertParamRec   alertParams;
    ModalFilterUPP          ErrorDlgFilterProcUPP;
    
    ProcessSerialNumber	ourProcess;

    va_start(args, format);
    s[0] = vsprintf(s+1, format, args);
    va_end(args);

    ErrorDlgFilterProcUPP = NewModalFilterUPP(ErrorDlgFilterProc);

    alertParams.movable = true;
    alertParams.helpButton = false;
    alertParams.filterProc = ErrorDlgFilterProcUPP;
    alertParams.defaultText = "\pOK";
    alertParams.cancelText = NULL;
    alertParams.otherText = NULL;
    alertParams.defaultButton = kAlertStdAlertOKButton;
    alertParams.cancelButton = 0;
    alertParams.position = kWindowDefaultPosition;

    ::GetCurrentProcess (&ourProcess);
    ::SetFrontProcess(&ourProcess);

    StandardAlert (kAlertStopAlert, (StringPtr)s, NULL, &alertParams, &itemHit);

    DisposeModalFilterUPP(ErrorDlgFilterProcUPP);
}


static pascal Boolean ErrorDlgFilterProc(DialogPtr theDialog, EventRecord *theEvent, short *theItemHit) {
    // We need this because this is a command-line application so it does not get normal events
    if (Button()) {
        *theItemHit = kStdOkItemIndex;
        return true;
    }
    
    return StdFilterProc(theDialog, theEvent, theItemHit);
}

// Uses usleep to sleep for full duration even if a signal is received
static void SleepTicks(UInt32 ticksToSleep) {
    UInt32 endSleep, timeNow, ticksRemaining;

    timeNow = TickCount();
    ticksRemaining = ticksToSleep;
    endSleep = timeNow + ticksToSleep;
    while ( (timeNow < endSleep) && (ticksRemaining <= ticksToSleep) ) {
        usleep(16667 * ticksRemaining);
        timeNow = TickCount();
        ticksRemaining = endSleep - timeNow;
    } 
}
