Updated 2007-10-06 04:01:27 by das

Critcl wrapper for Mac OS X Authorization services, only the AuthorizationExecuteWithPrivileges() API for now, c.f. [1] 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;
    
}

}
#---------------------------------------------------------------------------------------------------

Category Package