Critcl wrapper for Mac OS X Authorization services, only the AuthorizationExecuteWithPrivileges() API for now, c.f. [L1 ] for details.
Part of CarbonCritLib: http://rutherglen.ics.mq.edu.au/~steffen/tcltk/carboncritlib/tclAuthorization.tcl
[ DAS 06/10/07 ]
#!/bin/sh # ####################################################################### # # tclAuthorization.tcl # # Critcl wrapper for Mac OS X Authorization services: # - AuthorizationExecuteWithPrivileges() only for now # # Process this file with 'critcl -pkg' to build a loadable package (or # simply source this file if [package require critcl] and a compiler # are available at deployment). # # # Author: Daniel A. Steffen # E-mail: <[email protected]> # mail: Mathematics Departement # Macquarie University NSW 2109 Australia # www: <http://www.maths.mq.edu.au/~steffen/ > # # RCS: @(#) $Id$ # # BSD License: c.f. <http://www.opensource.org/licenses/bsd-license > # # Copyright (c) 2005-2007, Daniel A. Steffen <[email protected]> # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # * Redistributions of source code must retain the above # copyright notice, this list of conditions and the # following disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # * Neither the name of Macquarie University nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MACQUARIE # UNIVERSITY OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # ####################################################################### # \ exec critcl -pkg "$0" "$@" package require critcl if {![::critcl::compiling]} {error "No compiler found"} #--------------------------------------------------------------------------------------------------- package provide tclAuthorization 1.0 namespace eval tclAuthorization { if {[llength [info commands ::critcl::framework]]]} { ::critcl::framework Security } else { lappend ::critcl::v::compile -framework Security } ::critcl::ccode { #include <stdio.h> #include <unistd.h> #include <Security/Authorization.h> #include <Security/AuthorizationTags.h> typedef struct { OSStatus code; const char *name; } AuthErrId; static AuthErrId authErrIds[] = { { errAuthorizationSuccess, "AuthorizationSuccess: The operation completed successfully." }, { errAuthorizationInvalidSet, "AuthorizationInvalidSet: The set parameter is invalid." }, { errAuthorizationInvalidRef, "AuthorizationInvalidRef: The authorization parameter is invalid." }, { errAuthorizationInvalidTag, "AuthorizationInvalidTag: The tag parameter is invalid." }, { errAuthorizationInvalidPointer, "AuthorizationInvalidPointer: The authorizedRights parameter is invalid." }, { errAuthorizationDenied, "AuthorizationDenied: The authorization was denied." }, { errAuthorizationCanceled, "AuthorizationCanceled: The authorization was cancelled by the user." }, { errAuthorizationInteractionNotAllowed, "AuthorizationInteractionNotAllowed: The authorization was denied since no user interaction was possible." }, { errAuthorizationInternal, "AuthorizationInternal: something else went wrong" }, { errAuthorizationExternalizeNotAllowed, "AuthorizationExternalizeNotAllowed: authorization externalization denied" }, { errAuthorizationInternalizeNotAllowed, "AuthorizationInternalizeNotAllowed: authorization internalization denied" }, { errAuthorizationInvalidFlags, "AuthorizationInvalidFlags: invalid option flag(s)" }, { errAuthorizationToolExecuteFailure, "AuthorizationToolExecuteFailure: cannot execute privileged tool" }, { errAuthorizationToolEnvironmentError, "AuthorizationToolEnvironmentError: privileged tool environment error" }, { errAuthorizationBadAddress, "AuthorizationBadAddress: invalid socket address requested" }, { 0, NULL } }; static const char *AuthErrTxt(OSStatus status) { AuthErrId *errId = &authErrIds[0]; while (errId->name && errId->code != status) { errId++; } return errId->name; } } #--------------------------------------------------------------------------------------------------- # # tclAuthorization::executeWithPrivileges /path/to/executable ?arg ...? # # this command takes an absolute path to an executable along with optional arguments for it, # and runs that executable with an effective user id of root, after presenting a standard # Mac OS X authorization dialog (whose prompt shows the given executable and arguments). # # If sucessful, the command returns a channel connected to stdin and stdout of the # executable (stderr is unavailable). That channel needs be closed manually once the # executable has exited or is no longer needed. # #--------------------------------------------------------------------------------------------------- ::critcl::ccommand executeWithPrivileges {ClientData interp objc objv} { int i, result = TCL_ERROR; Tcl_DString ds; Tcl_Obj *pathPtr = NULL; const char *myPathToTool; char** myArguments = NULL; FILE *myCommunicationsPipe; Tcl_Channel chan; OSStatus myStatus; AuthorizationItem myAuthorizationExecuteRight = {kAuthorizationRightExecute, 0, NULL, 0}; AuthorizationRights myAuthorizationRights = {1, &myAuthorizationExecuteRight}; AuthorizationItem myAuthorizationPrompt = {kAuthorizationEnvironmentPrompt, 0, NULL, 0}; AuthorizationEnvironment myAuthorizationEnvironment = {1, &myAuthorizationPrompt}; AuthorizationRef myAuthorizationRef = NULL; while (1) { Tcl_DStringInit(&ds); if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "executable ?arg...?"); break; } pathPtr = Tcl_FSGetNormalizedPath(interp, objv[1]); if (!pathPtr) { Tcl_AppendResult(interp, "could not normalize path to executable \"", Tcl_GetString(objv[1]), "\"", NULL); break; } Tcl_IncrRefCount(pathPtr); if (Tcl_FSAccess(pathPtr, X_OK)) { Tcl_AppendResult(interp, "command \"", Tcl_GetString(objv[1]), "\" is not executable: ", Tcl_PosixError(interp), NULL); break; } myPathToTool = Tcl_FSGetNativePath(pathPtr); if (!myPathToTool) { Tcl_AppendResult(interp, "could not get native path to executable \"", Tcl_GetString(objv[1]), "\": ", Tcl_PosixError(interp), NULL); break; } Tcl_DStringAppend(&ds, "Authorize execution of command\n\t", -1); Tcl_DStringAppendElement(&ds, myPathToTool); if (objc > 2) { int n = objc - 2; myArguments = (char**) ckalloc((n + 1) * sizeof(char*)); for (i = 0; i < n; i++) { myArguments[i] = Tcl_GetString(objv[i+2]); Tcl_DStringAppendElement(&ds, myArguments[i]); } myArguments[i] = NULL; } Tcl_DStringAppend(&ds, "\n", 1); myAuthorizationPrompt.value = (void*) Tcl_DStringValue(&ds); myAuthorizationPrompt.valueLength = Tcl_DStringLength(&ds); myAuthorizationExecuteRight.value = (void*) myPathToTool; myAuthorizationExecuteRight.valueLength = strlen(myPathToTool); myStatus = AuthorizationCreate(&myAuthorizationRights, &myAuthorizationEnvironment, kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, &myAuthorizationRef); if (myStatus != errAuthorizationSuccess) { Tcl_AppendResult(interp, "could not authorize, ", AuthErrTxt(myStatus), NULL); break; } myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, myPathToTool, kAuthorizationFlagDefaults, myArguments, &myCommunicationsPipe); if (myStatus != errAuthorizationSuccess) { Tcl_AppendResult(interp, "could not execute command, ", AuthErrTxt(myStatus), NULL); break; } chan = Tcl_MakeFileChannel((void*)fileno(myCommunicationsPipe), TCL_READABLE|TCL_WRITABLE); if (!chan) { Tcl_AppendResult(interp, "could not make tcl channel for I/O pipe", NULL); break; } Tcl_RegisterChannel(interp, chan); Tcl_SetResult(interp, (char*)Tcl_GetChannelName(chan), TCL_VOLATILE); result = TCL_OK; break; } if(myAuthorizationRef) { AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDestroyRights); } if (pathPtr) { Tcl_DecrRefCount(pathPtr); } if (myArguments) { ckfree((char*)myArguments); } Tcl_DStringFree(&ds); return result; } } #---------------------------------------------------------------------------------------------------