Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
QISAPlatformMach-O/QISASetupTool.c
/* |
File: QISASetupTool.c |
Contains: Implementation of the setuid setup tool for the Mac OS X platform. |
Written by: DTS |
Copyright: Copyright © 2002 by Apple Computer, Inc., All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): |
*/ |
///////////////////////////////////////////////////////////////// |
// System interfaces |
#include <stdio.h> |
#include <unistd.h> |
#include <sys/stat.h> |
// MoreIsBetter interfaces |
#include "MoreSCFPortScanner.h" |
#include "MoreSCF.h" |
#include "MoreSCFHelpers.h" |
#include "MoreCFQ.h" |
#include "MoreSecurity.h" |
#include "MoreUNIX.h" |
// QISA interfaces |
#include "QISA.h" |
///////////////////////////////////////////////////////////////// |
// Set the following to 1 to get extra debugging output. |
#if MORE_DEBUG |
#define MORE_DEBUG_EXTRA 0 |
#endif |
// The Mach-O platform plug-in's bundle. It's passed to us |
// as part of the configuration dictionary. We store it in a |
// global so that it can be accessed by MyPortNameCallback. |
static CFBundleRef gPlatformBundle; |
static pascal kern_return_t MyPortNameCallback(io_object_t interface, |
CFMutableDictionaryRef interfaceInfo, |
CFStringRef proposedUserVisibleName, |
CFStringRef *userVisibleName) |
// This is a port name callback used to provide localised |
// port names to MoreSCFPortScanner. See the comments in |
// "MoreSCFPortScanner.h" for more detials. |
{ |
#pragma unused(interface) |
#pragma unused(interfaceInfo) |
assert(gPlatformBundle != NULL); |
*userVisibleName = CFBundleCopyLocalizedString(gPlatformBundle, proposedUserVisibleName, NULL, NULL); |
// If there is no localised name, *userVisibleName will just |
// be a copy of proposedUserVisibleName. However, MoreSCF wants |
// us to return NULL in that case. |
if ( (*userVisibleName != NULL) && CFEqual(*userVisibleName, proposedUserVisibleName) ) { |
CFQRelease(*userVisibleName); |
*userVisibleName = NULL; |
} |
return 0; |
} |
// This string is used as an entity name. Whenever this tool creates |
// a set, it creates an entity within that set with this key. Later |
// if it goes to overwrite a set, it makes sure that the set contains |
// the same global entity. Thus, you can only use this tool to create |
// a new set or overwrite a set that you created; you can't use it |
// to overwrite a set created by someone else. |
// |
// Note that this is a key security measure, so it has to be hard-wired |
// into the tool. Passing it as a parameter in the configuration |
// dictionary would defeat this (limited) security. |
#define kQISASetIdentifier CFSTR("com.apple.dts.QISA") |
static OSStatus CopyCustomDictionary(CFStringRef setID, CFStringRef dictKey, CFDictionaryRef *dict) |
// This routine copies a dictionary from the set's preferences. |
// The dictionary path is "/Sets/<setID/<dictKey>", where in the |
// case of this product <dictKey> is "com.apple.dts.QISA". This |
// is the recommended place to store application-specific preferences |
// within the SCF preferences. This routine should probably be |
// moved into MoreSCF proper, once i have a little more experience |
// with this idea. |
{ |
OSStatus err; |
CFStringRef path; |
assert(setID != NULL); |
assert(dictKey != NULL); |
assert( dict != NULL); |
assert( *dict == NULL); |
assert( MoreSCGetSCPreferencesRef() != NULL ); // You must be inside MoreSCOpen/MoreSCClose pair when calling this routine. |
path = NULL; |
path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/%@/%@/%@"), kSCPrefSets, setID, dictKey); |
err = CFQError(path); |
if (err == noErr) { |
*dict = (CFDictionaryRef) CFQRetain( SCPreferencesPathGetValue(MoreSCGetSCPreferencesRef(), path) ); |
err = MoreSCError(*dict); |
} |
CFQRelease(path); |
assert( (err == noErr) == (*dict != NULL) ); |
return err; |
} |
static OSStatus SetCustomDictionary(CFStringRef setID, CFStringRef dictKey, CFDictionaryRef dict) |
// This routine stores a dictionary in the set's preferences. |
// The dictionary path is "/Sets/<setID/<dictKey>", where in the |
// case of this product <dictKey> is "com.apple.dts.QISA". This |
// is the recommended place to store application-specific preferences |
// within the SCF preferences. This routine should probably be |
// moved into MoreSCF proper, once i have a little more experience |
// with this idea. |
{ |
OSStatus err; |
CFStringRef path; |
assert(setID != NULL); |
assert(dictKey != NULL); |
assert( dict != NULL); |
assert( MoreSCGetSCPreferencesRef() != NULL ); // You must be inside MoreSCOpen/MoreSCClose pair when calling this routine. |
path = NULL; |
path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/%@/%@/%@"), kSCPrefSets, setID, dictKey); |
err = CFQError(path); |
if (err == noErr) { |
err = MoreSCErrorBoolean( SCPreferencesPathSetValue(MoreSCGetSCPreferencesRef(), path, dict) ); |
} |
CFQRelease(path); |
return err; |
} |
static OSStatus DeleteSet(CFStringRef setID) |
// This routines is called to delete a set that we want to overwrite |
// in the SCF preferences. It has to jump through some hoops to keep |
// the SCF preferences database consistent. Also, it checks to make |
// sure that we're not deleting a set that we didn't create. |
{ |
OSStatus err; |
CFDictionaryRef junkEntityDict; |
CFArrayRef setIDs; |
CFIndex indexOfCurrentSet; |
setIDs = NULL; |
junkEntityDict = NULL; |
// Check to see that the set we're about to delete has our custom entity. |
// If not, that's an error. |
err = CopyCustomDictionary(setID, kQISASetIdentifier, &junkEntityDict); |
if (err != noErr) { |
err = errAuthorizationDenied; |
} |
// Get a list of sets and see whether the current set is the one we're trying to delete. |
if (err == noErr) { |
err = MoreSCCopySetIDs(&setIDs, &indexOfCurrentSet); |
} |
if ( (err == noErr) && CFEqual(CFArrayGetValueAtIndex(setIDs, indexOfCurrentSet), setID) ) { |
// If so, switch to another set. What happens if there is no other set? |
// Well, we create a default one. |
if ( CFArrayGetCount(setIDs) == 1 ) { |
err = MoreSCNewSet(CFSTR("Automatic"), NULL); |
} else { |
CFIndex newSetIndex; |
if (indexOfCurrentSet == 0) { |
newSetIndex = 1; |
} else { |
newSetIndex = 0; |
} |
err = MoreSCSetCurrentSet(CFArrayGetValueAtIndex(setIDs, newSetIndex)); |
} |
} |
if (err == noErr) { |
err = MoreSCDeleteSet(setID); |
} |
CFQRelease(setIDs); |
CFQRelease(junkEntityDict); |
return err; |
} |
static OSStatus StartPrivilegedMode(mode_t *oldUMask) |
// SCF requires that the EUID be 0 in order to open the database |
// with the lock held, and to commit changes. Also, if you |
// commit changes with a 077 umask (which was set that way by |
// MoreSecDestroyInheritedEnvironment), bad things happen |
// (non-privileged programs can no longer read the prefs!), so we |
// have to reset umask to the default before committing changes. |
{ |
*oldUMask = umask(S_IWGRP | S_IWOTH); |
return EXXXToOSStatus( MoreSecSetPrivilegedEUID() ); |
} |
static void StopPrivilegedMode(mode_t oldUMask) |
// Undoes what StartPrivilegedMode did. |
{ |
int junk; |
(void) umask(oldUMask); |
junk = MoreSecTemporarilySetNonPrivilegedEUID(); |
assert(junk == 0); |
} |
static OSStatus QISAMakeNetworkConfig( CFStringRef userName, |
CFStringRef password, |
CFStringRef number, |
Boolean useTerminal, |
CFStringRef userVisibleName, |
CFStringRef chosenCCL, |
CFStringRef bsdName) |
// A sub-routine called by SetupToolCommandProc to actually create |
// a network configuration. |
{ |
OSStatus err; |
OSStatus err2; |
MoreSCModemDigest modem; |
MoreSCPPPDigest ppp; |
MoreSCPPPOptions pppOptions; |
MoreSCIPv4Digest ipv4; |
CFStringRef existingSetID; |
CFStringRef newSetID; |
mode_t oldUMask; |
newSetID = NULL; |
existingSetID = NULL; |
// Open the databask with the expect umask and EUID 0, then do |
// the rest of the work with umask 077 and EUID == REUID. |
err = StartPrivilegedMode(&oldUMask); |
if (err == noErr) { |
err = MoreSCOpen(true, true); |
StopPrivilegedMode(oldUMask); |
} |
if (err == noErr) { |
// If the set already exists, delete it. |
if (err == noErr) { |
err = MoreSCFindSetByUserVisibleNameAndCopyID(userVisibleName, &existingSetID); |
} |
if (err == noErr && existingSetID != NULL) { |
#if MORE_DEBUG_EXTRA |
fprintf(stderr, "QISASetupTool: deleting set\n"); |
CFShow(existingSetID); |
#endif |
err = DeleteSet(existingSetID); |
} |
// Create the set based on our input parameters. |
if (err == noErr) { |
#if MORE_DEBUG_EXTRA |
fprintf(stderr, "QISASetupTool: creating set\n"); |
CFShow(userVisibleName); |
#endif |
memset(&modem, 0, sizeof(modem)); |
modem.connectionScript = chosenCCL; |
modem.dataCompressionErrorCorrection = true; |
modem.speaker = true; |
modem.pulseDial = false; |
modem.waitForDialTone = true; |
pppOptions = *MoreSCGetDefaultPPPOptions(false); |
pppOptions.commDisplayTerminalWindow = useTerminal; |
memset(&ppp, 0, sizeof(ppp)); |
ppp.active = true; |
ppp.authName = userName; |
ppp.authPassword = password; |
ppp.commRemoteAddress = number; |
ppp.options = &pppOptions; |
memset(&ipv4, 0, sizeof(ipv4)); |
ipv4.configMethod = kSCValNetIPv4ConfigMethodPPP; |
err = MoreSCMakeNewDialupSet( bsdName, |
userVisibleName, |
&modem, |
&ppp, |
NULL, |
NULL, |
&newSetID); |
} |
// Our our custom entity. This marks the set as created by us. |
// If we're asked to overwrite a set, we check that this entity |
// exists and fail if it doesn't (in DeleteSet). That way we |
// can only create a new set or overwrite a set that we previously |
// created. |
if (err == noErr) { |
CFDictionaryRef emptyDict; |
emptyDict = CFDictionaryCreate(NULL, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
err = CFQError(emptyDict); |
if (err == noErr) { |
err = SetCustomDictionary(newSetID, kQISASetIdentifier, emptyDict); |
} |
CFQRelease(emptyDict); |
} |
// Switch to the new set. |
if (err == noErr) { |
#if MORE_DEBUG_EXTRA |
fprintf(stderr, "QISASetupTool: Switching to set\n"); |
CFShow(newSetID); |
#endif |
err = MoreSCSetCurrentSet(newSetID); |
} |
err2 = StartPrivilegedMode(&oldUMask); |
MoreSCClose(&err, (err != noErr)); |
if (err2 == noErr) { |
StopPrivilegedMode(oldUMask); |
} else if (err == noErr) { |
// I'm going to ignore err2 in this case. We didn't manage to switch |
// to EUID 0 and yet the call to MoreSCClose worked, so who cares? |
} |
} |
CFQRelease(existingSetID); |
CFQRelease(newSetID); |
return err; |
} |
static OSStatus SetupToolCommandProc(AuthorizationRef auth, CFDictionaryRef request, CFDictionaryRef *result) |
// This routine is a callback passed to MoreSecHelperToolMain. |
// See the description of that routine (in "MoreSecurity.h") |
// for more information about how it's executed. The 30,000 |
// foot summary is that the helper tool's main function calls |
// MoreSecHelperToolMain which organises to call back this |
// routine in a privileged context with the configuration that |
// originated from the application. This routine does the |
// actual configuration and then returns any results in |
// *result, which is passed back to the application. |
{ |
OSStatus err; |
CFStringRef username; |
CFStringRef password; |
CFStringRef number; |
CFBooleanRef useTerminal; |
Boolean useTerminalBool; |
CFStringRef userVisibleName; |
CFStringRef chosenCCL; |
CFDictionaryRef chosenPort; |
CFStringRef bsdName; |
CFStringRef platformBundle; |
CFURLRef platformBundleURL; |
assert(auth != NULL); |
assert(request != NULL); |
assert( result != NULL); |
assert(*result == NULL); |
platformBundleURL = NULL; |
#if MORE_DEBUG_EXTRA |
fprintf(stderr, "QISASetupTool: request = \n"); |
CFShow(request); |
#endif |
// Extract parameters from dictionary and do some basic checks. |
if ( CFDictionaryContainsKey(request, kQISAKeyTemporary) ) { |
username = CFDictionaryGetValue(request, kQISAKeySetupUsername); |
password = CFDictionaryGetValue(request, kQISAKeySetupPassword); |
number = CFDictionaryGetValue(request, kQISAKeySetupNumber); |
useTerminal = CFDictionaryGetValue(request, kQISAKeySetupUseTerminal); |
} else { |
username = CFDictionaryGetValue(request, kQISAKeyUsername); |
password = CFDictionaryGetValue(request, kQISAKeyPassword); |
number = CFDictionaryGetValue(request, kQISAKeyNumber); |
useTerminal = CFDictionaryGetValue(request, kQISAKeyUseTerminal); |
} |
userVisibleName = CFDictionaryGetValue(request, kQISAKeyUserVisibleName); |
chosenCCL = CFDictionaryGetValue(request, kQISAKeyChosenCCL); |
chosenPort = CFDictionaryGetValue(request, kQISAKeyChosenPort); |
platformBundle = CFDictionaryGetValue(request, kQISAKeyPlatformBundle); |
err = noErr; |
if ( username == NULL || CFGetTypeID(username) != CFStringGetTypeID() ) { |
err = paramErr; |
} |
if ( password == NULL || CFGetTypeID(password) != CFStringGetTypeID() ) { |
err = paramErr; |
} |
if ( number == NULL || CFGetTypeID(number) != CFStringGetTypeID() ) { |
err = paramErr; |
} |
if ( useTerminal == NULL || CFGetTypeID(useTerminal) != CFBooleanGetTypeID() ) { |
err = paramErr; |
} else { |
useTerminalBool = CFBooleanGetValue(useTerminal); |
} |
if ( userVisibleName == NULL || CFGetTypeID(userVisibleName) != CFStringGetTypeID() ) { |
err = paramErr; |
} |
if ( chosenCCL == NULL || CFGetTypeID(chosenCCL) != CFStringGetTypeID() ) { |
err = paramErr; |
} |
if ( chosenPort == NULL || CFGetTypeID(chosenPort) != CFDictionaryGetTypeID() ) { |
err = paramErr; |
} else { |
bsdName = CFDictionaryGetValue(chosenPort, kSCPropNetInterfaceDeviceName); |
if ( bsdName == NULL || CFGetTypeID(bsdName) != CFStringGetTypeID() ) { |
err = paramErr; |
} |
} |
if ( platformBundle == NULL || CFGetTypeID(platformBundle) != CFStringGetTypeID() ) { |
err = paramErr; |
} |
// Create gPlatformBundle from the string in the dictionary. |
if (err == noErr) { |
platformBundleURL = CFURLCreateWithString(NULL, platformBundle, NULL); |
err = CFQError(platformBundleURL); |
} |
if (err == noErr) { |
gPlatformBundle = CFBundleCreate(NULL, platformBundleURL); |
err = CFQError(gPlatformBundle); |
} |
// Go do the work. |
if (err == noErr) { |
MoreSCFSetPortNameCallback(MyPortNameCallback); |
err = QISAMakeNetworkConfig(username, password, number, useTerminalBool, userVisibleName, chosenCCL, bsdName); |
} |
#if MORE_DEBUG_EXTRA |
fprintf(stderr, "QISASetupTool: response = %ld\n", err); |
#endif |
CFQRelease(platformBundleURL); |
return err; |
} |
int main(int argc, const char *argv[]) |
// The helper tool's main function. This follows the template |
// described in "MoreSecurity.h" very closely. |
{ |
int err; |
int result; |
AuthorizationRef auth; |
// If you enable the "pause" call below, the helper tool will stop |
// here and wait for a signal. You can then debug it by attaching |
// to its PID from GDB. |
#if MORE_DEBUG_EXTRA |
fprintf(stderr, "QISASetupTool: PID %qd starting\n", (long long) getpid()); |
fprintf(stderr, " EUID = %ld\n", (long) geteuid()); |
fprintf(stderr, " RUID = %ld\n", (long) getuid()); |
if (0) { |
fprintf(stderr, "Waiting for debugger\n"); |
(void) pause(); |
} |
#endif |
// Get any auth ref passed to us from AuthorizationExecuteWithPrivileges |
// before we call MoreSecDestroyInheritedEnvironment, because AEWP passes |
// its auth ref to us via the environment. |
auth = MoreSecHelperToolCopyAuthRef(); |
// Because we're normally running as a setuid root program, it's |
// important that we not trust any information coming to us from |
// our potentially malicious parent process. |
err = MoreSecDestroyInheritedEnvironment(kMoreSecKeepStandardFilesMask, argv); |
// Mask SIGPIPE, otherwise stuff won't work properly. |
if (err == 0) { |
err = MoreUNIXIgnoreSIGPIPE(); |
} |
// Call the MoreSecurity helper routine. |
if (err == 0) { |
err = MoreSecHelperToolMain(STDIN_FILENO, STDOUT_FILENO, auth, SetupToolCommandProc, argc, argv); |
} |
// Map the error code to a tool result. |
result = MoreSecErrorToHelperToolResult(err); |
#if MORE_DEBUG_EXTRA |
fprintf(stderr, "QISASetupTool: PID %qd stopping\n", (long long) getpid()); |
fprintf(stderr, " err = %d\n", err); |
fprintf(stderr, " result = %d\n", result); |
#endif |
return result; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-05-15