httpserver.m   [plain text]


//
//  Copyright (c) 2014-2015 Apple. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CFNetwork/CFNetwork.h>
#import <CFNetwork/CFHTTPServerPriv.h>
#import <GSS/GSS.h>

#import "httpserver.h"

@interface GSSHTTPServer ()
@property (nonatomic, readonly) _CFHTTPServerRef httpServer;
@end

@implementation GSSHTTPServer


static void
_connection_didBecomeInvalid(const void* arg)
{
}

static void
_connection_didReceiveError(const void* arg, CFErrorRef err)
{
}

static void
provideError(_CFHTTPServerRequestRef req, int statusCode, NSData *body, const char *message)
{
    CFHTTPMessageRef msg = _CFHTTPServerRequestCreateResponseMessage(req, statusCode);
    if (statusCode == 401) {
        NSString *header = @"NTLM";
        if (body) {
            NSString *b64 = [body base64EncodedStringWithOptions:0];
            header = [NSString stringWithFormat:@"NTLM %@", b64];
        }

        CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("WWW-Authenticate"), (__bridge CFStringRef)header);
    }
    const char* kErrorText = "Authentencation failure";
    CFDataRef d = CFDataCreate(kCFAllocatorDefault, (const UInt8*) kErrorText, strlen(kErrorText));
    _CFHTTPServerResponseRef response = _CFHTTPServerResponseCreateWithData(req, msg, d);
    CFRelease(d);
    CFRelease(msg);
    _CFHTTPServerResponseEnqueue(response);
    CFRelease(response);

}

static void
_connection_didReceiveRequest(const void* arg, _CFHTTPServerRequestRef req)
{
    _CFHTTPServerResponseRef response = NULL;
    static gss_ctx_id_t handle = NULL;
    OM_uint32 min_stat, maj_stat;

    CFStringRef header = _CFHTTPServerRequestCopyProperty(req, _kCFHTTPServerRequestMethod);
    if (header == NULL || !CFEqual(header, CFSTR("GET"))) {
        CFHTTPMessageRef msg = _CFHTTPServerRequestCreateResponseMessage(req, 500);
        const char* kErrorText = "Request not a GET";
        CFDataRef d = CFDataCreate(kCFAllocatorDefault, (const UInt8*) kErrorText, strlen(kErrorText));
        _CFHTTPServerResponseRef response = _CFHTTPServerResponseCreateWithData(req, msg, d);
        CFRelease(d);
        CFRelease(msg);
        _CFHTTPServerResponseEnqueue(response);
        CFRelease(response);
        return;
    }

    header = _CFHTTPServerRequestCopyProperty(req, CFSTR("Authorization"));
    if (header == NULL) {
        provideError(req, 401, NULL, "Authentication failure");
        return;
    }

    NSArray<NSString *> *components = [((__bridge NSString *)header) componentsSeparatedByString:@" "];
    if ([components count] != 2 || ![[components objectAtIndex:0] isEqualToString:@"NTLM"]) {
        provideError(req, 401, NULL, "Authentication failure, not NTLM");
        gss_delete_sec_context(&min_stat, &handle, NULL);
        return;
    }

    NSData *data = [[NSData alloc] initWithBase64EncodedString:[components objectAtIndex:1] options:0];

    gss_buffer_desc input = {
        .value = (void *)[data bytes],
        .length = [data length],
    };
    gss_buffer_desc output;
    gss_OID mech = NULL;


    maj_stat = gss_accept_sec_context(&min_stat, &handle, NULL, &input, NULL, NULL, &mech, &output, NULL, NULL, NULL);
    if (maj_stat == GSS_S_CONTINUE_NEEDED) {
        NSData *reply = [NSData dataWithBytes:output.value length:output.length];
        gss_release_buffer(&min_stat, &output);
        provideError(req, 401, reply, "Authentication continue needed");
        return;
    } else if (maj_stat != GSS_S_COMPLETE) {
        gss_delete_sec_context(&min_stat, &handle, NULL);
        gss_release_buffer(&min_stat, &output);
        provideError(req, 403, NULL, "Authentication failed");
        return;
    } else {
        gss_delete_sec_context(&min_stat, &handle, NULL);
    }
    gss_release_buffer(&min_stat, &output);

    CFDataRef d = CFDataCreate(kCFAllocatorDefault, (void *)"hello", 5);

    CFHTTPMessageRef msg = _CFHTTPServerRequestCreateResponseMessage(req, 200);
    response = _CFHTTPServerResponseCreateWithData(req, msg, d);
    CFRelease(d);
    CFRelease(msg);

    if (response) {
        _CFHTTPServerResponseEnqueue(response);
        CFRelease(response);
    }
}

static void
_connection_didSendResponse(const void* arg, _CFHTTPServerRequestRef req, _CFHTTPServerResponseRef response)
{
}

static void
_connection_didFailToSendResponse(const void* arg, _CFHTTPServerRequestRef req, _CFHTTPServerResponseRef response)
{
}


static void
_server_didCloseConnection(const void* info, _CFHTTPServerConnectionRef conn)
{
}

static void
_server_didBecomeInvalid(const void* info)
{
}

static void
_server_didReceiveError(const void* info, CFErrorRef err)
{
}

static void
_server_didOpenConnection(const void* info, _CFHTTPServerConnectionRef conn)
{
    _CFHTTPServerClient client = {
        0,
        NULL,
        NULL,
        NULL,
        NULL
    };

    _CFHTTPServerConnectionCallbacks callbacks = {
        _kCFHTTPServerConnectionCallbacksCurrentVersion,
        _connection_didBecomeInvalid,
        _connection_didReceiveError,
        _connection_didReceiveRequest,
        _connection_didSendResponse,
        _connection_didFailToSendResponse
    };

    _CFHTTPServerConnectionSetClient(conn, &client, &callbacks);
    _CFHTTPServerConnectionScheduleWithRunLoopAndMode(conn, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}


_CFHTTPServerClient serverClient = {
    _kCFHTTPServerClientCurrentVersion,
    NULL,
    NULL,
    NULL,
    NULL
};

_CFHTTPServerCallbacks callbacks = {
    _kCFHTTPServerCallbacksCurrentVersion,
    _server_didBecomeInvalid,
    _server_didReceiveError,
    _server_didOpenConnection,
    _server_didCloseConnection,
};


- (void)start:(NSNumber *)servicePort
{

    CFStringRef s = CFSTR("GSSCFHTTPServerTests");
    _httpServer = _CFHTTPServerCreateService(kCFAllocatorDefault, &serverClient, &callbacks, s, CFSTR("_gsscfhttpserver._tcp."), [servicePort intValue]);
    CFRelease(s);

    _CFHTTPServerScheduleWithRunLoopAndMode(_httpServer, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

    NSLog(@"http server running on port: %@", servicePort);

    while (_httpServer && _CFHTTPServerIsValid(_httpServer)) {
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1E30, true);
    }

    NSLog(@"server done");

}

@end