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 /*  uninstall.cpp */
19 
20 #define TESTING 0       /* for debugging */
21 #define VERBOSE_TEST 0  /* for debugging callPosixSpawn */
22 
23 
24 #include <Carbon/Carbon.h>
25 
26 #include <grp.h>
27 
28 #include <unistd.h>	// geteuid, seteuid
29 #include <pwd.h>	// passwd, getpwnam
30 #include <dirent.h>
31 #include <sys/param.h>  // for MAXPATHLEN
32 #include <sys/stat.h>   // For stat()
33 #include <sys/time.h>
34 #include <string.h>
35 #include <vector>
36 #include <string>
37 
38 using std::vector;
39 using std::string;
40 
41 
42 // WARNING -- SEARCHFORALLBOINCMANAGERS CODE HAS NOT BEEN TESTED
43 #define SEARCHFORALLBOINCMANAGERS 0
44 
45 #define MAX_LANGUAGES_TO_TRY 5
46 #define MANIPULATE_LOGINITEM_PLIST_FILE 0
47 
48 #include "mac_util.h"
49 #include "translate.h"
50 
51 
52 static OSStatus DoUninstall(void);
53 static OSStatus CleanupAllVisibleUsers(void);
54 static OSStatus DeleteOurBundlesFromDirectory(CFStringRef bundleID, char *extension, char *dirPath);
55 static void DeleteLoginItemOSAScript(char* user, char* appName);
56 static char * PersistentFGets(char *buf, size_t buflen, FILE *f);
57 OSErr GetCurrentScreenSaverSelection(char *moduleName, size_t maxLen);
58 OSErr SetScreenSaverSelection(char *moduleName, char *modulePath, int type);
59 static pid_t FindProcessPID(char* name, pid_t thePID);
60 static int KillOneProcess(char* name);
61 static double dtime(void);
62 static void SleepSeconds(double seconds);
63 static void GetPreferredLanguages();
64 static void LoadPreferredLanguages();
65 static Boolean ShowMessage(Boolean allowCancel, Boolean continueButton, Boolean yesNoButtons, const char *format, ...);
66 int callPosixSpawn(const char *cmd, bool delayForResult=false);
67 void print_to_log_file(const char *format, ...);
68 
69 #if MANIPULATE_LOGINITEM_PLIST_FILE
70 static void DeleteLoginItemFromPListFile(void);
71 int GetCountOfLoginItemsFromPlistFile(void);
72 OSErr GetLoginItemNameAtIndexFromPlistFile(int index, char *name, size_t maxLen);
73 OSErr DeleteLoginItemNameAtIndexFromPlistFile(int index);
74 #endif
75 
76 static char gAppName[256];
77 static char gBrandName[256];
78 static char gCatalogsDir[MAXPATHLEN];
79 static char * gCatalog_Name = (char *)"BOINC-Setup";
80 
81 
82 /* BEGIN TEMPORARY ITEMS TO ALLOW TRANSLATORS TO START WORK */
notused()83 void notused() {
84     ShowMessage(true, false, false, (char *)_("OK"));
85 }
86 /* END TEMPORARY ITEMS TO ALLOW TRANSLATORS TO START WORK */
87 
88 
main(int argc,char * argv[])89 int main(int argc, char *argv[])
90 {
91     char                        pathToSelf[MAXPATHLEN], pathToVBoxUninstallTool[MAXPATHLEN], *p;
92     char                        cmd[MAXPATHLEN+64];
93     Boolean                     cancelled = false;
94     pid_t                       activeAppPID = 0;
95     struct stat                 sbuf;
96     OSStatus                    err = noErr;
97 
98     pathToSelf[0] = '\0';
99     // Get the full path to our executable inside this application's bundle
100     getPathToThisApp(pathToSelf, sizeof(pathToSelf));
101     if (!pathToSelf[0]) {
102         ShowMessage(false, false, false, "Couldn't get path to self.");
103         return err;
104     }
105 
106     strlcpy(pathToVBoxUninstallTool, pathToSelf, sizeof(pathToVBoxUninstallTool));
107     strlcat(pathToVBoxUninstallTool, "/Contents/Resources/VirtualBox_Uninstall.tool", sizeof(pathToVBoxUninstallTool));
108 
109     // To allow for branding, assume name of executable inside bundle is same as name of bundle
110     p = strrchr(pathToSelf, '/');         // Assume name of executable inside bundle is same as name of bundle
111     if (p == NULL)
112         p = pathToSelf - 1;
113     strlcpy(gAppName, p+1, sizeof(gAppName));
114     p = strrchr(gAppName, '.');         // Strip off bundle extension (".app")
115     if (p)
116         *p = '\0';
117 
118     strlcpy(gCatalogsDir, pathToSelf, sizeof(gCatalogsDir));
119     strlcat(gCatalogsDir, "/Contents/Resources/locale/", sizeof(gCatalogsDir));
120 
121     strlcat(pathToSelf, "/Contents/MacOS/", sizeof(pathToSelf));
122     strlcat(pathToSelf, gAppName, sizeof(pathToSelf));
123 
124     p = strchr(gAppName, ' ');
125     p += 1;         // Point to brand name following "Uninstall "
126 
127     strlcpy(gBrandName, p, sizeof(gBrandName));
128 
129     // Determine whether this is the intial launch or the relaunch with privileges
130     if ( (argc == 3) && (strcmp(argv[1], "--privileged") == 0) ) {
131         // Prevent displaying "OSAScript" in menu bar on newer versions of OS X
132         activeAppPID = (pid_t)atol(argv[2]);
133         if (activeAppPID > 0) {
134             BringAppWithPidToFront(activeAppPID);   // Usually Finder
135         }
136         // Give the run loop a chance to handle the BringAppWithPidToFront call
137 //        CFRunLoopRunInMode(kCFRunLoopCommonModes, (CFTimeInterval)0.5, false);
138         // Apparently, usleep() lets run loop run
139         usleep(100000);
140 
141         LoadPreferredLanguages();
142 
143         if (geteuid() != 0) {        // Confirm that we are running as root
144             ShowMessage(false, false, false, (char *)_("Permission error after relaunch"));
145             BOINCTranslationCleanup();
146             return permErr;
147         }
148 
149         ShowMessage(false, true, false, (char *)_("Removal may take several minutes.\nPlease be patient."));
150 
151         err = DoUninstall();
152 
153         BOINCTranslationCleanup();
154         return err;
155     }
156 
157     // This is the initial launch.  Authenticate and relaunch ourselves with privileges.
158 
159     GetPreferredLanguages();    // We must do this before switching to root user
160     LoadPreferredLanguages();
161 
162     // Grid Republic uses generic dialog with Uninstall application's icon
163     cancelled = ! ShowMessage(true, true, false, (char *)_(
164             "Are you sure you want to completely remove %s from your computer?\n\n"
165             "This will remove the executables but will not touch %s data files."), p, p);
166 
167     if (! cancelled) {
168         // Prevent displaying "OSAScript" in menu bar on newer versions of OS X
169         activeAppPID = getActiveAppPid();
170 //        ShowMessage(false, true, false, "active app = %d", activeAppPID);  // for debugging
171 
172         // The "activate" command brings the password dialog to the front and makes it the active window.
173         // "with administrator privileges" launches the helper application as user root.
174         sprintf(cmd, "osascript -e 'activate' -e 'do shell script \"sudo \\\"%s\\\" --privileged %d\" with administrator privileges'", pathToSelf, activeAppPID);
175         err = callPosixSpawn(cmd, true);
176     }
177 
178     if (cancelled || (err != noErr)) {
179         ShowMessage(false, false, false, (char *)_("Canceled: %s has not been touched."), p);
180         BOINCTranslationCleanup();
181         return err;
182     }
183 
184     CFStringRef CFBOINCDataPath, CFUserPrefsPath;
185     char BOINCDataPath[MAXPATHLEN], temp[MAXPATHLEN], PathToPrefs[MAXPATHLEN];
186     Boolean success = false;
187     char * loginName = getlogin();
188 
189     CFURLRef urlref = CFURLCreateWithFileSystemPath(NULL, CFSTR("/Library"),
190                                                     kCFURLPOSIXPathStyle, true);
191     success = CFURLCopyResourcePropertyForKey(urlref, kCFURLLocalizedNameKey,
192                                                 &CFBOINCDataPath, NULL);
193     CFRelease(urlref);
194     if (success) {
195         success = CFStringGetCString(CFBOINCDataPath, temp,
196                     sizeof(temp), kCFStringEncodingUTF8);
197         CFRelease(CFBOINCDataPath);
198     }
199 
200     if (success) {
201         success = false;
202         strlcpy(BOINCDataPath, "/", sizeof(BOINCDataPath));
203         strlcat(BOINCDataPath, temp, sizeof(BOINCDataPath));
204         strlcat(BOINCDataPath, "/", sizeof(BOINCDataPath));
205 
206         urlref = CFURLCreateWithFileSystemPath(NULL, CFSTR("/Library/Application Support"),
207                                                 kCFURLPOSIXPathStyle, true);
208         success = CFURLCopyResourcePropertyForKey(urlref, kCFURLLocalizedNameKey,
209                                                     &CFBOINCDataPath, NULL);
210         CFRelease(urlref);
211     }
212     if (success) {
213         success = CFStringGetCString(CFBOINCDataPath, temp,
214                                         sizeof(temp), kCFStringEncodingUTF8);
215         CFRelease(CFBOINCDataPath);
216     }
217     if (success) {
218         strlcat(BOINCDataPath, temp, sizeof(BOINCDataPath));
219         strlcat(BOINCDataPath, "/BOINC Data", sizeof(BOINCDataPath));
220     } else {
221         strlcpy(BOINCDataPath,
222                 "/Library/Application Support/BOINC Data",
223                 sizeof(BOINCDataPath));
224     }
225 
226     success = false;
227 
228     urlref = CFURLCreateWithFileSystemPath(NULL, CFSTR("/Users"),
229                                                     kCFURLPOSIXPathStyle, true);
230     success = CFURLCopyResourcePropertyForKey(urlref, kCFURLLocalizedNameKey,
231                                                 &CFUserPrefsPath, NULL);
232     CFRelease(urlref);
233     if (success) {
234         success = CFStringGetCString(CFUserPrefsPath, temp, sizeof(temp),
235                                         kCFStringEncodingUTF8);
236         CFRelease(CFUserPrefsPath);
237     }
238     if (success) {
239         success = false;
240         strlcpy(PathToPrefs, "/", sizeof(PathToPrefs));
241         strlcat(PathToPrefs, temp, sizeof(PathToPrefs));
242         strlcat(PathToPrefs, "/[", sizeof(PathToPrefs));
243         strlcat(PathToPrefs, (char *)_("name  of user"), sizeof(PathToPrefs));
244         strlcat(PathToPrefs, "]/", sizeof(PathToPrefs));
245 
246         sprintf(temp, "/Users/%s/Library", loginName);
247         CFUserPrefsPath = CFStringCreateWithCString(kCFAllocatorDefault, temp,
248                                                     kCFStringEncodingUTF8);
249 
250         urlref = CFURLCreateWithFileSystemPath(NULL, CFUserPrefsPath,
251                                                     kCFURLPOSIXPathStyle, true);
252         CFRelease(CFUserPrefsPath);
253         success = CFURLCopyResourcePropertyForKey(urlref, kCFURLLocalizedNameKey,
254                                                 &CFUserPrefsPath, NULL);
255         CFRelease(urlref);
256     }
257     if (success) {
258         success = CFStringGetCString(CFUserPrefsPath, temp, sizeof(temp),
259                                         kCFStringEncodingUTF8);
260         CFRelease(CFUserPrefsPath);
261     }
262     if (success) {
263         success = false;
264         strlcat(PathToPrefs, temp, sizeof(PathToPrefs));
265         strlcat(PathToPrefs, "/", sizeof(PathToPrefs));
266 
267         sprintf(temp, "/Users/%s/Library/Preferences", loginName);
268         CFUserPrefsPath = CFStringCreateWithCString(kCFAllocatorDefault, temp,
269                                                     kCFStringEncodingUTF8);
270         urlref = CFURLCreateWithFileSystemPath(NULL, CFUserPrefsPath,
271                                                     kCFURLPOSIXPathStyle, true);
272         CFRelease(CFUserPrefsPath);
273         success = CFURLCopyResourcePropertyForKey(urlref, kCFURLLocalizedNameKey,
274                                                 &CFUserPrefsPath, NULL);
275         CFRelease(urlref);
276     }
277     if (success) {
278         success = CFStringGetCString(CFUserPrefsPath, temp, sizeof(temp),
279                                         kCFStringEncodingUTF8);
280         CFRelease(CFUserPrefsPath);
281     }
282     if (success) {
283         strlcat(PathToPrefs, temp, sizeof(PathToPrefs));
284         strlcat(PathToPrefs, "/BOINC Manager Preferences", sizeof(PathToPrefs));
285     } else {
286         strlcpy(PathToPrefs,
287                 "/Users/[username]/Library/Preferences/BOINC Manager Preferences",
288                 sizeof(PathToPrefs));
289     }
290 
291     // stat() returns zero on success
292     if (stat(pathToVBoxUninstallTool, &sbuf) == 0) {
293         char cmd[MAXPATHLEN+30];
294 
295         cancelled = ! ShowMessage(true, false, true, (char *)_(
296             "Do you also want to remove VirtualBox from your computer?\n"
297             "(VirtualBox was installed along with BOINC.)"));
298         if (! cancelled) {
299             if (KillOneProcess("VirtualBox")) {
300                 sleep(5);
301             }
302             // List of processes to kill taken from my_processes
303             // array in VirtualBox_Uninstall.tool script:
304             KillOneProcess("VirtualBox-amd64");
305             KillOneProcess("VirtualBox-x86");
306             KillOneProcess("VirtualBoxVM");
307             KillOneProcess("VirtualBoxVM-amd64");
308             KillOneProcess("VirtualBoxVM-x86");
309             KillOneProcess("VBoxManage");
310             KillOneProcess("VBoxManage-amd64");
311             KillOneProcess("VBoxManage-x86");
312             KillOneProcess("VBoxHeadless");
313             KillOneProcess("VBoxHeadless-amd64");
314             KillOneProcess("VBoxHeadless-x86");
315             KillOneProcess("vboxwebsrv");
316             KillOneProcess("vboxwebsrv-amd64");
317             KillOneProcess("vboxwebsrv-x86");
318             KillOneProcess("VBoxXPCOMIPCD");
319             KillOneProcess("VBoxXPCOMIPCD-amd64");
320             KillOneProcess("VBoxXPCOMIPCD-x86");
321             KillOneProcess("VBoxSVC");
322             KillOneProcess("VBoxSVC-amd64");
323             KillOneProcess("VBoxSVC-x86");
324             KillOneProcess("VBoxNetDHCP");
325             KillOneProcess("VBoxNetDHCP-amd64");
326             KillOneProcess("VBoxNetDHCP-x86");
327             sleep(2);
328 
329             snprintf(cmd, sizeof(cmd), "source \"%s\" --unattended", pathToVBoxUninstallTool);
330             callPosixSpawn(cmd);
331         }
332     }
333 
334     ShowMessage(false, false, false, (char *)_("Removal completed.\n\n You may want to remove the following remaining items using the Finder: \n"
335      "the directory \"%s\"\n\nfor each user, the file\n"
336      "\"%s\"."), BOINCDataPath, PathToPrefs);
337 
338 
339     BOINCTranslationCleanup();
340     return err;
341 }
342 
343 
DoUninstall(void)344 static OSStatus DoUninstall(void) {
345     pid_t                   coreClientPID = 0;
346     pid_t                   BOINCManagerPID = 0;
347     char                    cmd[1024];
348     char                    *p;
349     passwd                  *pw;
350     OSStatus                err = noErr;
351 #if SEARCHFORALLBOINCMANAGERS
352     char                    myRmCommand[MAXPATHLEN+10], plistRmCommand[MAXPATHLEN+10];
353     char                    notBoot[] = "/Volumes/";
354     CFStringRef             cfPath;
355     CFURLRef                appURL;
356     int                     pathOffset, i;
357 #endif
358 
359 #if TESTING
360     ShowMessage(false, false, false, "Permission OK after relaunch");
361 #endif
362 
363     //TODO: It would be nice to get the app name from the bundle ID or signature
364     // so we don't have to try all 4 and to allow for future branded versions
365 
366     for (;;) {
367         BOINCManagerPID = FindProcessPID("BOINCManager", 0);
368         if (BOINCManagerPID == 0) break;
369         kill(BOINCManagerPID, SIGTERM);
370         sleep(2);
371     }
372 
373     for (;;) {
374         BOINCManagerPID = FindProcessPID("GridRepublic Desktop", 0);
375         if (BOINCManagerPID == 0) break;
376         kill(BOINCManagerPID, SIGTERM);
377         sleep(2);
378     }
379 
380     for (;;) {
381         BOINCManagerPID = FindProcessPID("Progress Thru Processors Desktop", 0);
382         if (BOINCManagerPID == 0) break;
383         kill(BOINCManagerPID, SIGTERM);
384         sleep(2);
385     }
386 
387     for (;;) {
388         BOINCManagerPID = FindProcessPID("Charity Engine Desktop", 0);
389         if (BOINCManagerPID == 0) break;
390         kill(BOINCManagerPID, SIGTERM);
391         sleep(2);
392     }
393 
394     // Core Client may still be running if it was started without Manager
395     coreClientPID = FindProcessPID("boinc", 0);
396     if (coreClientPID)
397         kill(coreClientPID, SIGTERM);   // boinc catches SIGTERM & exits gracefully
398 
399 #if SEARCHFORALLBOINCMANAGERS
400 // WARNING -- SEARCHFORALLBOINCMANAGERS CODE HAS NOT BEEN TESTED
401 
402     // Phase 1: try to find all our applications using LaunchServices
403     for (i=0; i<100; i++) {
404         strlcpy(myRmCommand, "rm -rf \"", 10);
405         pathOffset = strlen(myRmCommand);
406 
407         err = GetPathToAppFromID('BNC!', CFSTR("edu.berkeley.boinc"),  myRmCommand+pathOffset, MAXPATHLEN);
408         if (err) {
409             break;
410         }
411 
412         strlcat(myRmCommand, "\"", sizeof(myRmCommand));
413 
414 #if TESTING
415         ShowMessage(false, false, false, "manager: %s", myRmCommand);
416 #endif
417 
418         p = strstr(myRmCommand, notBoot);
419 
420         if (p == myRmCommand+pathOffset) {
421 #if TESTING
422             ShowMessage(false, false, false, "Not on boot volume: %s", myRmCommand);
423 #endif
424             break;
425         } else {
426 
427             // First delete just the application's info.plist file and update the
428             // LaunchServices Database; otherwise GetPathToAppFromID might return
429             // this application again after it's been deleted.
430             strlcpy(plistRmCommand, myRmCommand, sizeof(plistRmCommand));
431             strlcat(plistRmCommand, "/Contents/info.plist", sizeof(plistRmCommand));
432 #if TESTING
433         ShowMessage(false, false, false, "Deleting info.plist: %s", plistRmCommand);
434 #endif
435             callPosixSpawn(plistRmCommand);
436             cfPath = CFStringCreateWithCString(NULL, myRmCommand+pathOffset, kCFStringEncodingUTF8);
437             appURL = CFURLCreateWithFileSystemPath(NULL, CFStringRef filePath, kCFURLPOSIXPathStyle, true);
438             if (cfPath) {
439                 CFRelease(cfPath);
440             }
441             if (appURL) {
442                 CFRelease(appURL);
443             }
444 
445             err = LSRegisterURL, true);
446 #if TESTING
447             if (err)
448                 ShowMessage(false, false, false, "LSRegisterFSRef returned error %d", err);
449 #endif
450             callPosixSpawn(myRmCommand);
451         }
452     }
453 #endif  // SEARCHFORALLBOINCMANAGERS
454 
455     // Phase 2: step through default Applications directory searching for our applications
456     err = DeleteOurBundlesFromDirectory(CFSTR("edu.berkeley.boinc"), "app", "/Applications");
457 
458     // Phase 3: step through default Screen Savers directory searching for our screen savers
459     err = DeleteOurBundlesFromDirectory(CFSTR("edu.berkeley.boincsaver"), "saver", "/Library/Screen Savers");
460 
461     // Phase 4: Delete our files and directories at our installer's default locations
462     // Remove everything we've installed, whether BOINC, GridRepublic, Progress Thru Processors or
463     // Charity Engine
464 
465     //TODO: It would be nice to get the app name from the bundle ID or signature
466     // so we don't have to try all 4 and to allow for future branded versions
467 
468     // These first 4 should already have been deleted by the above code, but do them anyway for safety
469     callPosixSpawn ("rm -rf /Applications/BOINCManager.app");
470     callPosixSpawn ("rm -rf \"/Library/Screen Savers/BOINCSaver.saver\"");
471 
472     callPosixSpawn ("rm -rf \"/Applications/GridRepublic Desktop.app\"");
473     callPosixSpawn ("rm -rf \"/Library/Screen Savers/GridRepublic.saver\"");
474 
475     callPosixSpawn ("rm -rf \"/Applications/Progress Thru Processors Desktop.app\"");
476     callPosixSpawn ("rm -rf \"/Library/Screen Savers/Progress Thru Processors.saver\"");
477 
478     callPosixSpawn ("rm -rf \"/Applications/Charity Engine Desktop.app\"");
479     callPosixSpawn ("rm -rf \"/Library/Screen Savers/Charity Engine.saver\"");
480 
481     // Delete any receipt from an older installer (which had
482     // a wrapper application around the installer package.)
483     callPosixSpawn ("rm -rf /Library/Receipts/GridRepublic.pkg");
484     callPosixSpawn ("rm -rf /Library/Receipts/Progress\\ Thru\\ Processors.pkg");
485     callPosixSpawn ("rm -rf /Library/Receipts/Charity\\ Engine.pkg");
486     callPosixSpawn ("rm -rf /Library/Receipts/BOINC.pkg");
487 
488     // Delete any receipt from a newer installer (a bare package.)
489     callPosixSpawn ("rm -rf /Library/Receipts/GridRepublic\\ Installer.pkg");
490     callPosixSpawn ("rm -rf /Library/Receipts/Progress\\ Thru\\ Processors\\ Installer.pkg");
491     callPosixSpawn ("rm -rf /Library/Receipts/Charity\\ Engine\\ Installer.pkg");
492     callPosixSpawn ("rm -rf /Library/Receipts/BOINC\\ Installer.pkg");
493 
494     // Phase 5: Set BOINC Data owner and group to logged in user
495     // We don't customize BOINC Data directory name for branding
496 //    callPosixSpawn ("rm -rf \"/Library/Application Support/BOINC Data\"");
497     p = getlogin();
498     pw = getpwnam(p);
499     sprintf(cmd, "chown -R %d:%d \"/Library/Application Support/BOINC Data\"", pw->pw_uid, pw->pw_gid);
500     callPosixSpawn (cmd);
501     callPosixSpawn("chmod -R u+rw-s,g+r-w-s,o+r-w \"/Library/Application Support/BOINC Data\"");
502     callPosixSpawn("chmod 600 \"/Library/Application Support/BOINC Data/gui_rpc_auth.cfg\"");
503 
504     // Phase 6: step through all users and do user-specific cleanup
505     CleanupAllVisibleUsers();
506 
507     callPosixSpawn ("dscl . -delete /users/boinc_master");
508     callPosixSpawn ("dscl . -delete /groups/boinc_master");
509     callPosixSpawn ("dscl . -delete /users/boinc_project");
510     callPosixSpawn ("dscl . -delete /groups/boinc_project");
511 
512 
513     return 0;
514 }
515 
516 
DeleteOurBundlesFromDirectory(CFStringRef bundleID,char * extension,char * dirPath)517 static OSStatus DeleteOurBundlesFromDirectory(CFStringRef bundleID, char *extension, char *dirPath) {
518     DIR                     *dirp;
519     dirent                  *dp;
520     CFStringRef             urlStringRef = NULL;
521     int                     index;
522     CFStringRef             thisID = NULL;
523     CFBundleRef             thisBundle = NULL;
524     CFURLRef                bundleURLRef = NULL;
525     char                    myRmCommand[MAXPATHLEN+10], *p;
526     int                     pathOffset;
527 
528     dirp = opendir(dirPath);
529     if (dirp == NULL) {      // Should never happen
530         ShowMessage(false, false, false, "Error: opendir(\"%s\") failed", dirPath);
531         return -1;
532     }
533 
534     index = -1;
535     while (true) {
536         index++;
537 
538         dp = readdir(dirp);
539         if (dp == NULL)
540             break;                  // End of list
541 
542         p = strrchr(dp->d_name, '.');
543         if (p == NULL)
544             continue;
545 
546         if (strcmp(p+1, extension))
547             continue;
548 
549         strlcpy(myRmCommand, "rm -rf \"", 10);
550         pathOffset = strlen(myRmCommand);
551         strlcat(myRmCommand, dirPath, sizeof(myRmCommand));
552         strlcat(myRmCommand, "/", sizeof(myRmCommand));
553         strlcat(myRmCommand, dp->d_name, sizeof(myRmCommand));
554         urlStringRef = CFStringCreateWithCString(kCFAllocatorDefault, myRmCommand+pathOffset, CFStringGetSystemEncoding());
555         if (urlStringRef) {
556             bundleURLRef = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, urlStringRef, kCFURLPOSIXPathStyle, false);
557             if (bundleURLRef) {
558                 thisBundle = CFBundleCreate( kCFAllocatorDefault, bundleURLRef );
559                 if (thisBundle) {
560                     thisID = CFBundleGetIdentifier(thisBundle);
561                         if (thisID) {
562                             strlcat(myRmCommand, "\"", sizeof(myRmCommand));
563                             if (CFStringCompare(thisID, bundleID, 0) == kCFCompareEqualTo) {
564 #if TESTING
565                                 ShowMessage(false, false, false, "Bundles: %s", myRmCommand);
566 #endif
567 
568                                callPosixSpawn(myRmCommand);
569                             } else {
570 #if TESTING
571 //                                ShowMessage(false, false, false, "Bundles: Not deleting %s", myRmCommand+pathOffset);
572 #endif
573                             }
574 
575 
576                         } // if (thisID)
577 #if TESTING
578                         else
579                             ShowMessage(false, false, false, "CFBundleGetIdentifier failed for index %d", index);
580 #endif
581                         CFRelease(thisBundle);
582                } //if (thisBundle)
583 #if TESTING
584                         else
585                             ShowMessage(false, false, false, "CFBundleCreate failed for index %d", index);
586 #endif
587                 CFRelease(bundleURLRef);
588             } // if (bundleURLRef)
589 #if TESTING
590         else
591             ShowMessage(false, false, false, "CFURLCreateWithFileSystemPath failed");
592 #endif
593 
594             CFRelease(urlStringRef);
595         } // if (urlStringRef)
596 #if TESTING
597         else
598             ShowMessage(false, false, false, "CFStringCreateWithCString failed");
599 #endif
600     }   // while true
601 
602     closedir(dirp);
603 
604     return noErr;
605 }
606 
607 
608 enum {
609 	kSystemEventsCreator = 'sevs'
610 };
611 
612 CFStringRef kSystemEventsBundleID = CFSTR("com.apple.systemevents");
613 char *systemEventsAppName = "System Events";
614 
615 // Find all visible users and delete their login item to launch BOINC Manager.
616 // Remove each user from groups boinc_master and boinc_project.
617 // For now, don't delete user's BOINC Preferences file.
CleanupAllVisibleUsers(void)618 static OSStatus CleanupAllVisibleUsers(void)
619 {
620     passwd              *pw;
621     vector<string>      human_user_names;
622     vector<uid_t>       human_user_IDs;
623     uid_t               saved_uid, saved_euid;
624     char                human_user_name[256];
625     int                 i;
626     int                 userIndex;
627     int                 flag;
628     char                buf[256];
629     char                s[1024];
630     char                cmd[2048];
631     char                systemEventsPath[1024];
632     pid_t               systemEventsPID;
633     FILE                *f;
634     char                *p;
635     int                 id;
636     OSStatus            err;
637     Boolean             changeSaver;
638 
639     saved_uid = getuid();
640     saved_euid = geteuid();
641 
642     err = noErr;
643     systemEventsPath[0] = '\0';
644 
645     err = GetPathToAppFromID(kSystemEventsCreator, kSystemEventsBundleID,  systemEventsPath, sizeof(systemEventsPath));
646 
647 #if TESTING
648     if (err == noErr) {
649         ShowMessage(false, false, false, "SystemEvents is at %s", systemEventsPath);
650     } else {
651         ShowMessage(false, false, false, "GetPathToAppFromID(kSystemEventsCreator, kSystemEventsBundleID) returned error %d ", (int) err);
652     }
653 #endif
654 
655     // First, find all users on system
656     f = popen("dscl . list /Users UniqueID", "r");
657     if (f) {
658         while (PersistentFGets(buf, sizeof(buf), f)) {
659             p = strrchr(buf, ' ');
660             if (p) {
661                 id = atoi(p+1);
662                 if (id < 501) {
663 #if TESTING
664 //                    printf("skipping user ID %d\n", id);
665 //                    fflush(stdout);
666 #endif
667                     continue;
668                 }
669 
670                 while (p > buf) {
671                     if (*p != ' ') break;
672                     --p;
673                 }
674 
675                 *(p+1) = '\0';
676                 human_user_names.push_back(string(buf));
677                 human_user_IDs.push_back((uid_t)id);
678 #if TESTING
679                 ShowMessage(false, false, false, "user ID %d: %s\n", id, buf);
680 #endif
681                 *(p+1) = ' ';
682             }
683         }
684         pclose(f);
685     }
686 
687     for (userIndex=human_user_names.size(); userIndex>0; --userIndex) {
688         flag = 0;
689         strlcpy(human_user_name, human_user_names[userIndex-1].c_str(), sizeof(human_user_name));
690 
691         // Check whether this user is a login (human) user
692         sprintf(s, "dscl . -read \"/Users/%s\" NFSHomeDirectory", human_user_name);
693         f = popen(s, "r");
694         if (f) {
695             while (PersistentFGets(buf, sizeof(buf), f)) {
696                 p = strrchr(buf, ' ');
697                 if (p) {
698                     if (strstr(p, "/var/empty") != NULL) flag = 1;
699                 }
700             }
701             pclose(f);
702         }
703 
704         sprintf(s, "dscl . -read \"/Users/%s\" UserShell", human_user_name);
705         f = popen(s, "r");
706         if (f) {
707             while (PersistentFGets(buf, sizeof(buf), f)) {
708                 p = strrchr(buf, ' ');
709                 if (p) {
710                     if (strstr(p, "/usr/bin/false") != NULL) flag |= 2;
711                 }
712             }
713             pclose(f);
714         }
715 
716         // Skip all non-human (non-login) users
717         if (flag == 3) { // if (Home Directory == "/var/empty") && (UserShell == "/usr/bin/false")
718 #if TESTING
719             ShowMessage(false, false, false, "Flag=3: skipping user ID %d: %s", human_user_IDs[userIndex-1], buf);
720 #endif
721             continue;
722         }
723 
724         pw = getpwnam(human_user_name);
725         if (pw == NULL)
726             continue;
727 
728 #if TESTING
729         ShowMessage(false, false, false, "Deleting login item for user %s: Posix name=%s, Full name=%s, UID=%d",
730                 human_user_name, pw->pw_name, pw->pw_gecos, pw->pw_uid);
731 #endif
732 
733         // Remove user from groups boinc_master and boinc_project
734         sprintf(s, "dscl . -delete /groups/boinc_master users \"%s\"", human_user_name);
735         callPosixSpawn (s);
736 
737         sprintf(s, "dscl . -delete /groups/boinc_project users \"%s\"", human_user_name);
738         callPosixSpawn (s);
739 
740 #if TESTING
741 //    ShowMessage(false, false, false, "Before seteuid(%d) for user %s, euid = %d", pw->pw_uid, human_user_name, geteuid());
742 #endif
743         setuid(0);
744         // Delete our login item(s) for this user
745 #if MANIPULATE_LOGINITEM_PLIST_FILE
746        if (compareOSVersionTo(10, 8) >= 0) {
747             seteuid(pw->pw_uid);    // Temporarily set effective uid to this user
748             DeleteLoginItemFromPListFile();
749             seteuid(saved_euid);    // Set effective uid back to privileged user
750         } else {            // OS 10.7.x
751 #endif
752             // We must leave effective user ID as privileged user (root)
753             // because the target user may not be in the sudoers file.
754 
755             // We must launch the System Events application for the target user
756 
757 #if TESTING
758             ShowMessage(false, false, false, "Telling System Events to quit (before DeleteLoginItemOSAScript)");
759 #endif
760             // Find SystemEvents process.  If found, quit it in case
761             // it is running under a different user.
762             systemEventsPID = FindProcessPID(systemEventsAppName, 0);
763             if (systemEventsPID != 0) {
764                 err = kill(systemEventsPID, SIGKILL);
765             }
766 #if TESTING
767             if (err != noErr) {
768                 ShowMessage(false, false, false, "kill(systemEventsPID, SIGKILL) returned error %d ", (int) err);
769             }
770 #endif
771             // Wait for the process to be gone
772             for (i=0; i<50; ++i) {      // 5 seconds max delay
773                 SleepSeconds(0.1);      // 1/10 second
774                 systemEventsPID = FindProcessPID(systemEventsAppName, 0);
775                 if (systemEventsPID == 0) break;
776             }
777 #if TESTING
778             if (i >= 50) {
779                 ShowMessage(false, false, false, "Failed to make System Events quit");
780             }
781 #endif
782             sleep(2);
783 
784             if (systemEventsPath[0] != '\0') {
785 #if TESTING
786                 ShowMessage(false, false, false, "Launching SystemEvents for user %s", pw->pw_name);
787 #endif
788                 sprintf(cmd, "sudo -u \"%s\" -b \"%s/Contents/MacOS/System Events\"", pw->pw_name, systemEventsPath);
789                 err = callPosixSpawn(cmd);
790                 if (err == noErr) {
791                     // Wait for the process to start
792                     for (i=0; i<50; ++i) {      // 5 seconds max delay
793                         SleepSeconds(0.1);      // 1/10 second
794                         systemEventsPID = FindProcessPID(systemEventsAppName, 0);
795                         if (systemEventsPID != 0) break;
796                     }
797 #if TESTING
798                     if (i >= 50) {
799                         ShowMessage(false, false, false, "Failed to launch System Events for user %s", pw->pw_name);
800                     }
801 #endif
802                     sleep(2);
803 
804                     DeleteLoginItemOSAScript(pw->pw_name, "BOINCManager");
805                     DeleteLoginItemOSAScript(pw->pw_name, "GridRepublic Desktop");
806                     DeleteLoginItemOSAScript(pw->pw_name, "Progress Thru Processors Desktop");
807                     DeleteLoginItemOSAScript(pw->pw_name, "Charity Engine Desktop");
808 
809 #if TESTING
810                 } else {
811                     ShowMessage(false, false, false, "[2] Command: %s returned error %d", cmd, (int) err);
812 #endif
813                 }
814             }
815 #if MANIPULATE_LOGINITEM_PLIST_FILE
816         }
817 #endif
818 
819         // We don't delete the user's BOINC Manager preferences
820 //        sprintf(s, "rm -f \"/Users/%s/Library/Preferences/BOINC Manager Preferences\"", human_user_name);
821 //        callPosixSpawn (s);
822 
823         // Delete per-user BOINC Manager and screensaver files
824         sprintf(s, "rm -fR \"/Users/%s/Library/Application Support/BOINC\"", human_user_name);
825         callPosixSpawn (s);
826 
827         //  Set screensaver to "Computer Name" default screensaver only
828         //  if it was BOINC, GridRepublic, Progress Thru Processors or Charity Engine.
829         changeSaver = false;
830         seteuid(pw->pw_uid);    // Temporarily set effective uid to this user
831         if (compareOSVersionTo(10, 6) < 0) {
832             f = popen("defaults -currentHost read com.apple.screensaver moduleName", "r");
833             if (f) {
834                 while (PersistentFGets(s, sizeof(s), f)) {
835                     if (strstr(s, "BOINCSaver")) {
836                         changeSaver = true;
837                         break;
838                     }
839                     if (strstr(s, "GridRepublic")) {
840                         changeSaver = true;
841                         break;
842                     }
843                     if (strstr(s, "Progress Thru Processors")) {
844                         changeSaver = true;
845                         break;
846                     }
847                     if (strstr(s, "Charity Engine")) {
848                         changeSaver = true;
849                         break;
850                     }
851                 }
852                 pclose(f);
853             }
854         } else {
855             err = GetCurrentScreenSaverSelection(s, sizeof(s) -1);
856             if (err == noErr) {
857                 if (strstr(s, "BOINCSaver")) {
858                     changeSaver = true;
859                 }
860                 if (strstr(s, "GridRepublic")) {
861                     changeSaver = true;
862                 }
863                 if (strstr(s, "Progress Thru Processors")) {
864                     changeSaver = true;
865                 }
866                 if (strstr(s, "Charity Engine")) {
867                     changeSaver = true;
868                 }
869             }
870         }
871 
872         if (changeSaver) {
873             if (compareOSVersionTo(10, 6) < 0) {
874                 callPosixSpawn ("defaults -currentHost write com.apple.screensaver moduleName \"Computer Name\"");
875                 callPosixSpawn ("defaults -currentHost write com.apple.screensaver modulePath \"/System/Library/Frameworks/ScreenSaver.framework/Versions/A/Resources/Computer Name.saver\"");
876             } else {
877                 err = SetScreenSaverSelection("Computer Name",
878                     "/System/Library/Frameworks/ScreenSaver.framework/Versions/A/Resources/Computer Name.saver", 0);
879             }
880         }
881 
882         seteuid(saved_euid);    // Set effective uid back to privileged user
883 
884 #if TESTING
885 //    ShowMessage(false, false, false, "After seteuid(%d) for user %s, euid = %d, saved_uid = %d", pw->pw_uid, human_user_name, geteuid(), saved_uid);
886 #endif
887     }       // End userIndex loop
888 
889     sleep(1);
890 
891 #if TESTING
892     ShowMessage(false, false, false, "Telling System Events to quit (at end)");
893 #endif
894     systemEventsPID = FindProcessPID(systemEventsAppName, 0);
895     if (systemEventsPID != 0) {
896         err = kill(systemEventsPID, SIGKILL);
897     }
898 #if TESTING
899     if (err != noErr) {
900         ShowMessage(false, false, false, "kill(systemEventsPID, SIGKILL) returned error %d ", (int) err);
901     }
902 #endif
903 
904     return noErr;
905 }
906 
907 
908 // Used for OS <= 10.7
DeleteLoginItemOSAScript(char * user,char * appName)909 static void DeleteLoginItemOSAScript(char* user, char* appName)
910 {
911     char                    cmd[2048];
912     OSErr                   err;
913 
914     sprintf(cmd, "sudo -u \"%s\" osascript -e 'tell application \"System Events\"' -e 'delete (every login item whose name contains \"%s\")' -e 'end tell'", user, appName);
915     err = callPosixSpawn(cmd);
916 #if TESTING
917     if (err) {
918         ShowMessage(false, false, false, "Command: %s returned error %d", cmd, err);
919     }
920 #endif
921 }
922 
923 
924 #if MANIPULATE_LOGINITEM_PLIST_FILE
925 // Used for OS >= 10.8
DeleteLoginItemFromPListFile(void)926 static void DeleteLoginItemFromPListFile(void)
927 {
928     Boolean                 success;
929     int                     numberOfLoginItems, counter;
930     char                    theName[256], *q;
931 
932     success = false;
933 
934     numberOfLoginItems = GetCountOfLoginItemsFromPlistFile();
935 
936     // Search existing login items in reverse order, deleting ours
937     for (counter = numberOfLoginItems ; counter > 0 ; counter--)
938     {
939         GetLoginItemNameAtIndexFromPlistFile(counter-1, theName, sizeof(theName));
940         q = theName;
941         while (*q)
942         {
943             // It is OK to modify the returned string because we "own" it
944             *q = toupper(*q);	// Make it case-insensitive
945             q++;
946         }
947 
948         if (strstr(theName, "BOINCMANAGER"))
949             success = DeleteLoginItemNameAtIndexFromPlistFile(counter-1);
950         if (strstr(theName, "GRIDREPUBLIC DESKTOP"))
951             success = DeleteLoginItemNameAtIndexFromPlistFile(counter-1);
952         if (strstr(theName, "PROGRESS THRU PROCESSORS DESKTOP"))
953             success = DeleteLoginItemNameAtIndexFromPlistFile(counter-1);
954         if (strstr(theName, "CHARITY ENGINE DESKTOP"))
955             success = DeleteLoginItemNameAtIndexFromPlistFile(counter-1);
956     }
957 }
958 #endif // MANIPULATE_LOGINITEM_PLIST_FILE
959 
960 
PersistentFGets(char * buf,size_t buflen,FILE * f)961 static char * PersistentFGets(char *buf, size_t buflen, FILE *f) {
962     char *p = buf;
963     size_t len = buflen;
964     size_t datalen = 0;
965 
966     *buf = '\0';
967     while (datalen < (buflen - 1)) {
968         fgets(p, len, f);
969         if (feof(f)) break;
970         if (ferror(f) && (errno != EINTR)) break;
971         if (strchr(buf, '\n')) break;
972         datalen = strlen(buf);
973         p = buf + datalen;
974         len -= datalen;
975     }
976     return (buf[0] ? buf : NULL);
977 }
978 
979 
GetCurrentScreenSaverSelection(char * moduleName,size_t maxLen)980 OSErr GetCurrentScreenSaverSelection(char *moduleName, size_t maxLen) {
981     OSErr err = noErr;
982     CFStringRef nameKey = CFStringCreateWithCString(NULL,"moduleName",kCFStringEncodingASCII);
983     CFStringRef moduleNameAsCFString;
984     CFDictionaryRef theData;
985 
986     theData = (CFDictionaryRef)CFPreferencesCopyValue(CFSTR("moduleDict"),
987                 CFSTR("com.apple.screensaver"),
988                 kCFPreferencesCurrentUser,
989                 kCFPreferencesCurrentHost
990                 );
991     if (theData == NULL) {
992         CFRelease(nameKey);
993         return (-1);
994     }
995 
996     if (CFDictionaryContainsKey(theData, nameKey)  == false)
997 	{
998         moduleName[0] = 0;
999         CFRelease(nameKey);
1000         CFRelease(theData);
1001 	    return(-1);
1002 	}
1003 
1004     moduleNameAsCFString = CFStringCreateCopy(NULL, (CFStringRef)CFDictionaryGetValue(theData, nameKey));
1005     CFStringGetCString(moduleNameAsCFString, moduleName, maxLen, kCFStringEncodingASCII);
1006 
1007     CFRelease(nameKey);
1008     CFRelease(theData);
1009     CFRelease(moduleNameAsCFString);
1010     return err;
1011 }
1012 
1013 
SetScreenSaverSelection(char * moduleName,char * modulePath,int type)1014 OSErr SetScreenSaverSelection(char *moduleName, char *modulePath, int type) {
1015     OSErr err = noErr;
1016     CFStringRef preferenceName = CFSTR("com.apple.screensaver");
1017     CFStringRef mainKeyName = CFSTR("moduleDict");
1018     CFDictionaryRef emptyData;
1019     CFMutableDictionaryRef newData;
1020     Boolean success;
1021 
1022     CFStringRef nameKey = CFStringCreateWithCString(NULL, "moduleName", kCFStringEncodingASCII);
1023     CFStringRef nameValue = CFStringCreateWithCString(NULL, moduleName, kCFStringEncodingASCII);
1024 
1025     CFStringRef pathKey = CFStringCreateWithCString(NULL, "path", kCFStringEncodingASCII);
1026     CFStringRef pathValue = CFStringCreateWithCString(NULL, modulePath, kCFStringEncodingASCII);
1027 
1028     CFStringRef typeKey = CFStringCreateWithCString(NULL, "type", kCFStringEncodingASCII);
1029     CFNumberRef typeValue = CFNumberCreate(NULL, kCFNumberIntType, &type);
1030 
1031     emptyData = CFDictionaryCreate(NULL, NULL, NULL, 0, NULL, NULL);
1032     if (emptyData == NULL) {
1033         CFRelease(nameKey);
1034         CFRelease(nameValue);
1035         CFRelease(pathKey);
1036         CFRelease(pathValue);
1037         CFRelease(typeKey);
1038         CFRelease(typeValue);
1039         return(-1);
1040     }
1041 
1042     newData = CFDictionaryCreateMutableCopy(NULL,0, emptyData);
1043 
1044     if (newData == NULL)
1045 	{
1046         CFRelease(nameKey);
1047         CFRelease(nameValue);
1048         CFRelease(pathKey);
1049         CFRelease(pathValue);
1050         CFRelease(typeKey);
1051         CFRelease(typeValue);
1052         CFRelease(emptyData);
1053         return(-1);
1054     }
1055 
1056     CFDictionaryAddValue(newData, nameKey, nameValue);
1057     CFDictionaryAddValue(newData, pathKey, pathValue);
1058     CFDictionaryAddValue(newData, typeKey, typeValue);
1059 
1060     CFPreferencesSetValue(mainKeyName, newData, preferenceName, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
1061     success = CFPreferencesSynchronize(preferenceName, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
1062 
1063     if (!success) {
1064         err = -1;
1065     }
1066 
1067     CFRelease(nameKey);
1068     CFRelease(nameValue);
1069     CFRelease(pathKey);
1070     CFRelease(pathValue);
1071     CFRelease(typeKey);
1072     CFRelease(typeValue);
1073     CFRelease(emptyData);
1074     CFRelease(newData);
1075 
1076     return err;
1077 }
1078 
1079 
1080 #if MANIPULATE_LOGINITEM_PLIST_FILE
1081 
GetCountOfLoginItemsFromPlistFile()1082 int GetCountOfLoginItemsFromPlistFile() {
1083     CFArrayRef	arrayOfLoginItemsFixed;
1084     int valueToReturn = -1;
1085     CFStringRef loginItemArrayKeyName = CFSTR("CustomListItems");
1086     CFDictionaryRef topLevelDict;
1087 
1088     topLevelDict = (CFDictionaryRef)CFPreferencesCopyValue(CFSTR("SessionItems"), CFSTR("com.apple.loginitems"), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
1089     if (topLevelDict == NULL) {
1090         return (0);
1091     }
1092 
1093     if (CFDictionaryContainsKey(topLevelDict, loginItemArrayKeyName)  == false)
1094 	{
1095         CFRelease(topLevelDict);
1096 	    return(0);
1097 	}
1098 
1099     arrayOfLoginItemsFixed = (CFArrayRef)CFDictionaryGetValue(topLevelDict, loginItemArrayKeyName);
1100     if( arrayOfLoginItemsFixed == NULL)
1101 	{
1102         CFRelease(topLevelDict);
1103 	    return(0);
1104 	}
1105 
1106     valueToReturn = (int) CFArrayGetCount(arrayOfLoginItemsFixed);
1107 
1108     CFRelease(topLevelDict);
1109     return(valueToReturn);
1110 }
1111 
1112 
1113 // Returns empty string on failure
GetLoginItemNameAtIndexFromPlistFile(int index,char * name,size_t maxLen)1114 OSErr GetLoginItemNameAtIndexFromPlistFile(int index, char *name, size_t maxLen) {
1115     CFArrayRef	arrayOfLoginItemsFixed = NULL;
1116     CFStringRef loginItemArrayKeyName = CFSTR("CustomListItems");
1117     CFDictionaryRef topLevelDict = NULL;
1118     CFDictionaryRef loginItemDict = NULL;
1119     CFStringRef nameKey = CFSTR("Name");
1120     CFStringRef nameAsCFString = NULL;
1121 
1122     *name = 0;  // Prepare for failure
1123 
1124     topLevelDict = (CFDictionaryRef)CFPreferencesCopyValue(CFSTR("SessionItems"), CFSTR("com.apple.loginitems"), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
1125     if (topLevelDict == NULL) {
1126         return (-1);
1127     }
1128 
1129     if (CFDictionaryContainsKey(topLevelDict, loginItemArrayKeyName)  == false)
1130 	{
1131         CFRelease(topLevelDict);
1132 	    return(-1);
1133 	}
1134 
1135     arrayOfLoginItemsFixed = (CFArrayRef) CFDictionaryGetValue(topLevelDict, loginItemArrayKeyName);
1136     if( arrayOfLoginItemsFixed == NULL)
1137 	{
1138         CFRelease(topLevelDict);
1139 	    return(-1);
1140 	}
1141 
1142     loginItemDict = (CFDictionaryRef) CFArrayGetValueAtIndex(arrayOfLoginItemsFixed, (CFIndex)index);
1143     if (loginItemDict == NULL)
1144     {
1145         CFRelease(topLevelDict);
1146         return(-1);
1147     }
1148 
1149 	if (CFDictionaryContainsKey(loginItemDict, nameKey) == false)
1150 	{
1151         CFRelease(topLevelDict);
1152         return(-1);
1153 	}
1154 
1155     nameAsCFString = CFStringCreateCopy(NULL, (CFStringRef)CFDictionaryGetValue(loginItemDict, nameKey));
1156     CFStringGetCString(nameAsCFString, name, maxLen, kCFStringEncodingASCII);
1157 
1158     CFRelease(topLevelDict);
1159     CFRelease(nameAsCFString);
1160     return(noErr);
1161 }
1162 
1163 
DeleteLoginItemNameAtIndexFromPlistFile(int index)1164 OSErr DeleteLoginItemNameAtIndexFromPlistFile(int index){
1165     CFArrayRef	arrayOfLoginItemsFixed = NULL;
1166     CFMutableArrayRef arrayOfLoginItemsModifiable;
1167     CFStringRef topLevelKeyName = CFSTR("SessionItems");
1168     CFStringRef preferenceName = CFSTR("com.apple.loginitems");
1169     CFStringRef loginItemArrayKeyName = CFSTR("CustomListItems");
1170     CFDictionaryRef oldTopLevelDict = NULL;
1171     CFMutableDictionaryRef newTopLevelDict = NULL;
1172     Boolean success;
1173     OSErr err = noErr;
1174 
1175     oldTopLevelDict = (CFDictionaryRef)CFPreferencesCopyValue(topLevelKeyName, preferenceName, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
1176     if (oldTopLevelDict == NULL) {
1177         return (-1);
1178     }
1179 
1180     if (CFDictionaryContainsKey(oldTopLevelDict, loginItemArrayKeyName)  == false)
1181 	{
1182         CFRelease(oldTopLevelDict);
1183 	    return(-1);
1184 	}
1185 
1186     arrayOfLoginItemsFixed = (CFArrayRef) CFDictionaryGetValue(oldTopLevelDict, loginItemArrayKeyName);
1187     if( arrayOfLoginItemsFixed == NULL)
1188 	{
1189         CFRelease(oldTopLevelDict);
1190 	    return(-1);
1191 	}
1192 
1193     arrayOfLoginItemsModifiable = CFArrayCreateMutableCopy(NULL, 0, arrayOfLoginItemsFixed);
1194     if( arrayOfLoginItemsModifiable == NULL)
1195 	{
1196         CFRelease(oldTopLevelDict);
1197 	    return(-1);
1198 	}
1199 
1200     CFArrayRemoveValueAtIndex(arrayOfLoginItemsModifiable, (CFIndex) index);
1201 
1202     newTopLevelDict = CFDictionaryCreateMutableCopy(NULL, 0, oldTopLevelDict);
1203     CFDictionaryReplaceValue(newTopLevelDict, loginItemArrayKeyName, arrayOfLoginItemsModifiable);
1204 
1205     CFPreferencesSetValue(topLevelKeyName, newTopLevelDict, preferenceName, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
1206     success = CFPreferencesSynchronize(preferenceName, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
1207 
1208     if (!success) {
1209         err = -1;
1210     }
1211 
1212     CFRelease(oldTopLevelDict);
1213     CFRelease(newTopLevelDict);
1214 
1215     return(err);
1216 
1217 }
1218 #endif // MANIPULATE_LOGINITEM_PLIST_FILE
1219 
1220 
FindProcessPID(char * name,pid_t thePID)1221 static pid_t FindProcessPID(char* name, pid_t thePID)
1222 {
1223     FILE *f;
1224     char buf[1024];
1225     size_t n = 0;
1226     pid_t aPID;
1227 
1228     if (name != NULL)     // Search ny name
1229         n = strlen(name);
1230 
1231     f = popen("ps -a -x -c -o command,pid", "r");
1232     if (f == NULL)
1233         return 0;
1234 
1235     while (PersistentFGets(buf, sizeof(buf), f))
1236     {
1237         if (name != NULL) {     // Search by name
1238             if (strncmp(buf, name, n) == 0)
1239             {
1240                 aPID = atol(buf+16);
1241                 pclose(f);
1242                 return aPID;
1243             }
1244         } else {      // Search by PID
1245             aPID = atol(buf+16);
1246             if (aPID == thePID) {
1247                 pclose(f);
1248                 return aPID;
1249             }
1250         }
1251     }
1252     pclose(f);
1253     return 0;
1254 }
1255 
1256 
1257 // Kill a process if it exists
KillOneProcess(char * name)1258 static int KillOneProcess(char* name) {
1259     pid_t   thePid;
1260     char cmd[MAXPATHLEN+10];
1261 
1262     thePid = FindProcessPID(name, 0);
1263     if (thePid <= 0) return 0;   // No such process
1264 
1265     kill(thePid, SIGQUIT);
1266     for (int i=0; i<20; ++i) {
1267         SleepSeconds(0.1);
1268         if (!FindProcessPID(NULL, thePid)) {
1269             return 0;
1270         }
1271     }
1272     kill(thePid, SIGKILL);
1273     for (int i=0; i<20; ++i) {
1274         SleepSeconds(0.1);
1275         if (!FindProcessPID(NULL, thePid)) {
1276             return 0;
1277         }
1278     }
1279     sprintf(cmd, "killall %s", name);
1280     return callPosixSpawn(cmd);
1281 }
1282 
1283 
1284 // return time of day (seconds since 1970) as a double
1285 //
dtime(void)1286 static double dtime(void) {
1287     struct timeval tv;
1288     gettimeofday(&tv, 0);
1289     return tv.tv_sec + (tv.tv_usec/1.e6);
1290 }
1291 
1292 // Uses usleep to sleep for full duration even if a signal is received
SleepSeconds(double seconds)1293 static void SleepSeconds(double seconds) {
1294     double end_time = dtime() + seconds - 0.01;
1295     // sleep() and usleep() can be interrupted by SIGALRM,
1296     // so we may need multiple calls
1297     //
1298     while (1) {
1299         if (seconds >= 1) {
1300             sleep((unsigned int) seconds);
1301         } else {
1302             usleep((int)fmod(seconds*1000000, 1000000));
1303         }
1304         seconds = end_time - dtime();
1305         if (seconds <= 0) break;
1306     }
1307 }
1308 
1309 
1310 // Because language preferences are set on a per-user basis, we
1311 // must get the preferred languages while set to the current
1312 // user, before we switch to root in our second pass.
1313 // So we get the preferred languages here and write them to a
1314 // temporary file to be retrieved by our second pass.
1315 // We must do it this way because, for unknown reasons, the
1316 // CFBundleCopyLocalizationsForPreferences() API does not work
1317 // correctly if we seteuid and setuid to the logged in user
1318 // after running as root.
1319 
GetPreferredLanguages()1320 static void GetPreferredLanguages() {
1321     DIR *dirp;
1322     struct dirent *dp;
1323     char searchPath[MAXPATHLEN];
1324     struct stat sbuf;
1325     CFMutableArrayRef supportedLanguages;
1326     CFStringRef aLanguage;
1327     char shortLanguage[32];
1328     CFArrayRef preferredLanguages;
1329     int i, j, k;
1330     char * language;
1331     char *uscore;
1332     FILE *f;
1333     char loginName[256];
1334     char tempFileName[MAXPATHLEN];
1335 
1336     // Create an array of all our supported languages
1337     supportedLanguages = CFArrayCreateMutable(kCFAllocatorDefault, 100, &kCFTypeArrayCallBacks);
1338 
1339     aLanguage = CFStringCreateWithCString(NULL, "en", kCFStringEncodingMacRoman);
1340     CFArrayAppendValue(supportedLanguages, aLanguage);
1341     aLanguage = NULL;
1342 
1343     dirp = opendir(gCatalogsDir);
1344     if (!dirp) goto bail;
1345     while (true) {
1346         dp = readdir(dirp);
1347         if (dp == NULL)
1348             break;                  // End of list
1349 
1350         if (dp->d_name[0] == '.')
1351             continue;               // Ignore names beginning with '.'
1352 
1353         strlcpy(searchPath, gCatalogsDir, sizeof(searchPath));
1354         strlcat(searchPath, dp->d_name, sizeof(searchPath));
1355         strlcat(searchPath, "/", sizeof(searchPath));
1356         strlcat(searchPath, gCatalog_Name, sizeof(searchPath));
1357         strlcat(searchPath, ".mo", sizeof(searchPath));
1358         // stat() returns zero on success
1359         if (stat(searchPath, &sbuf) != 0) continue;
1360 //        printf("Adding %s to supportedLanguages array\n", dp->d_name);
1361         aLanguage = CFStringCreateWithCString(NULL, dp->d_name, kCFStringEncodingMacRoman);
1362         CFArrayAppendValue(supportedLanguages, aLanguage);
1363         aLanguage = NULL;
1364 
1365         // If it has a region code ("it_IT") also try without region code ("it")
1366         // TODO: Find a more general solution
1367         strlcpy(shortLanguage, dp->d_name, sizeof(shortLanguage));
1368         uscore = strchr(shortLanguage, '_');
1369         if (uscore) {
1370             *uscore = '\0';
1371             aLanguage = CFStringCreateWithCString(NULL, shortLanguage, kCFStringEncodingMacRoman);
1372             CFArrayAppendValue(supportedLanguages, aLanguage);
1373             aLanguage = NULL;
1374         }
1375     }
1376 
1377     closedir(dirp);
1378 
1379     // Write a temp file to tell our PostInstall.app our preferred languages
1380     strncpy(loginName, getenv("USER"), sizeof(loginName)-1);
1381     snprintf(tempFileName, sizeof(tempFileName), "/tmp/UninstallBOINC-%s", loginName);
1382     mkdir(tempFileName, 0777);
1383     chmod(tempFileName, 0777);  // Needed because mkdir sets permissions restricted by umask (022)
1384 
1385     snprintf(tempFileName, sizeof(tempFileName), "/tmp/UninstallBOINC-%s/BOINC_preferred_languages", loginName);
1386     f = fopen(tempFileName, "w");
1387 
1388     for (i=0; i<MAX_LANGUAGES_TO_TRY; ++i) {
1389 
1390         preferredLanguages = CFBundleCopyLocalizationsForPreferences(supportedLanguages, NULL );
1391 
1392 #if 0   // For testing
1393         int c = CFArrayGetCount(preferredLanguages);
1394         for (k=0; k<c; ++k) {
1395         CFStringRef s = (CFStringRef)CFArrayGetValueAtIndex(preferredLanguages, k);
1396             printf("Preferred language %u is %s\n", k, CFStringGetCStringPtr(s, kCFStringEncodingMacRoman));
1397         }
1398 
1399 #endif
1400 
1401         for (j=0; j<CFArrayGetCount(preferredLanguages); ++j) {
1402             aLanguage = (CFStringRef)CFArrayGetValueAtIndex(preferredLanguages, j);
1403             language = (char *)CFStringGetCStringPtr(aLanguage, kCFStringEncodingMacRoman);
1404             if (language == NULL) {
1405                 if (CFStringGetCString(aLanguage, shortLanguage, sizeof(shortLanguage), kCFStringEncodingMacRoman)) {
1406                     language = shortLanguage;
1407                 }
1408             }
1409             if (f) {
1410                 fprintf(f, "%s\n", language);
1411             }
1412 
1413             // Remove all copies of this language from our list of supported languages
1414             // so we can get the next preferred language in order of priority
1415             for (k=CFArrayGetCount(supportedLanguages)-1; k>=0; --k) {
1416                 if (CFStringCompare(aLanguage, (CFStringRef)CFArrayGetValueAtIndex(supportedLanguages, k), 0) == kCFCompareEqualTo) {
1417                     CFArrayRemoveValueAtIndex(supportedLanguages, k);
1418                 }
1419             }
1420 
1421             // Since the original strings are English, no
1422             // further translation is needed for language en.
1423             if (!strcmp(language, "en")) {
1424                 fclose(f);
1425                 CFRelease(preferredLanguages);
1426                 preferredLanguages = NULL;
1427                 CFArrayRemoveAllValues(supportedLanguages);
1428                 CFRelease(supportedLanguages);
1429                 supportedLanguages = NULL;
1430                 return;
1431             }
1432         }
1433 
1434         CFRelease(preferredLanguages);
1435         preferredLanguages = NULL;
1436 
1437     }
1438     fclose(f);
1439 
1440 bail:
1441     CFArrayRemoveAllValues(supportedLanguages);
1442     CFRelease(supportedLanguages);
1443     supportedLanguages = NULL;
1444 }
1445 
1446 
LoadPreferredLanguages()1447 static void LoadPreferredLanguages(){
1448     FILE *f;
1449     int i;
1450     char *p;
1451     char language[32];
1452     char loginName[256];
1453     char tempFileName[MAXPATHLEN];
1454 
1455     BOINCTranslationInit();
1456 
1457     // First pass wrote a list of our preferred languages to a temp file
1458     strncpy(loginName, getenv("USER"), sizeof(loginName)-1);
1459     snprintf(tempFileName, sizeof(tempFileName), "/tmp/UninstallBOINC-%s/BOINC_preferred_languages", loginName);
1460     f = fopen(tempFileName, "r");
1461     if (!f) return;
1462 
1463     for (i=0; i<MAX_LANGUAGES_TO_TRY; ++i) {
1464         fgets(language, sizeof(language), f);
1465         if (feof(f)) break;
1466         language[sizeof(language)-1] = '\0';    // Guarantee a null terminator
1467         p = strchr(language, '\n');
1468         if (p) *p = '\0';           // Replace newline with null terminator
1469         if (language[0]) {
1470             if (!BOINCTranslationAddCatalog(gCatalogsDir, language, gCatalog_Name)) {
1471                 printf("could not load catalog for langage %s\n", language);
1472             }
1473         }
1474     }
1475     fclose(f);
1476 }
1477 
1478 
ShowMessage(Boolean allowCancel,Boolean continueButton,Boolean yesNoButtons,const char * format,...)1479 static Boolean ShowMessage(Boolean allowCancel, Boolean continueButton, Boolean yesNoButtons, const char *format, ...) {
1480     va_list                 args;
1481     char                    s[1024];
1482     CFOptionFlags           responseFlags;
1483     CFURLRef                myIconURLRef = NULL;
1484     CFBundleRef             myBundleRef;
1485     CFDictionaryRef         myInfoPlistData;
1486     CFStringRef             appIconName;
1487     Boolean                 result;
1488 
1489     myBundleRef = CFBundleGetMainBundle();
1490     if (myBundleRef) {
1491         if (!strcmp(gBrandName, "BOINC")) {
1492             // For BOINC uninstaller use our special icon
1493             myIconURLRef = CFBundleCopyResourceURL(myBundleRef, CFSTR("PutInTrash.icns"), NULL, NULL);
1494         } else {
1495             // For branded uninstaller (GR, CE, PtP) use uninstaller application icon
1496             myInfoPlistData = CFBundleGetInfoDictionary(myBundleRef);
1497             if (myInfoPlistData) {
1498                 appIconName = (CFStringRef)CFDictionaryGetValue(myInfoPlistData, CFSTR("CFBundleIconFile"));
1499                 myIconURLRef = CFBundleCopyResourceURL(myBundleRef, appIconName, NULL, NULL);
1500             }
1501         }
1502     }
1503 
1504     va_start(args, format);
1505     vsprintf(s, format, args);
1506     va_end(args);
1507 
1508     CFStringRef myString = CFStringCreateWithCString(NULL, s, kCFStringEncodingUTF8);
1509     CFStringRef theTitle = CFStringCreateWithCString(NULL, gAppName, kCFStringEncodingUTF8);
1510     CFStringRef cancelString = CFStringCreateWithCString(NULL, (char*)_("Cancel"), kCFStringEncodingUTF8);
1511     CFStringRef continueString = CFStringCreateWithCString(NULL, (char*)_("Continue..."), kCFStringEncodingUTF8);
1512     CFStringRef yesString = CFStringCreateWithCString(NULL, (char*)_("Yes"), kCFStringEncodingUTF8);
1513     CFStringRef noString = CFStringCreateWithCString(NULL, (char*)_("No"), kCFStringEncodingUTF8);
1514 
1515     BringAppToFront();
1516 
1517     // Set default button to Continue, OK or No
1518     // Set alternate button to Cancel, Yes, or hidden
1519     SInt32 retval = CFUserNotificationDisplayAlert(0.0, kCFUserNotificationPlainAlertLevel,
1520                 myIconURLRef, NULL, NULL, theTitle, myString,
1521                 continueButton ? continueString : (yesNoButtons ? noString : NULL),
1522                 (allowCancel || yesNoButtons) ? (yesNoButtons ? yesString : cancelString) : NULL,
1523                 NULL, &responseFlags);
1524 
1525     if (myIconURLRef) CFRelease(myIconURLRef);
1526     if (myString) CFRelease(myString);
1527     if (theTitle) CFRelease(theTitle);
1528     if (cancelString) CFRelease(cancelString);
1529     if (continueString) CFRelease(continueString);
1530     if (yesString) CFRelease(yesString);
1531     if (noString) CFRelease(noString);
1532 
1533     if (retval) return false;
1534 
1535     result = (responseFlags == kCFUserNotificationDefaultResponse);
1536     // Return TRUE if user clicked Continue, Yes or OK, FALSE if user clicked Cancel or No
1537     // Note: if yesNoButtons is true, we made default button "No" and alternate button "Yes"
1538     return (yesNoButtons ? !result : result);
1539 }
1540 
1541 
1542 #define NOT_IN_TOKEN                0
1543 #define IN_SINGLE_QUOTED_TOKEN      1
1544 #define IN_DOUBLE_QUOTED_TOKEN      2
1545 #define IN_UNQUOTED_TOKEN           3
1546 
parse_command_line(char * p,char ** argv)1547 int parse_command_line(char* p, char** argv) {
1548     int state = NOT_IN_TOKEN;
1549     int argc=0;
1550 
1551     while (*p) {
1552         switch(state) {
1553         case NOT_IN_TOKEN:
1554             if (isspace(*p)) {
1555             } else if (*p == '\'') {
1556                 p++;
1557                 argv[argc++] = p;
1558                 state = IN_SINGLE_QUOTED_TOKEN;
1559                 break;
1560             } else if (*p == '\"') {
1561                 p++;
1562                 argv[argc++] = p;
1563                 state = IN_DOUBLE_QUOTED_TOKEN;
1564                 break;
1565             } else {
1566                 argv[argc++] = p;
1567                 state = IN_UNQUOTED_TOKEN;
1568             }
1569             break;
1570         case IN_SINGLE_QUOTED_TOKEN:
1571             if (*p == '\'') {
1572                 if (*(p-1) == '\\') break;
1573                 *p = 0;
1574                 state = NOT_IN_TOKEN;
1575             }
1576             break;
1577         case IN_DOUBLE_QUOTED_TOKEN:
1578             if (*p == '\"') {
1579                 if (*(p-1) == '\\') break;
1580                 *p = 0;
1581                 state = NOT_IN_TOKEN;
1582             }
1583             break;
1584         case IN_UNQUOTED_TOKEN:
1585             if (isspace(*p)) {
1586                 *p = 0;
1587                 state = NOT_IN_TOKEN;
1588             }
1589             break;
1590         }
1591         p++;
1592     }
1593     argv[argc] = 0;
1594     return argc;
1595 }
1596 
1597 #include <spawn.h>
1598 
callPosixSpawn(const char * cmdline,bool delayForResult)1599 int callPosixSpawn(const char *cmdline, bool delayForResult) {
1600     char command[1024];
1601     char progName[1024];
1602     char progPath[MAXPATHLEN];
1603     char* argv[100];
1604     int argc = 0;
1605     char *p;
1606     pid_t thePid = 0;
1607     int result = 0;
1608     int status = 0;
1609     extern char **environ;
1610 
1611     // Make a copy of cmdline because parse_command_line modifies it
1612     strlcpy(command, cmdline, sizeof(command));
1613     argc = parse_command_line(const_cast<char*>(command), argv);
1614     strlcpy(progPath, argv[0], sizeof(progPath));
1615     strlcpy(progName, argv[0], sizeof(progName));
1616     p = strrchr(progName, '/');
1617     if (p) {
1618         argv[0] = p+1;
1619     } else {
1620         argv[0] = progName;
1621     }
1622 
1623 #if VERBOSE_TEST
1624     print_to_log_file("***********");
1625     for (int i=0; i<argc; ++i) {
1626         print_to_log_file("argv[%d]=%s", i, argv[i]);
1627     }
1628     print_to_log_file("***********\n");
1629 #endif
1630 
1631     errno = 0;
1632 
1633     result = posix_spawnp(&thePid, progPath, NULL, NULL, argv, environ);
1634 #if VERBOSE_TEST
1635     print_to_log_file("callPosixSpawn command: %s", cmdline);
1636     print_to_log_file("callPosixSpawn: posix_spawnp returned %d: %s", result, strerror(result));
1637 #endif
1638     if (result) {
1639         return result;
1640     }
1641 // CAF    int val =
1642     waitpid(thePid, &status, WUNTRACED);
1643 // CAF        if (val < 0) printf("first waitpid returned %d\n", val);
1644     if (delayForResult) {
1645         // For unknow reason, we don't get the error immediately if user cancelled authorization dialog.
1646         SleepSeconds(0.5);
1647 // CAF    val =
1648         waitpid(thePid, &status, WUNTRACED);
1649 // CAF        if (val < 0) printf("delayed waitpid returned %d\n", val);
1650     }
1651     if (status != 0) {
1652 #if VERBOSE_TEST
1653         print_to_log_file("waitpid() returned status=%d", status);
1654 #endif
1655         result = status;
1656     } else {
1657         if (WIFEXITED(status)) {
1658             result = WEXITSTATUS(status);
1659             if (result == 1) {
1660 #if VERBOSE_TEST
1661                 print_to_log_file("WEXITSTATUS(status) returned 1, errno=%d: %s", errno, strerror(errno));
1662 #endif
1663                 result = errno;
1664             }
1665 #if VERBOSE_TEST
1666             else if (result) {
1667                 print_to_log_file("WEXITSTATUS(status) returned %d", result);
1668             }
1669 #endif
1670         }   // end if (WIFEXITED(status)) else
1671     }       // end if waitpid returned 0 sstaus else
1672 
1673     return result;
1674 }
1675 
1676 
1677 #if VERBOSE_TEST
strip_cr(char * buf)1678 void strip_cr(char *buf)
1679 {
1680     char *theCR;
1681 
1682     theCR = strrchr(buf, '\n');
1683     if (theCR)
1684         *theCR = '\0';
1685     theCR = strrchr(buf, '\r');
1686     if (theCR)
1687         *theCR = '\0';
1688 }
1689 #endif	// VERBOSE_TEST
1690 
1691 
1692 // For debugging
print_to_log_file(const char * format,...)1693 void print_to_log_file(const char *format, ...) {
1694 #if VERBOSE_TEST
1695     FILE *f;
1696     va_list args;
1697     char buf[256];
1698     time_t t;
1699     strlcpy(buf, getenv("HOME"), sizeof(buf));
1700     strlcat(buf, "/Documents/test_log.txt", sizeof(buf));
1701     f = fopen(buf, "a");
1702     if (!f) return;
1703 
1704 //  freopen(buf, "a", stdout);
1705 //  freopen(buf, "a", stderr);
1706 
1707     time(&t);
1708     strlcpy(buf, asctime(localtime(&t)), sizeof(buf));
1709     strip_cr(buf);
1710 
1711     fputs(buf, f);
1712     fputs("   ", f);
1713 
1714     va_start(args, format);
1715     vfprintf(f, format, args);
1716     va_end(args);
1717 
1718     fputs("\n", f);
1719     fflush(f);
1720     fclose(f);
1721 #endif
1722 }
1723