//
//  main.m
//  kordophone
//
//  Created by James Magahern on 11/12/18.
//  Copyright © 2018 James Magahern. All rights reserved.
//

#import <Foundation/Foundation.h>

#import "MBIMBridge.h"

void printUsage()
{
    fprintf(stderr, "Usage: kordophoned [-h] [-s | -c (certificate.p12)] [-a (access control file)\n");
    fprintf(stderr, "\t-h \t Show this help message\n");
    fprintf(stderr, "\t-s \t Use SSL (requires -c option)\n");
    fprintf(stderr, "\t-c \t SSL certificate path encoded as pkcs12\n");
    fprintf(stderr, "\t-a \t Optional access control file\n");
    fprintf(stderr, "\t-p \t Specify port number (default: 5738)\n");
}

BOOL acquireCredentials(bool encrypted, const char *accessFile, NSString **out_username, NSString **out_password)
{
    BOOL success = NO;
    NSString *asString = nil;
    NSError *launchError = nil;
    
    NSString *accessFilePath = [NSString stringWithUTF8String:accessFile];
    if (encrypted) {
        NSPipe *stdoutPipe = [NSPipe pipe];
        NSPipe *stderrPipe = [NSPipe pipe];
        NSTask *task = [[NSTask alloc] init];
        task.launchPath = @"/usr/local/bin/gpg";
        task.arguments = @[ @"-q", @"-d", accessFilePath ];
        task.standardOutput = stdoutPipe;
        task.standardError = stderrPipe;
        
        success = [task launchAndReturnError:&launchError];
        [task waitUntilExit];
        
        if (success) {
            NSFileHandle *stdoutFile = stdoutPipe.fileHandleForReading;
            NSData *data = [stdoutFile readDataToEndOfFile]; // blocks
            [stdoutFile closeFile];
            
            if ([task terminationStatus] != 0) {
                NSData *stderrData = [[stderrPipe fileHandleForReading] readDataToEndOfFile];
                MBIMLogFatal(@"GPG error when decrypting access file: %@", [[NSString alloc] initWithData:stderrData encoding:NSUTF8StringEncoding]);
                return NO;
            }
            
            asString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        }
    } else {
        NSError *fileReadError = nil;
        asString = [NSString stringWithContentsOfFile:accessFilePath
                                             encoding:NSASCIIStringEncoding
                                                error:&fileReadError];
        if (fileReadError != nil) {
            MBIMLogFatal(@"File open error when opening access file: %@", fileReadError.localizedDescription);
            return NO;
        }
        
        success = (asString.length > 0);
    }
    
    if (success) {
        NSScanner *scanner = [NSScanner scannerWithString:asString];
        
        BOOL scannerSuccess = NO;
        NSString *username = nil;
        scannerSuccess = [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet]
                                                 intoString:&username];
        if (!scannerSuccess) {
            MBIMLogFatal(@"Error parsing username from access file");
            return NO;
        }
        
        NSString *password = nil;
        scannerSuccess = [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet]
                                                 intoString:&password];
        if (!scannerSuccess) {
            MBIMLogFatal(@"Error parsing password from access file");
            return NO;
        }
        
        if ([username length] && [password length]) {
            *out_username = username;
            *out_password = password;
        } else {
            MBIMLogFatal(@"Error parsing username or password from access file");
            return NO;
        }
    } else {
        MBIMLogFatal(@"Unable to launch GPG executable to decrypt access file: %@", [launchError localizedDescription]);
        return NO;
    }
    
    return YES;
}

int main(int argc, char *const argv[]) {
    @autoreleasepool {
        BOOL usesSSL = NO;
        BOOL showHelp = NO;
        BOOL usesAccessControl = NO;
        long portNumber = -1;
        const char *certPath = NULL;
        const char *accessFilePath = NULL;
        
        int c = -1;
        while ( (c = getopt(argc, argv, "hsc:a:p:")) != -1 ) {
            switch (c) {
                case 's':
                    usesSSL = YES;
                    break;
                case 'c':
                    certPath = optarg;
                    break;
                case 'a':
                    usesAccessControl = YES;
                    accessFilePath = optarg;
                    break;
                case 'p':
                    portNumber = strtol(optarg, NULL, 10);
                    break;
                case 'h':
                    showHelp = YES;
                    break;
                case '?':
                    if (optopt == 'c') {
                        fprintf (stderr, "Option -%c requires an argument.\n", optopt);
                    } else if (isprint(optopt)) {
                        fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                    } else {
                        fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
                        return 1;
                    }
                default:
                    abort ();
            }
        }
        
        if (showHelp) {
            printUsage();
            return 1;
        }
        
        if (usesSSL && certPath == NULL) {
            fprintf(stderr, "Error: wants SSL (-s) but no ssl certificate path (-c) provided\n");
            return 1;
        }
        
        MBIMBridge *bridge = [MBIMBridge sharedInstance];
        
        if (usesAccessControl) {
            NSString *username = nil;
            NSString *password = nil;
            
            BOOL success = acquireCredentials(false, accessFilePath, &username, &password);
            if (!success) {
                MBIMLogInfo(
                    @"Access file must be a GPG encrypted file (encrypted with your private key, to your pub key) "
                     "with the follwing format: \n"
                     "(username)\n"
                     "(password)"
                );
                
                return 1;
            } else {
                const ssize_t ast_len = 55;
                const unichar asterisks[ast_len] = u"*******************************************************";
                NSString *obscuredPassword = [NSString stringWithCharacters:asterisks
                                                                     length:MIN([password length], ast_len)];
                MBIMLogNotify(@"Using access control credentials: username(%@) password(%@)", username, obscuredPassword);
                
                bridge.usesAccessControl = YES;
                bridge.authUsername = username;
                bridge.authPassword = password;
            }
        }
        
        if (usesSSL && certPath != NULL) {
            bridge.usesSSL = YES;
            bridge.sslCertPath = [NSString stringWithCString:certPath encoding:NSASCIIStringEncoding];
        }
        
        if (portNumber > 0) {
            bridge.port = portNumber;
        }
        
        [bridge connect];
        
        BOOL running = YES;
        while (running) {
            [[NSRunLoop currentRunLoop] run];
        }
    }
    
    return 0;
}
