1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2017 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
17 
18 // SetupSecurity.cpp
19 
20 
21 #include <Carbon/Carbon.h>
22 
23 #include <grp.h>	// getgrname, getgrgid
24 #include <pwd.h>	// getpwnam, getpwuid, getuid
25 #include <unistd.h>     // usleep
26 #include <sys/param.h>  // for MAXPATHLEN
27 #include <sys/stat.h>
28 #include <dirent.h>
29 #include <spawn.h>
30 
31 #include "file_names.h"
32 #include "mac_util.h"
33 #include "SetupSecurity.h"
34 
35 // Set VERBOSE_TEST to 1 for debugging DoSudoPosixSpawn()
36 #define VERBOSE_TEST 0
37 
38 static OSStatus UpdateNestedDirectories(char * basepath);
39 static OSStatus MakeXMLFilesPrivate(char * basepath);
40 static OSStatus DoSudoPosixSpawn(const char *pathToTool, char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6);
41 #ifndef __x86_64__
42 static pascal Boolean ErrorDlgFilterProc(DialogPtr theDialog, EventRecord *theEvent, short *theItemHit);
43 #endif
44 #ifdef _DEBUG
45 static OSStatus SetFakeMasterNames(void);
46 #endif
47 static OSStatus CreateUserAndGroup(char * user_name, char * group_name);
48 static double dtime(void);
49 static void SleepSeconds(double seconds);
50 
51 #if VERBOSE_TEST
52 extern void print_to_log_file(const char *format, ...);
53 #endif
54 
55 #define DELAY_SECONDS 0.05
56 #define DELAY_SECONDS_R 0.167
57 
58 #define REAL_BOINC_MASTER_NAME "boinc_master"
59 #define REAL_BOINC_PROJECT_NAME "boinc_project"
60 
61 #ifdef _DEBUG
62 // GDB can't attach to applications which are running as a diferent user or group so
63 // it ignores the S_ISUID and S_ISGID permission bits when launching an application.
64 // To work around this, the _DEBUG version uses the current user and group.
65 //
66 // NOTE: The Manager and Client call these routines only "#ifdef _DEBUG" (i.e.,
67 // only from the DEVELOPMENT BUILD), never from the Deployment build.
68 //
69 // As of January, 2017: In the past, the client and BOINC Manager used to call
70 // routines in this source file when debugging with SANDBOX defined, but they
71 // can no longer do so because various operations here must be performed as root
72 // and the AuthorizationExecuteWithPrivileges() API was deprecated as of OS 10.7.
73 // Please see the comments in check_security.cpp for more details describing the
74 // new method replacing that approach. Because the new approach has not been
75 // thoroughly tested, we have not yet removed the client-specific code or the
76 // manager-specific code from this file.
77 //
78 static char boinc_master_user_name[64];
79 static char boinc_master_group_name[64];
80 static char boinc_project_user_name[64];
81 static char boinc_project_group_name[64];
82 #else
83 #define boinc_master_user_name REAL_BOINC_MASTER_NAME
84 #define boinc_master_group_name REAL_BOINC_MASTER_NAME
85 #define boinc_project_user_name REAL_BOINC_PROJECT_NAME
86 #define boinc_project_group_name REAL_BOINC_PROJECT_NAME
87 #endif
88 
89 #define MIN_ID 501   /* Minimum user ID / Group ID to create */
90 
91 static char                    dsclPath[] = "/usr/bin/dscl";
92 static char                    chmodPath[] = "/bin/chmod";
93 static char                    chownPath[] = "/usr/sbin/chown";
94 #define RIGHTS_COUNT 3          /* Count of the 3 above items */
95 
CreateBOINCUsersAndGroups()96 int CreateBOINCUsersAndGroups() {
97     OSStatus        err = noErr;
98 
99     if (geteuid() != 0) {
100         ShowSecurityError("CreateBOINCUsersAndGroups must be called as root");
101     }
102 
103     err = CreateUserAndGroup(REAL_BOINC_MASTER_NAME, REAL_BOINC_MASTER_NAME);
104     if (err != noErr)
105         return err;
106 
107     err = CreateUserAndGroup(REAL_BOINC_PROJECT_NAME, REAL_BOINC_PROJECT_NAME);
108     if (err != noErr)
109         return err;
110 
111     err = ResynchDSSystem();
112     if (err != noErr)
113         return err;
114 
115     return noErr;
116 }
117 
118 
119 // Pass NULL for path when calling this routine from within BOINC Manager
SetBOINCAppOwnersGroupsAndPermissions(char * path)120 int SetBOINCAppOwnersGroupsAndPermissions(char *path) {
121     char                    fullpath[MAXPATHLEN];
122     char                    dir_path[MAXPATHLEN];
123     char                    buf1[80];
124     char                    *p;
125     struct stat             sbuf;
126     Boolean                 isDirectory;
127     OSStatus                err = noErr;
128 
129 #define NUMBRANDS 3
130 
131 char *saverName[NUMBRANDS];
132 
133 saverName[0] = "BOINCSaver";
134 saverName[1] = "GridRepublic";
135 saverName[2] = "Progress Thru Processors";
136 
137     if (geteuid() != 0) {
138         ShowSecurityError("SetBOINCAppOwnersGroupsAndPermissions must be called as root");
139     }
140 
141 
142 #ifdef _DEBUG
143     err = SetFakeMasterNames();
144     if (err)
145         return err;
146 #endif
147 
148     if (path == NULL) {        // NULL means we were called from within BOINC Manager
149         // Get the full path to this application's bundle (BOINC Manager's bundle)
150         dir_path[0] = '\0';
151         // Get the full path to our executable inside this application's bundle
152         getPathToThisApp(dir_path, sizeof(dir_path));
153         if (!dir_path[0]) {
154             ShowSecurityError(false, false, false, "Couldn't get path to self.");
155             return -1;
156         }
157 
158         // To allow for branding, assume name of executable inside bundle is same as name of bundle
159         p = strrchr(dir_path, '/');         // Assume name of executable inside bundle is same as name of bundle
160         if (p == NULL)
161             p = dir_path - 1;
162         strlcpy(fullpath, p+1, sizeof(fullpath));
163         p = strrchr(fullpath, '.');         // Strip off bundle extension (".app")
164         if (p)
165             *p = '\0';
166 
167         strlcat(dir_path, "/Contents/MacOS/", sizeof(dir_path));
168         strlcat(dir_path, fullpath, sizeof(dir_path));
169     } else {
170         if (strlen(path) >= (MAXPATHLEN-1)) {
171             ShowSecurityError("SetBOINCAppOwnersGroupsAndPermissions: path to Manager is too long");
172             return -1;
173         }
174 
175         strlcpy(dir_path, path, MAXPATHLEN);    // Path to BOINC Manager's bundle was passed as argument
176     }
177 
178     strlcpy(fullpath, dir_path, sizeof(fullpath));
179 
180 #ifdef _DEBUG
181     // chmod -R u=rwx,g=rwx,o=rx path/BOINCManager.app
182     // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
183     // Set read, write permission for user;  read and execute permission for group and others
184     err = DoSudoPosixSpawn(chmodPath, "-R", "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL);
185 #else
186     // chmod -R u=rx,g=rx,o=rx path/BOINCManager.app
187     // 0555 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
188     // Set read, write permission for user;  read and execute permission for group and others
189     err = DoSudoPosixSpawn(chmodPath, "-R", "u=rx,g=rx,o=rx", fullpath, NULL, NULL, NULL);
190 #endif
191     if (err)
192         return err;
193 
194     // Get the full path to BOINC Manager executable inside this application's bundle
195     strlcat(fullpath, "/Contents/MacOS/", sizeof(fullpath));
196     // To allow for branding, assume name of executable inside bundle is same as name of bundle
197     p = strrchr(dir_path, '/');         // Assume name of executable inside bundle is same as name of bundle
198     if (p == NULL)
199         p = dir_path - 1;
200     strlcat(fullpath, p+1, sizeof(fullpath));
201     p = strrchr(fullpath, '.');         // Strip off  bundle extension (".app")
202     if (p)
203         *p = '\0';
204 
205     sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
206     // chown boinc_master:boinc_master path/BOINCManager.app/Contents/MacOS/BOINCManager
207     err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
208     if (err)
209         return err;
210 
211 #ifdef _DEBUG
212         // chmod u=rwx,g=rwx,o=rx path/BOINCManager.app/Contents/MacOS/BOINCManager
213         // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
214         // Set read, write and execute permission for user & group, read & execute for others
215         err = DoSudoPosixSpawn(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL, NULL);
216 #else
217         // chmod u=rx,g=rx,o=rx path/BOINCManager.app/Contents/MacOS/BOINCManager
218         // 0555 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
219         // Set read and execute permission for user, group & others
220         err = DoSudoPosixSpawn(chmodPath, "u=rx,g=rx,o=rx", fullpath, NULL, NULL, NULL, NULL);
221 #endif
222     if (err)
223         return err;
224 
225     // Get the full path to BOINC Clients inside this application's bundle
226     strlcpy(fullpath, dir_path, sizeof(fullpath));
227     strlcat(fullpath, "/Contents/Resources/boinc", sizeof(fullpath));
228     if (strlen(fullpath) >= (MAXPATHLEN-1)) {
229         ShowSecurityError("SetBOINCAppOwnersGroupsAndPermissions: path to client is too long");
230         return -1;
231     }
232 
233     sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
234     // chown boinc_master:boinc_master path/BOINCManager.app/Contents/Resources/boinc
235     err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
236     if (err)
237         return err;
238 
239 #ifdef _DEBUG
240         // chmod u=rwsx,g=rwsx,o=rx path/BOINCManager.app/Contents/Resources/boinc
241         // 06775 = S_ISUID | S_ISGID | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
242         // Set setuid-on-execution, setgid-on-execution plus read, write and execute permission for user & group, read & execute for others
243         err = DoSudoPosixSpawn(chmodPath, "u=rwsx,g=rwsx,o=rx", fullpath, NULL, NULL, NULL, NULL);
244 #else
245         // chmod u=rsx,g=rsx,o=rx path/BOINCManager.app/Contents/Resources/boinc
246         // 06555 = S_ISUID | S_ISGID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
247         // Set setuid-on-execution, setgid-on-execution plus read and execute permission for user, group & others
248         err = DoSudoPosixSpawn(chmodPath, "u=rsx,g=rsx,o=rx", fullpath, NULL, NULL, NULL, NULL);
249 #endif
250     if (err)
251         return err;
252 
253     for (int i=0; i<NUMBRANDS; i++) {
254         // Version 6 screensaver has its own embedded switcher application, but older versions don't.
255         // We don't allow unauthorized users to run the switcher application in the BOINC Data directory
256         // because they could use it to run as user & group boinc_project and damage project files.
257         // The screensaver's switcher application runs as user and group "nobody" to avoid this risk.
258 
259         // Does switcher exist in screensaver bundle?
260         sprintf(fullpath, "/Library/Screen Savers/%s.saver/Contents/Resources/gfx_switcher", saverName[i]);
261         err = stat(fullpath, &sbuf);
262         isDirectory = S_ISDIR(sbuf.st_mode);
263         if ((err == noErr) && (! isDirectory)) {
264 #ifdef _DEBUG
265             sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
266             // chown boinc_master:boinc_master "/Library/Screen Savers/BOINCSaver.saver/Contents/Resources/gfx_switcher"
267             err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
268             if (err)
269                 return err;
270 
271             // chmod u=rwx,g=rwx,o=rx "/Library/Screen Savers/BOINCSaver.saver/Contents/Resources/gfx_switcher"
272             // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
273             // Set read, write and execute permission for user & group;  read and execute permission for others
274             err = DoSudoPosixSpawn(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL, NULL);
275             if (err)
276                 return err;
277 #else
278             sprintf(buf1, "root:%s", boinc_master_group_name);
279             // chown root:boinc_master "/Library/Screen Savers/BOINCSaver.saver/Contents/Resources/gfx_switcher"
280             err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
281             if (err)
282                 return err;
283 
284             // chmod u=rsx,g=rx,o=rx "/Library/Screen Savers/BOINCSaver.saver/Contents/Resources/gfx_switcher"
285             // 04555 = S_ISUID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
286             // Set setuid-on-execution plus read and execute permission for user, group & others
287             err = DoSudoPosixSpawn(chmodPath, "u=rsx,g=rx,o=rx", fullpath, NULL, NULL, NULL, NULL);
288             if (err)
289                 return err;
290 #endif
291         }
292     }
293 
294     return noErr;
295 }
296 
297 
SetBOINCDataOwnersGroupsAndPermissions()298 int SetBOINCDataOwnersGroupsAndPermissions() {
299     Boolean         isDirectory;
300     char            fullpath[MAXPATHLEN];
301     char            buf1[80];
302     struct stat     sbuf;
303     OSStatus        err = noErr;
304     OSStatus        result;
305     char            *BOINCDataDirPath = "/Library/Application Support/BOINC Data";
306 
307     if (geteuid() != 0) {
308         ShowSecurityError("SetBOINCDataOwnersGroupsAndPermissions must be called as root");
309     }
310 
311 #ifdef _DEBUG
312     err = SetFakeMasterNames();
313     if (err)
314         return err;
315 #endif
316 
317     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
318 
319     // Does BOINC Data directory exist?
320     result = stat(fullpath, &sbuf);
321     isDirectory = S_ISDIR(sbuf.st_mode);
322     if ((result != noErr) || (! isDirectory))
323         return dirNFErr;                    // BOINC Data Directory does not exist
324 
325     // Set owner and group of BOINC Data directory's contents
326     sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
327     // chown -R boinc_master:boinc_master "/Library/Application Support/BOINC Data"
328     err = DoSudoPosixSpawn(chownPath, "-R", buf1, BOINCDataDirPath, NULL, NULL, NULL);
329     if (err)
330         return err;
331 
332 #if 0   // Redundant if we already set BOINC Data directory to boinc_master:boinc_master
333     // Set owner and group of BOINC Data directory itself
334     sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
335     // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data"
336     err = DoSudoPosixSpawn(chownPath, buf1, BOINCDataDirPath, NULL, NULL, NULL, NULL);
337     if (err)
338         return err;
339 #endif
340 
341     // Set permissions of BOINC Data directory's contents:
342     //   ss_config.xml is world-readable so screensaver coordinator can read it
343     //   all other *.xml are not world-readable to keep authenticators private
344     //   gui_rpc_auth.cfg is not world-readable to keep RPC password private
345     //   all other files are world-readable so default screensaver can read them
346 
347     // First make all files world-readable (temporarily)
348     // chmod -R u+rw,g+rw,o+r-w "/Library/Application Support/BOINC Data"
349     // 0661 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
350     // Set read and write permission for user and group, read-only for others (leaves execute bits unchanged)
351     err = DoSudoPosixSpawn(chmodPath, "-R", "u+rw,g+rw,o+r-w", BOINCDataDirPath, NULL, NULL, NULL);
352     if (err)
353         return err;
354 
355     // Next make gui_rpc_auth.cfg not world-readable to keep RPC password private
356     // Does gui_rpc_auth.cfg file exist?
357     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
358     strlcat(fullpath, "/", MAXPATHLEN);
359     strlcat(fullpath, GUI_RPC_PASSWD_FILE, MAXPATHLEN);
360 
361     result = stat(fullpath, &sbuf);
362     isDirectory = S_ISDIR(sbuf.st_mode);
363     if ((result == noErr) && (! isDirectory)) {
364         // Make gui_rpc_auth.cfg file readable and writable only by user boinc_master and group boinc_master
365 
366         // Set owner and group of gui_rpc_auth.cfg file
367         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
368         // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data/gui_rpc_auth.cfg"
369         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
370         if (err)
371             return err;
372 
373         // chmod u=rw,g=rw,o= "/Library/Application Support/BOINC Data/gui_rpc_auth.cfg"
374         // 0660 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
375         // Set read and write permission for user and group, no access for others
376         err = DoSudoPosixSpawn(chmodPath, "u=rw,g=rw,o=", fullpath, NULL, NULL, NULL, NULL);
377         if (err)
378             return err;
379     }           // gui_rpc_auth.cfg
380 
381     // Next make all *.xml files not world-readable to keep authenticators private
382     err = MakeXMLFilesPrivate(BOINCDataDirPath);
383     if (err)
384         return err;
385 
386     // Next make ss_config.xml world-readable so screensaver coordinator can read it
387     // Does screensaver config file ss_config.xml exist?
388     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
389     strlcat(fullpath, "/", MAXPATHLEN);
390     strlcat(fullpath, SS_CONFIG_FILE, MAXPATHLEN);
391 
392     result = stat(fullpath, &sbuf);
393     isDirectory = S_ISDIR(sbuf.st_mode);
394     if ((result == noErr) && (! isDirectory)) {
395         // Make ss_config.xml file world readable but writable only by user boinc_master and group boinc_master
396 
397         // Set owner and group of ss_config.xml file
398         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
399         // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data/ss_config.xml"
400         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
401         if (err)
402             return err;
403 
404         // chmod u=rw,g=rw,o=r "/Library/Application Support/BOINC Data/ss_config.xml"
405         // 0664 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
406         // Set read and write permission for user and group, read-only for others
407         err = DoSudoPosixSpawn(chmodPath, "u=rw,g=rw,o=r", fullpath, NULL, NULL, NULL, NULL);
408         if (err)
409             return err;
410     }           // ss_config.xml
411 
412 
413     // Set permissions of BOINC Data directory itself
414     // chmod u=rwx,g=rwx,o=x "/Library/Application Support/BOINC Data"
415     // 0771 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IXOTH
416     // Set read, write and execute permission for user & group; execute-only permission for others
417     err = DoSudoPosixSpawn(chmodPath, "u=rwx,g=rwx,o=x", BOINCDataDirPath, NULL, NULL, NULL, NULL);
418     if (err)
419         return err;
420 
421 
422     // Does projects directory exist?
423     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
424     strlcat(fullpath, "/", MAXPATHLEN);
425     strlcat(fullpath, PROJECTS_DIR, MAXPATHLEN);
426 
427     result = stat(fullpath, &sbuf);
428     isDirectory = S_ISDIR(sbuf.st_mode);
429     if ((result == noErr) && (isDirectory)) {
430         // Set owner and group of projects directory and it's contents
431         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
432         // chown -R boinc_master:boinc_project "/Library/Application Support/BOINC Data/projects"
433         err = DoSudoPosixSpawn(chownPath, "-Rh", buf1, fullpath, NULL, NULL, NULL);
434         if (err)
435             return err;
436 
437 #if 0       // Redundant if the same as projects directory's contents
438         // Set owner and group of projects directory itself
439         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
440         // chown -R boinc_master:boinc_project "/Library/Application Support/BOINC Data/projects"
441         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
442         if (err)
443             return err;
444 #endif
445 
446         // Set permissions of project directories' contents
447         // Contents of project directories must be world-readable so BOINC Client can read
448         // files written by projects which have user boinc_project and group boinc_project
449         // chmod -R u+rw,g+rw,o+r-w "/Library/Application Support/BOINC Data/projects"
450         // 0664 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
451         // set read and write permission for user and group, no access for others (leaves execute bits unchanged)
452         err = DoSudoPosixSpawn(chmodPath, "-R", "u+rw,g+rw,o+r-w", fullpath, NULL, NULL, NULL);
453         if (err)
454             return err;
455 
456         // Set permissions for projects directory itself (not its contents)
457         // chmod u=rwx,g=rwx,o= "/Library/Application Support/BOINC Data/projects"
458         // 0770 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP
459         // Set read, write and execute permission for user & group, no access for others
460         err = DoSudoPosixSpawn(chmodPath, "u=rwx,g=rwx,o=", fullpath, NULL, NULL, NULL, NULL);
461         if (err)
462             return err;
463 
464         // Set execute permissions for project subdirectories
465         err = UpdateNestedDirectories(fullpath);    // Sets execute for user, group and others
466         if (err)
467             return err;
468     }       // projects directory
469 
470     // Does slots directory exist?
471     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
472     strlcat(fullpath, "/", MAXPATHLEN);
473     strlcat(fullpath, SLOTS_DIR, MAXPATHLEN);
474 
475     result = stat(fullpath, &sbuf);
476     isDirectory = S_ISDIR(sbuf.st_mode);
477     if ((result == noErr) && (isDirectory)) {
478         // Set owner and group of slots directory and it's contents
479         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
480         // chown -R boinc_master:boinc_project "/Library/Application Support/BOINC Data/slots"
481         err = DoSudoPosixSpawn(chownPath, "-Rh", buf1, fullpath, NULL, NULL, NULL);
482         if (err)
483             return err;
484 
485 #if 0       // Redundant if the same as slots directory's contents
486         // Set owner and group of slots directory itself
487         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
488         // chown boinc_master:boinc_project "/Library/Application Support/BOINC Data/slots"
489         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
490         if (err)
491             return err;
492 #endif
493 
494         // Set permissions of slot directories' contents
495         // Contents of slot directories must be world-readable so BOINC Client can read
496         // files written by projects which have user boinc_project and group boinc_project
497         // chmod -R u+rw,g+rw,o+r-w "/Library/Application Support/BOINC Data/slots"
498         // 0664 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
499         // set read and write permission for user and group, no access for others (leaves execute bits unchanged)
500         err = DoSudoPosixSpawn(chmodPath, "-R", "u+rw,g+rw,o+r-w", fullpath, NULL, NULL, NULL);
501         if (err)
502             return err;
503 
504         // Set permissions for slots directory itself (not its contents)
505         // chmod u=rwx,g=rwx,o= "/Library/Application Support/BOINC Data/slots"
506         // 0770 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP
507         // Set read, write and execute permission for user & group, no access for others
508         err = DoSudoPosixSpawn(chmodPath, "u=rwx,g=rwx,o=", fullpath, NULL, NULL, NULL, NULL);
509         if (err)
510             return err;
511 
512         // Set execute permissions for slot subdirectories
513         err = UpdateNestedDirectories(fullpath);    // Sets execute for user, group and others
514         if (err)
515             return err;
516     }       // slots directory
517 
518     // Does locale directory exist?
519     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
520     strlcat(fullpath, "/locale", MAXPATHLEN);
521 
522     result = stat(fullpath, &sbuf);
523     isDirectory = S_ISDIR(sbuf.st_mode);
524     if ((result == noErr) && (isDirectory)) {
525 #if 0   // Redundant if we already set contents of BOINC Data directory to boinc_master:boinc_master
526         // Set owner and group of locale directory and all its contents
527         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
528         // chown -R boinc_master:boinc_master "/Library/Application Support/BOINC Data/locale"
529         err = DoSudoPosixSpawn(chownPath, "-R", buf1, fullpath, NULL, NULL, NULL);
530         if (err)
531             return err;
532 #endif
533 
534         // chmod -R u+r-w,g+r-w,o+r-w "/Library/Application Support/BOINC Data/locale"
535         // 0550 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXUSR | S_IROTH | S_IXOTH
536         // Set execute permission for user, group, and others if it was set for any
537         err = DoSudoPosixSpawn(chmodPath, "-R", "+X", fullpath, NULL, NULL, NULL);
538         // Set read-only permission for user, group, and others (leaves execute bits unchanged)
539         err = DoSudoPosixSpawn(chmodPath, "-R", "u+r-w,g+r-w,o+r-w", fullpath, NULL, NULL, NULL);
540         if (err)
541             return err;
542     }       // locale directory
543 
544     // Does switcher directory exist?
545     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
546     strlcat(fullpath, "/", MAXPATHLEN);
547     strlcat(fullpath, SWITCHER_DIR, MAXPATHLEN);
548 
549     result = stat(fullpath, &sbuf);
550     isDirectory = S_ISDIR(sbuf.st_mode);
551     if ((result == noErr) && (isDirectory)) {
552 #if 0   // Redundant if we already set contents of BOINC Data directory to boinc_master:boinc_master
553         // Set owner and group of switcher directory
554         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_master_group_name);
555         // chown boinc_master:boinc_master "/Library/Application Support/BOINC Data/switcher"
556         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
557         if (err)
558             return err;
559 #endif
560 
561         // chmod u=rx,g=rx,o= "/Library/Application Support/BOINC Data/switcher"
562         // 0550 = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP
563         // Set read and execute permission for user and group, no access for others
564         err = DoSudoPosixSpawn(chmodPath, "u=rx,g=rx,o=", fullpath, NULL, NULL, NULL, NULL);
565         if (err)
566             return err;
567     }       // switcher directory
568 
569     strlcat(fullpath, "/", MAXPATHLEN);
570     strlcat(fullpath, SWITCHER_FILE_NAME, MAXPATHLEN);
571     result = stat(fullpath, &sbuf);
572     isDirectory = S_ISDIR(sbuf.st_mode);
573     if ((result == noErr) && (! isDirectory)) {
574         // Set owner and group of switcher application
575         sprintf(buf1, "root:%s", boinc_master_group_name);
576         // chown root:boinc_master "/Library/Application Support/BOINC Data/switcher/switcher"
577         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
578         if (err)
579             return err;
580 
581         // Set permissions of switcher application
582         // chmod u=s,g=rx,o= "/Library/Application Support/BOINC Data/switcher/switcher"
583         // 04050 = S_ISUID | S_IRGRP | S_IXGRP
584         // Set setuid-on-execution plus read and execute permission for group boinc_master only
585         err = DoSudoPosixSpawn(chmodPath, "u=s,g=rx,o=", fullpath, NULL, NULL, NULL, NULL);
586         if (err)
587             return err;
588     }       // switcher application
589 
590     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
591     strlcat(fullpath, "/", MAXPATHLEN);
592     strlcat(fullpath, SWITCHER_DIR, MAXPATHLEN);
593     strlcat(fullpath, "/", MAXPATHLEN);
594     strlcat(fullpath, SETPROJECTGRP_FILE_NAME, MAXPATHLEN);
595     result = stat(fullpath, &sbuf);
596     isDirectory = S_ISDIR(sbuf.st_mode);
597     if ((result == noErr) && (! isDirectory)) {
598         // Set owner and group of setprojectgrp application
599         sprintf(buf1, "%s:%s", boinc_master_user_name, boinc_project_group_name);
600         // chown boinc_master:boinc_project "/Library/Application Support/BOINC Data/switcher/setprojectgrp"
601         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
602         if (err)
603             return err;
604 
605         // Set permissions of setprojectgrp application
606         // chmod u=rx,g=s,o= "/Library/Application Support/BOINC Data/switcher/setprojectgrp"
607         // 02500 = S_ISGID | S_IRUSR | S_IXUSR
608         // Set setgid-on-execution plus read and execute permission for user only
609         err = DoSudoPosixSpawn(chmodPath, "u=rx,g=s,o=", fullpath, NULL, NULL, NULL, NULL);
610         if (err)
611             return err;
612     }       // setprojectgrp application
613 
614 #ifdef __APPLE__
615 #if 0       // AppStats is deprecated as of version 5.8.15
616     strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
617     strlcat(fullpath, "/", MAXPATHLEN);
618     strlcat(fullpath, SWITCHER_DIR, MAXPATHLEN);
619     strlcat(fullpath, "/", MAXPATHLEN);
620     strlcat(fullpath, APP_STATS_FILE_NAME, MAXPATHLEN);
621     result = stat(fullpath, &sbuf);
622     isDirectory = S_ISDIR(sbuf.st_mode);
623     if ((result == noErr) && (! isDirectory)) {
624         // Set owner and group of AppStats application (must be setuid root)
625         sprintf(buf1, "root:%s", boinc_master_group_name);
626         // chown root:boinc_project "/Library/Application Support/BOINC Data/switcher/AppStats"
627         err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
628         if (err)
629             return err;
630 
631         // Set permissions of AppStats application
632         // chmod u=rsx,g=rx,o= "/Library/Application Support/BOINC Data/switcher/AppStats"
633         // 04550 = S_ISUID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP
634         // Set setuid-on-execution plus read and execute permission for user and group
635         err = DoSudoPosixSpawn(chmodPath, "u=rsx,g=rx,o=", fullpath, NULL, NULL, NULL, NULL);
636         if (err)
637             return err;
638     }       // setprojectgrp application
639 #endif
640 #endif  // __APPLE__
641 
642     return noErr;
643 }
644 
645 
646 // make all *.xml files not world-readable to keep authenticators private
MakeXMLFilesPrivate(char * basepath)647 static OSStatus MakeXMLFilesPrivate(char * basepath) {
648     char            fullpath[MAXPATHLEN];
649     OSStatus        retval = 0;
650     DIR             *dirp;
651     int             len;
652     dirent          *dp;
653 
654     dirp = opendir(basepath);
655     if (dirp == NULL)           // Should never happen
656         return -1;
657 
658     while (true) {
659         dp = readdir(dirp);
660         if (dp == NULL)
661             break;                  // End of list
662 
663         if (dp->d_name[0] == '.')
664             continue;               // Ignore names beginning with '.'
665 
666         len = strlen(dp->d_name);
667         if (len < 5)
668             continue;
669 
670         if (strcmp(dp->d_name+len-4, ".xml"))
671             continue;
672 
673         strlcpy(fullpath, basepath, sizeof(fullpath));
674         strlcat(fullpath, "/", sizeof(fullpath));
675         strlcat(fullpath, dp->d_name, sizeof(fullpath));
676 
677         // chmod u+rw,g+rw,o= "/Library/Application Support/BOINC Data/????.xml"
678         // 0660 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP0
679         // Set read and write permission for user and group, no access for others
680         retval = DoSudoPosixSpawn(chmodPath, "u+rw,g+rw,o=", fullpath, NULL, NULL, NULL, NULL);
681         if (retval)
682             break;
683     }       // End while (true)
684 
685     closedir(dirp);
686 
687     return retval;
688 }
689 
690 
UpdateNestedDirectories(char * basepath)691 static OSStatus UpdateNestedDirectories(char * basepath) {
692     Boolean         isDirectory;
693     char            fullpath[MAXPATHLEN];
694     struct stat     sbuf;
695     OSStatus        retval = 0;
696     DIR             *dirp;
697     dirent          *dp;
698 
699     dirp = opendir(basepath);
700     if (dirp == NULL)           // Should never happen
701         return -1;
702 
703     while (true) {
704         dp = readdir(dirp);
705         if (dp == NULL)
706             break;                  // End of list
707 
708         if (dp->d_name[0] == '.')
709             continue;               // Ignore names beginning with '.'
710 
711         strlcpy(fullpath, basepath, sizeof(fullpath));
712         strlcat(fullpath, "/", sizeof(fullpath));
713         strlcat(fullpath, dp->d_name, sizeof(fullpath));
714 
715         retval = stat(fullpath, &sbuf);
716         if (retval) {
717             if (lstat(fullpath, &sbuf) == 0) {
718                 // A broken symlink in a slot directory may be OK if slot is no longer in use
719                 if (S_ISLNK(sbuf.st_mode)) {
720                     retval = 0;
721                     continue;
722                 }
723             }
724             break;              // Should never happen
725         }
726         isDirectory = S_ISDIR(sbuf.st_mode);
727 
728         if (isDirectory) {
729             // chmod u=rwx,g=rwx,o=rx fullpath
730             // 0775 = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH
731             // Set read, write and execute permission for user & group;  read and execute permission for others
732             retval = DoSudoPosixSpawn(chmodPath, "u=rwx,g=rwx,o=rx", fullpath, NULL, NULL, NULL, NULL);
733             if (retval)
734                 break;
735 
736             retval = UpdateNestedDirectories(fullpath);
737             if (retval)
738                 break;
739         } else {
740             // Since we are changing ownership from boinc_project to boinc_master,
741             // make sure executable-by-group bit is set if executable-by-owner is set
742             if ((sbuf.st_mode & 0110) == 0100) {    // If executable by owner but not by group
743                 retval = DoSudoPosixSpawn(chmodPath, "g+x", fullpath, NULL, NULL, NULL, NULL);
744             }
745         }
746 
747     }       // End while (true)
748 
749     closedir(dirp);
750 
751     return retval;
752 }
753 
754 
CreateUserAndGroup(char * user_name,char * group_name)755 static OSStatus CreateUserAndGroup(char * user_name, char * group_name) {
756     OSStatus        err = noErr;
757     passwd          *pw = NULL;
758     group           *grp = NULL;
759     uid_t           userid = 0;
760     gid_t           groupid = 0;
761     gid_t           usergid = 0;
762     Boolean         userExists = false;
763     Boolean         groupExists = false;
764     short           i;
765     static short    start_id = MIN_ID;
766     char            buf1[80];
767     char            buf2[80];
768     char            buf3[80];
769     char            buf4[80];
770 
771     // OS 10.4 has problems with Accounts pane if we create uid or gid > 501
772     pw = getpwnam(user_name);
773     if (pw) {
774         userid = pw->pw_uid;
775         userExists = true;
776     }
777 
778     grp = getgrnam(group_name);
779     if (grp) {
780         groupid = grp->gr_gid;
781         groupExists = true;
782     }
783 
784     sprintf(buf1, "/groups/%s", group_name);
785     sprintf(buf2, "/users/%s", user_name);
786 
787     if ( userExists && groupExists )
788         goto setRealName;       // User and group already exist
789 
790     // If only user or only group exists, try to use the same ID for the one we create
791     if (userExists) {      // User exists but group does not
792         usergid = pw->pw_gid;
793         if (usergid) {
794             grp = getgrgid(usergid);
795             if (grp == NULL)    // Set the group ID = users existing group if this group ID is available
796                 groupid = usergid;
797         }
798         if (groupid == 0) {
799             grp = getgrgid(userid);
800             if (grp == NULL)    // Set the group ID = user ID if this group ID is available
801                 groupid = userid;
802         }
803     } else {
804         if (groupExists) {      // Group exists but user does not
805            pw = getpwuid(groupid);
806             if (pw == NULL)    // Set the user ID = group ID if this user ID is available
807                 userid = groupid;
808         }
809     }
810 
811     // We need to find an available user ID, group ID, or both.  Find a value that is currently
812     // neither a user ID or a group ID.
813     // If we need both a new user ID and a new group ID, finds a value that can be used for both.
814     if ( (userid == 0) || (groupid == 0) ) {
815         for(i=start_id; ; i++) {
816            if ((uid_t)i != userid) {
817                 pw = getpwuid((uid_t)i);
818                 if (pw)
819                     continue;               // Already exists as a user ID of a different user
820             }
821 
822             if ((gid_t)i != groupid) {
823                 grp = getgrgid((gid_t)i);
824                 if (grp)
825                     continue;               // Already exists as a group ID of a different group
826             }
827 
828             if (! userExists)
829                 userid = (uid_t)i;
830             if (! groupExists)
831                 groupid = (gid_t)i;
832 
833             start_id = i + 1;               // Start with next higher value next time
834 
835             break;                          // Success!
836         }
837     }
838 
839     sprintf(buf3, "%d", groupid);
840     sprintf(buf4, "%d", userid);
841 
842     if (! groupExists) {             // If we need to create group
843         // Something like "dscl . -create /groups/boinc_master"
844         err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf1, NULL, NULL, NULL);
845         if (err)
846             return err;
847 
848         // Something like "dscl . -create /groups/boinc_master gid 33"
849         err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf1, "gid", buf3, NULL);
850         if (err)
851             return err;
852     }           // if (! groupExists)
853 
854     if (! userExists) {             // If we need to create user
855         // Something like "dscl . -create /users/boinc_master"
856         err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf2, NULL, NULL, NULL);
857         if (err)
858             return err;
859 
860         // Something like "dscl . -create /users/boinc_master uid 33"
861         err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf2, "uid", buf4, NULL);
862         if (err)
863             return err;
864 
865         // Prevent a security hole by not allowing a login from this user
866         // Something like "dscl . -create /users/boinc_master shell /usr/bin/false"
867         err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf2, "shell", "/usr/bin/false", NULL);
868         if (err)
869             return err;
870 
871         // Something like "dscl . -create /users/boinc_master home /var/empty"
872         err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf2, "home", "/var/empty", NULL);
873         if (err)
874             return err;
875     }           // if (! userExists)
876 
877     // Always set the user gid if we created either the user or the group or both
878     // Something like "dscl . -create /users/boinc_master gid 33"
879     err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf2, "gid", buf3, NULL);
880     if (err)
881         return err;
882 
883 setRealName:
884     // Always set the RealName field to an empty string
885     // Note: create RealName with empty string fails under OS 10.7, but
886     // creating it with non-empty string and changing to empty string does work.
887     //
888     // Something like "dscl . -create /users/boinc_master RealName tempName"
889     err = DoSudoPosixSpawn(dsclPath, ".", "-create", buf2, "RealName", user_name, NULL);
890     if (err)
891         return err;
892 
893     // Something like 'dscl . -change /users/boinc_master RealName ""'
894     err = DoSudoPosixSpawn(dsclPath, ".", "-change", buf2, "RealName", user_name, "");
895     if (err)
896         return err;
897 
898     err = ResynchDSSystem();
899     if (err != noErr)
900         return err;
901 
902     SleepSeconds(2.0);
903 
904     return noErr;
905 }
906 
907 
AddAdminUserToGroups(char * user_name,bool add_to_boinc_project)908 int AddAdminUserToGroups(char *user_name, bool add_to_boinc_project) {
909 #ifndef _DEBUG
910     char            buf1[80];
911     OSStatus        err = noErr;
912 
913     sprintf(buf1, "/groups/%s", boinc_master_group_name);
914 
915     // "dscl . -merge /groups/boinc_master users user_name"
916     err = DoSudoPosixSpawn(dsclPath, ".", "-merge", buf1, "users", user_name, NULL);
917     if (err)
918         return err;
919 
920     if (add_to_boinc_project)  {
921         sprintf(buf1, "/groups/%s", boinc_project_group_name);
922 
923         // "dscl . -merge /groups/boinc_project users user_name"
924         err = DoSudoPosixSpawn(dsclPath, ".", "-merge", buf1, "users", user_name, NULL);
925         if (err)
926             return err;
927     }
928 
929     err = ResynchDSSystem();
930     if (err != noErr)
931         return err;
932 
933 #endif          // ! _DEBUG
934     return noErr;
935 }
936 
937 
ResynchDSSystem()938 OSStatus ResynchDSSystem() {
939     OSStatus        err = noErr;
940 
941     err = DoSudoPosixSpawn("/usr/bin/dscacheutil", "-flushcache", NULL, NULL, NULL, NULL, NULL);
942     err = DoSudoPosixSpawn("/usr/bin/dsmemberutil", "flushcache", NULL, NULL, NULL, NULL, NULL);
943     return noErr;
944 }
945 
946 
947 #ifdef _DEBUG
948 // GDB can't attach to applications which are running as a diferent user or group so
949 //  it ignores the S_ISUID and S_ISGID permisison bits when launching an application.
950 // To work around this, the _DEBUG version uses the current user and group.
SetFakeMasterNames()951 static OSStatus SetFakeMasterNames() {
952     passwd              *pw;
953     group               *grp;
954     gid_t               boinc_master_gid;
955     uid_t               boinc_master_uid;
956 
957     boinc_master_uid = geteuid();
958     pw = getpwuid(boinc_master_uid);
959     if (pw == NULL)
960         return -1;      // Should never happen
961     strlcpy(boinc_master_user_name, pw->pw_name, sizeof(boinc_master_user_name));
962 
963     boinc_master_gid = getegid();
964     grp = getgrgid(boinc_master_gid);
965     if (grp == NULL)
966         return -1;
967     strlcpy(boinc_master_group_name, grp->gr_name, sizeof(boinc_master_group_name));
968 
969 #ifndef DEBUG_WITH_FAKE_PROJECT_USER_AND_GROUP
970     // For better debugging of SANDBOX permissions logic
971     strlcpy(boinc_project_user_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_user_name));
972     strlcpy(boinc_project_group_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_group_name));
973 #else
974     // For easier debugging of project applications
975     strlcpy(boinc_project_user_name, pw->pw_name, sizeof(boinc_project_user_name));
976     strlcpy(boinc_project_group_name, grp->gr_name, sizeof(boinc_project_group_name));
977 #endif
978 
979     return noErr;
980 }
981 #endif
982 
983 
DoSudoPosixSpawn(const char * pathToTool,char * arg1,char * arg2,char * arg3,char * arg4,char * arg5,char * arg6)984 static OSStatus DoSudoPosixSpawn(const char *pathToTool, char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6) {
985     short               i;
986     char                *args[9];
987     char                toolName[1024];
988     pid_t               thePid = 0;
989     int                 result = 0;
990     int                 status = 0;
991     extern char         **environ;
992 
993     for (i=0; i<5; i++) {       // Retry 5 times if error (is that still necessary?)
994         strlcpy(toolName, pathToTool, sizeof(toolName));
995         args[0] = "/usr/bin/sudo";
996         args[1] = toolName;
997         args[2] = arg1;
998         args[3] = arg2;
999         args[4] = arg3;
1000         args[5] = arg4;
1001         args[6] = arg5;
1002         args[7] = arg6;
1003         args[8] = NULL;
1004 
1005 #if VERBOSE_TEST
1006         print_to_log_file("***********");
1007         for (int i=0; i<8; ++i) {
1008             if (args[i] == NULL) break;
1009             print_to_log_file("argv[%d]=%s", i, args[i]);
1010         }
1011         print_to_log_file("***********\n");
1012 #endif
1013 
1014         errno = 0;
1015 
1016         result = posix_spawnp(&thePid, "/usr/bin/sudo", NULL, NULL, args, environ);
1017 #if VERBOSE_TEST
1018         print_to_log_file("callPosixSpawn: posix_spawnp returned %d: %s", result, strerror(result));
1019 #endif
1020         if (result) {
1021             return result;
1022         }
1023     // CAF    int val =
1024         waitpid(thePid, &status, WUNTRACED);
1025     // CAF        if (val < 0) printf("first waitpid returned %d\n", val);
1026         if (status != 0) {
1027 #if VERBOSE_TEST
1028             print_to_log_file("waitpid() returned status=%d", status);
1029 #endif
1030             result = status;
1031         } else {
1032             if (WIFEXITED(status)) {
1033                 result = WEXITSTATUS(status);
1034                 if (result == 1) {
1035 #if VERBOSE_TEST
1036                     print_to_log_file("WEXITSTATUS(status) returned 1, errno=%d: %s", errno, strerror(errno));
1037 #endif
1038                     result = errno;
1039                 }
1040 #if VERBOSE_TEST
1041                 else if (result) {
1042                     print_to_log_file("WEXITSTATUS(status) returned %d", result);
1043                 }
1044 #endif
1045             }   // end if (WIFEXITED(status)) else
1046         }       // end if waitpid returned 0 sstaus else
1047 
1048 #if 0
1049     if (strcmp(arg2, "-R") == 0)
1050         SleepSeconds(DELAY_SECONDS_R);
1051     else
1052         SleepSeconds(DELAY_SECONDS);
1053 #endif
1054     if (result == 0)
1055         break;
1056 }
1057     if (result != 0)
1058         ShowSecurityError("\"%s %s %s %s %s %s\" returned error %d", pathToTool,
1059                             arg1 ? arg1 : "", arg2 ? arg2 : "", arg3 ? arg3 : "",
1060                             arg4 ? arg4 : "", arg5 ? arg5 : "", result);
1061 
1062    return result;
1063 }
1064 
1065 
1066 
ShowSecurityError(const char * format,...)1067 void ShowSecurityError(const char *format, ...) {
1068     va_list                 args;
1069 
1070 #ifdef __x86_64__
1071     va_start(args, format);
1072     vfprintf(stderr, format, args);
1073     va_end(args);
1074 #else
1075     char                    s[1024];
1076     short                   itemHit;
1077     AlertStdAlertParamRec   alertParams;
1078     ModalFilterUPP          ErrorDlgFilterProcUPP;
1079 
1080     va_start(args, format);
1081     s[0] = vsprintf(s+1, format, args);
1082     va_end(args);
1083 
1084     ErrorDlgFilterProcUPP = NewModalFilterUPP(ErrorDlgFilterProc);
1085 
1086     alertParams.movable = true;
1087     alertParams.helpButton = false;
1088     alertParams.filterProc = ErrorDlgFilterProcUPP;
1089     alertParams.defaultText = "\pOK";
1090     alertParams.cancelText = NULL;
1091     alertParams.otherText = NULL;
1092     alertParams.defaultButton = kAlertStdAlertOKButton;
1093     alertParams.cancelButton = 0;
1094     alertParams.position = kWindowDefaultPosition;
1095 
1096     BringAppToFront();
1097 
1098     StandardAlert (kAlertStopAlert, (StringPtr)s, NULL, &alertParams, &itemHit);
1099 
1100     DisposeModalFilterUPP(ErrorDlgFilterProcUPP);
1101 #endif
1102 }
1103 
1104 
1105 #ifndef __x86_64__
ErrorDlgFilterProc(DialogPtr theDialog,EventRecord * theEvent,short * theItemHit)1106 static pascal Boolean ErrorDlgFilterProc(DialogPtr theDialog, EventRecord *theEvent, short *theItemHit) {
1107     // We need this because this is a command-line application so it does not get normal events
1108     if (GetCurrentEventButtonState()) {
1109         *theItemHit = kStdOkItemIndex;
1110         return true;
1111     }
1112 
1113     return StdFilterProc(theDialog, theEvent, theItemHit);
1114 }
1115 #endif
1116 
1117 
1118 // return time of day (seconds since 1970) as a double
1119 //
dtime(void)1120 static double dtime(void) {
1121     struct timeval tv;
1122     gettimeofday(&tv, 0);
1123     return tv.tv_sec + (tv.tv_usec/1.e6);
1124 }
1125 
1126 // Uses usleep to sleep for full duration even if a signal is received
SleepSeconds(double seconds)1127 static void SleepSeconds(double seconds) {
1128     double end_time = dtime() + seconds - 0.01;
1129     // sleep() and usleep() can be interrupted by SIGALRM,
1130     // so we may need multiple calls
1131     //
1132     while (1) {
1133         if (seconds >= 1) {
1134             sleep((unsigned int) seconds);
1135         } else {
1136             usleep((int)fmod(seconds*1000000, 1000000));
1137         }
1138         seconds = end_time - dtime();
1139         if (seconds <= 0) break;
1140     }
1141 }
1142