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