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.
demoserver.c
/* |
File: demoserver.c |
Abstract: Demonstration of various authentication from a server perspective |
Version: <1.0> |
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. |
Copyright © 2005 Apple Computer, Inc., All Rights Reserved |
*/ |
#include <Kerberos/Kerberos.h> |
#include <DirectoryService/DirectoryService.h> |
#include <unistd.h> |
#include <stdio.h> |
#include <sys/types.h> |
#include <sys/socket.h> |
#include "GSSauthenticate.h" |
#include "NetworkHelper.h" |
#include "DSUtility.h" |
#pragma mark Prototypes |
int AuthCleartext( char *inUsername, char *inPassword ); |
int AuthCRAMMD5( char *inUsername, char *inChallenge, char *inResponse ); |
int AuthGSSAPI( int inSocket ); |
int HandleNewConnection( int inSocket ); |
int HandleCleartext( int inSocket ); |
int HandleCRAMMD5( int inSocket ); |
char *FindUserFromPrincipal( char *inPrincipal ); |
#pragma mark - |
#pragma mark Authentication Routines |
int AuthCleartext( char *inUsername, char *inPassword ) |
{ |
tDirReference dsRef = 0; |
tDirNodeReference dsSearchNodeRef = 0; |
tDirNodeReference dsUserNodeRef = 0; |
tDirStatus dsStatus; |
char *pRecordName = NULL; |
char *pNodeName = NULL; |
// Key steps to Authenticating a user: |
// - First locate the user in the directory |
// - Open Directory Service reference |
// - Locate and open the Search Node |
// - Locate the user's official RecordName and Directory Node based on the username provided |
// - Then use authentication appropriate for the type of method |
// Open Directory Services reference |
dsStatus = dsOpenDirService( &dsRef ); |
if( dsStatus == eDSNoErr ) { |
// use utility function in DSUtility.h to open the search node |
dsStatus = OpenSearchNode( dsRef, &dsSearchNodeRef ); |
if( dsStatus == eDSNoErr ) { |
// use utility function in DSUtility.h to locate the user information |
dsStatus = LocateUserRecordNameAndNode( dsRef, dsSearchNodeRef, inUsername, &pRecordName, &pNodeName ); |
if( dsStatus == eDSNoErr ) { |
// we should have values available, but let's check to be sure |
if( pNodeName != NULL && pNodeName[0] != '\0' && |
pRecordName != NULL && pRecordName[0] != '\0' ) |
{ |
// need to create a tDataListPtr from the "/plugin/node" path, using "/" as the separator |
tDataListPtr dsUserNodePath = dsBuildFromPath( dsRef, pNodeName, "/" ); |
dsStatus = dsOpenDirNode( dsRef, dsUserNodePath, &dsUserNodeRef ); |
if( dsStatus == eDSNoErr ) { |
// Use our Utility routine to do the authentication |
dsStatus = DoPasswordAuth( dsRef, dsUserNodeRef, kDSStdAuthNodeNativeClearTextOK, |
pRecordName, inPassword ); |
// Determine if successful. There are cases where you may receive other errors |
// such as eDSAuthPasswordExpired. |
if( dsStatus == eDSNoErr ) { |
printf( "Successful: Authentication successful for user '%s'\n", pRecordName ); |
} else { |
printf( "Failure: Authentication for user '%s' - %d\n", pRecordName, dsStatus ); |
} |
} |
// free the data list as it is no longer needed |
dsDataListDeallocate( dsRef, dsUserNodePath ); |
free( dsUserNodePath ); |
dsUserNodePath = NULL; |
} |
// need to free any node name that may have been returned |
if( pNodeName != NULL ) { |
free( pNodeName ); |
pNodeName = NULL; |
} |
// need to free any record name that may have been returned |
if( pRecordName != NULL ) { |
free( pRecordName ); |
pRecordName = NULL; |
} |
} |
// close the search node cause we are done here |
dsCloseDirNode( dsSearchNodeRef ); |
dsSearchNodeRef = 0; |
} else { |
printf( "Unable to locate and open the Search node\n" ); |
return 1; |
} |
// need to close Directory Services at this point |
dsCloseDirService( dsRef ); |
dsRef = 0; |
} |
return dsStatus; |
} |
int AuthCRAMMD5( char *inUsername, char *inChallenge, char *inResponse ) |
{ |
tDirReference dsRef = 0; |
tDirNodeReference dsSearchNodeRef = 0; |
tDirNodeReference dsUserNodeRef = 0; |
tDirStatus dsStatus; |
char *pRecordName = NULL; |
char *pNodeName = NULL; |
// Key steps to Authenticating a user: |
// - First locate the user in the directory |
// - Open Directory Service reference |
// - Locate and open the Search Node |
// - Locate the user's official RecordName and Directory Node based on the username provided |
// - Then use authentication appropriate for the type of method |
// Open Directory Services reference |
dsStatus = dsOpenDirService( &dsRef ); |
if( dsStatus == eDSNoErr ) { |
// use utility function to open the search node |
dsStatus = OpenSearchNode( dsRef, &dsSearchNodeRef ); |
if( dsStatus == eDSNoErr ) { |
// use utility function to locate the user information |
dsStatus = LocateUserRecordNameAndNode( dsRef, dsSearchNodeRef, inUsername, &pRecordName, &pNodeName ); |
if( dsStatus == eDSNoErr ) { |
// we should have values available, but let's check to be sure |
if( pNodeName != NULL && pNodeName[0] != '\0' && |
pRecordName != NULL && pRecordName[0] != '\0' ) |
{ |
// need to create a tDataListPtr from the "/plugin/node" path, using "/" as the separator |
tDataListPtr dsUserNodePath = dsBuildFromPath( dsRef, pNodeName, "/" ); |
// attempt to open the node provided |
dsStatus = dsOpenDirNode( dsRef, dsUserNodePath, &dsUserNodeRef ); |
if( dsStatus == eDSNoErr ) { |
// Here is the Utility function that will do the authentication for us |
dsStatus = DoChallengeResponseAuth( dsRef, dsUserNodeRef, kDSStdAuthCRAM_MD5, pRecordName, |
inChallenge, strlen(inChallenge), |
inResponse, strlen(inResponse) ); |
// Determine if successful. There are cases where you may receive other errors |
// such as eDSAuthPasswordExpired. |
if( dsStatus == eDSNoErr ) { |
printf( "Successful: CRAM-MD5 Authentication successful for user '%s'\n", pRecordName ); |
} else { |
printf( "Failure: CRAM-MD5 Authentication for user '%s' - %d\n", pRecordName, dsStatus ); |
} |
} |
// free the data list as it is no longer needed |
dsDataListDeallocate( dsRef, dsUserNodePath ); |
free( dsUserNodePath ); |
dsUserNodePath = NULL; |
} |
// need to free any node name that may have been returned |
if( pNodeName != NULL ) { |
free( pNodeName ); |
pNodeName = NULL; |
} |
// need to free any record name that may have been returned |
if( pRecordName != NULL ) { |
free( pRecordName ); |
pRecordName = NULL; |
} |
} |
// close the search node cause we are done here |
dsCloseDirNode( dsSearchNodeRef ); |
dsSearchNodeRef = 0; |
} else { |
printf( "Unable to locate and open the Search node\n" ); |
return 1; |
} |
// need to close Directory Services at this point |
dsCloseDirService( dsRef ); |
dsRef = 0; |
} |
return dsStatus; |
} |
int AuthGSSAPI( int inSocket ) |
{ |
char *pRecvToken = NULL; |
int iRecvTokenLen = 0; |
char *pSendToken = NULL; |
int iSendTokenLen = 0; |
char *pUserPrinc = NULL; |
char *pServiceName = NULL; |
gss_ctx_id_t gssContext = GSS_C_NO_CONTEXT; |
gss_cred_id_t gssCreds = GSS_C_NO_CREDENTIAL; |
OM_uint32 iResult = GSS_S_DEFECTIVE_CREDENTIAL; |
OM_uint32 iMinorStatus; |
// Key steps to authenticating a user with GSSAPI: |
// |
// GSSAPI is a request-response loop until finished. For every token received |
// there may is a token sent back. This loop is done until an error occurs or |
// we recieve a GSS_S_COMPLETE. All tokens generated must be sent back to |
// complete the process. |
// After successfully verifying the GSSAPI succeeded, we need to also verify |
// the user is a valid user on the system. GSSAPI validates the user's credentials |
// not whether or not the user is allowed on this system. |
do { |
// read token data from the socket |
iRecvTokenLen = ReadToken( inSocket, &pRecvToken ); |
printf( "Received a token with length of %d\n", iRecvTokenLen ); |
if( iRecvTokenLen > 0 ) { |
// let's read and write data sending to and from the GSSauthenticate |
iResult = AuthenticateGSS( pRecvToken, iRecvTokenLen, &pSendToken, &iSendTokenLen, |
&pServiceName, &pUserPrinc, &gssContext, &gssCreds ); |
} |
// if we have a token to send, let's send it to the client |
if( iSendTokenLen != 0 ) { |
printf( "Sending a token length of %d\n", iSendTokenLen ); |
int iSent = SendToken(inSocket, pSendToken, iSendTokenLen); |
if( iSent == -1 || iSent != iSendTokenLen ) { |
// we had a problem, set failure state |
printf( "Failed to send packet.. bailing\n" ); |
iResult = GSS_S_FAILURE; |
} |
} |
// free up our token if we have one |
if( pRecvToken != NULL ) { |
free( pRecvToken ); |
pRecvToken = NULL; |
} |
// free up our send token if we have one |
if( pSendToken != NULL ) { |
free( pSendToken ); |
pSendToken = NULL; |
} |
} while( iResult == GSS_S_CONTINUE_NEEDED ); |
printf( "\n" ); // just print a line feed for visuals |
// if we completed successfully print out the userPrincipal if we had one |
if( iResult == GSS_S_COMPLETE && pUserPrinc != NULL && pServiceName != NULL ) { |
char *pRecordName; |
printf( "Success: Valid credentials provided by '%s' service '%s'\n", pUserPrinc, pServiceName ); |
// here you would compare the service to ensure it was the service you expected |
// if( strcasecmp( pServiceName, #expected service#) == 0 ) { |
// |
// } |
if( pRecordName = FindUserFromPrincipal( pUserPrinc ) ) { |
printf( "Success: Found a user record for user - %s\n", pRecordName ); |
// send a response |
SendToken( inSocket, "Accepted", sizeof("Accepted") ); |
// need to free pRecordName cause it is returned |
free( pRecordName ); |
pRecordName = NULL; |
} else { |
// send response |
SendToken( inSocket, "Rejected", sizeof("Rejected") ); |
printf( "Failure: No matching user record found for user\n" ); |
} |
} else { |
if( pUserPrinc != NULL ) { |
printf( "Error: Unable to authenticate user %s\n", pUserPrinc ); |
} |
if( pServiceName != NULL ) { |
printf( "Error: Unable to verify service %s\n", pServiceName ); |
} |
} |
// clean up any context |
if( gssContext != GSS_C_NO_CONTEXT ) { |
gss_delete_sec_context( &iMinorStatus, gssContext, GSS_C_NO_BUFFER ); |
gssContext = GSS_C_NO_CONTEXT; |
} |
// clean up any credentials |
if( gssCreds != GSS_C_NO_CREDENTIAL ) { |
gss_release_cred( &iMinorStatus, &gssCreds ); |
gssCreds = GSS_C_NO_CREDENTIAL; |
} |
return 0; |
} |
#pragma mark - |
#pragma mark Connection Negotiation |
int HandleNewConnection( int inSocket ) |
{ |
char *pRecvToken = NULL; |
int iRecvTokenLen = 0; |
int iResult = -1; |
// use ReadToken to get the first packet to identify the type of authentication |
iRecvTokenLen = ReadToken( inSocket, &pRecvToken ); |
if( pRecvToken != NULL ) { |
if( strcasecmp(pRecvToken, "GSSAPI") == 0 ) { |
printf( "Attempt to authenticate with method GSSAPI received\n" ); |
iResult = AuthGSSAPI( inSocket ); |
} else if( strcasecmp(pRecvToken, "CRAM-MD5") == 0 ) { |
printf( "Attempt to authenticate with method CRAM-MD5 received\n" ); |
iResult = HandleCRAMMD5( inSocket ); |
} else if( strcasecmp(pRecvToken, "cleartext") == 0 ) { |
printf( "Attempt to authenticate with method cleartext received\n" ); |
iResult = HandleCleartext( inSocket ); |
} else { |
printf( "Unknown method received for authentication\n" ); |
} |
// just print a blank line |
printf( "\n" ); |
// free our token |
free( pRecvToken ); |
pRecvToken = NULL; |
} |
return iResult; |
} |
int HandleCleartext( int inSocket ) |
{ |
char *pUsername = NULL; |
char *pPassword = NULL; |
char *pRecordname = NULL; |
int iResult = -1; |
// we will be using ReadToken and WriteToken to send data back and forth for this |
// example, though not necessarily the most efficient, it is simplistic for this |
// demonstration. |
// we expect 2 tokens, username then password |
// read the username from the network stream |
if( ReadToken(inSocket, &pUsername) > 0 ) { |
// read the password from the network stream |
if( ReadToken(inSocket, &pPassword) > 0 ) { |
iResult = AuthCleartext( pUsername, pPassword ); |
// send a response |
if( iResult == eDSNoErr ) { |
SendToken( inSocket, "Accepted", sizeof("Accepted") ); |
} else { |
SendToken( inSocket, "Rejected", sizeof("Rejected") ); |
} |
} |
} |
// free any allocated strings |
if( pUsername != NULL ) { |
free( pUsername ); |
pUsername = NULL; |
} |
if( pPassword != NULL ) { |
free( pPassword ); |
pPassword = NULL; |
} |
if( pRecordname != NULL ) { |
free( pRecordname ); |
pRecordname = NULL; |
} |
return iResult; |
} |
int HandleCRAMMD5( int inSocket ) |
{ |
unsigned char pChallenge[255] = { 0, }; |
char pHostname[128] = { 0, }; |
char *pResponse = NULL; |
char *pUsername = NULL; |
struct timeval stCurrentTime; |
int iResult = -1; |
// we will be using ReadToken and WriteToken to send data back and forth for this |
// example, though not necessarily the most efficient, it is simplistic for this |
// demonstration. |
// Since CRAM-MD5 was requested, let's generate a challenge and send it to the client |
// using example method in RFC 1460, page 12. |
gethostname( pHostname, 127 ); |
gettimeofday( &stCurrentTime, NULL ); // assume no error occurred |
snprintf( pChallenge, 255, "<%ld.%ld@%s>", (long) getpid(), stCurrentTime.tv_sec, pHostname ); |
printf( "Sending challenge %s\n", pChallenge ); |
// send our challenge to the client |
if( SendToken(inSocket, pChallenge, strlen(pChallenge)) > 0 ) { |
// now wait for username and response to return |
if( ReadToken(inSocket, &pUsername) > 0 ) { |
if( ReadToken(inSocket, &pResponse) > 0 ) { |
// here is where we authenticate the user using Open Directory |
iResult = AuthCRAMMD5( pUsername, pChallenge, pResponse ); |
// send a response |
if( iResult == eDSNoErr ) { |
SendToken( inSocket, "Accepted", sizeof("Accepted") ); |
} else { |
SendToken( inSocket, "Rejected", sizeof("Rejected") ); |
} |
} |
} |
} |
// free up any strings we allocated |
if( pUsername != NULL ) { |
free( pUsername ); |
pUsername = NULL; |
} |
if( pResponse != NULL ) { |
free( pResponse ); |
pResponse = NULL; |
} |
return iResult; |
} |
#pragma mark - |
#pragma mark Main Entry |
// this demostration application will receive a authentication method type and some value following |
// it to attempt to authenticate a user. |
int main( int argc, char *argv[] ) |
{ |
int iPort; |
int iSocket; |
if( argc != 2 ) { |
printf( "Usage: demoserver <port #>\n" ); |
return 1; |
} |
if( geteuid() != 0 ) { |
printf( "Warning: Tool must be run as EUID = 0 to use Kerberos/GSSAPI\n" ); |
} |
// if we were relying on Kerberos for this service, we could pre-flight our |
// service principal before we start the service, or just let GSSAPI determine |
// at the time of connection |
// |
// we have opted for at time of connection, because we don't know what we |
// are using uses for service principal |
// let's convert the string provided for the port into a number |
iPort = strtol( argv[1], NULL, 10 ); |
// don't allow privileged ports for this test |
if( iPort < 1024 ) { |
printf( "Error: Port value must be greater than 1024\n" ); |
} |
// this demonstration code will wait for an authentication identification code |
// GSSAPI |
// CRAM-MD5 |
// cleartext |
// Once method is determined, it will attempt to authenticate the user |
iSocket = BindSocket( iPort ); |
if( iSocket > 0 ) { |
// just informative |
printf( "Listening on port %d\n", iPort ); |
// go into a loop until we are killed |
while( 1 ) { |
int iAccept; |
// Accept a TCP connection |
if( (iAccept = accept(iSocket, NULL, 0)) < 0) { |
printf("Error: Couldn't accept connection"); |
continue; |
} |
// once we have a connection, go to our request-response loop |
if( HandleNewConnection( iAccept ) == 0 ) { |
// if we had no error we could go do something, but in this case we will |
// just close the connection |
close( iAccept ); |
iAccept = 0; |
} else { |
// close our socket |
close( iAccept ); |
iAccept = 0; |
} |
} |
// close our socket |
close( iSocket ); |
iSocket = 0; |
} else { |
printf( "Error: Unable to listen on port %d\n", iPort ); |
return 1; |
} |
return 0; |
} |
#pragma mark - |
#pragma mark Support Functions |
// helper routine to fine a user's official RecordName |
// |
char *FindUserFromPrincipal( char *inPrincipal ) |
{ |
// now let's parse the name and see if we can find a valid user.. |
tDirReference dsRef = 0; |
tDirNodeReference dsSearchNodeRef = 0; |
tDirStatus dsStatus; |
char *pRecordName = NULL; |
char *pNodeName = NULL; |
char *pUsername = strdup( inPrincipal ); |
char *pAtSymbol = strchr( pUsername, '@' ); |
// need to parse just the name of the user, since principal is user@REALM |
if( pAtSymbol != NULL ) { |
*pAtSymbol = '\0'; |
} |
// Open Directory Services reference |
dsStatus = dsOpenDirService( &dsRef ); |
if( dsStatus == eDSNoErr ) { |
// use utility function in DSUtility.h to open the search node |
dsStatus = OpenSearchNode( dsRef, &dsSearchNodeRef ); |
if( dsStatus == eDSNoErr ) { |
// use utility function in DSUtility.h to locate the user information |
dsStatus = LocateUserRecordNameAndNode( dsRef, dsSearchNodeRef, pUsername, &pRecordName, &pNodeName ); |
if( dsStatus == eDSNoErr ) { |
// need to free any node name that may have been returned |
if( pNodeName != NULL ) { |
free( pNodeName ); |
pNodeName = NULL; |
} |
} |
// close the search node cause we are done here |
dsCloseDirNode( dsSearchNodeRef ); |
dsSearchNodeRef = 0; |
} else { |
printf( "Unable to locate and open the Search node to verify user\n" ); |
} |
// need to close Directory Services at this point |
dsCloseDirService( dsRef ); |
dsRef = 0; |
} |
if( pUsername != NULL ) { |
free( pUsername ); |
pUsername = NULL; |
} |
return pRecordName; |
} |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-06-01