//
//  SSHTunnel.m
//  SSH Tunnel Manager 2
//
//  Created by Yann Bizeul on Wed Nov 19 2003.
//  Copyright (c) 2003 __MyCompanyName__. All rights reserved.
//

#import "SSHTunnel.h"
#include <unistd.h>

// start diff lorenz textor
/*
#define T_START NSLocalizedString(@"T_START",@"")
#define T_STOP NSLocalizedString(@"T_STOP",@"")
#define S_IDLE NSLocalizedString(@"S_IDLE",@"")
#define S_CONNECTING NSLocalizedString(@"S_CONNECTING",@"")
#define S_CONNECTED NSLocalizedString(@"S_CONNECTED",@"")
#define S_AUTH NSLocalizedString(@"S_AUTH",@"")
#define S_PORT NSLocalizedString(@"S_PORT",@"")
*/
#define T_START @"START: %@"
#define T_STOP @"STOP: %@"
#define S_IDLE @"Idle"
#define S_CONNECTING @"Connecting..."
#define S_CONNECTED @"Connected"
#define S_AUTH @"Authenticated"
#define S_PORT "Port %@ forwarded"
// end diff lorenz textor

@implementation SSHTunnel

#pragma mark -
#pragma mark Initialization
-(id)init
{
    return [ self initWithName:@"New Tunnel"];
}
-(id)initWithName:(NSString*)aName
{
    NSDictionary *dictionary = [ NSDictionary dictionaryWithObjectsAndKeys:
	[ NSNumber numberWithBool: NO ],@"compression",
	[ NSNumber numberWithBool: YES ],@"connAuth",
	@"", @"connHost",
	aName, @"connName",
	@"", @"connPort",
	[ NSNumber numberWithBool: NO ],@"connRemote",
	@"", @"connUser",
	@"3des", @"encryption",
	[ NSNumber numberWithBool: NO ],@"socks4",
	[ NSNumber numberWithInt: 1080 ], @"socks4p",
	[ NSArray array ], @"tunnelsLocal",
	[ NSArray array ], @"tunnelsRemote",
	[ NSNumber numberWithBool: NO ],@"v1", nil
	];
    return [ self initWithDictionary: dictionary ];
}
-(id)initWithDictionary:(NSDictionary*)aDictionary
{
    NSEnumerator *e;
    NSString *key;

    self = [ super init ];
    e = [[ aDictionary allKeys ] objectEnumerator ];
    while (key = [ e nextObject ])
    {
	[ self setValue: [ aDictionary objectForKey: key ] forKey: key ];
    }
    code = 0;
    if ([[ self valueForKey: @"autoConnect" ] boolValue ])
	[ self startTunnel ];
    return self;
}
+(id)tunnelWithName:(NSString*)aName
{
    return [[ SSHTunnel alloc ] initWithName: aName ];
}
+(SSHTunnel*)tunnelFromDictionary:(NSDictionary*)aDictionary
{
    return [[ SSHTunnel alloc ] initWithDictionary: aDictionary ];
}
+(NSArray*)tunnelsFromArray:(NSArray*)anArray
{
    NSMutableArray *newArray;
    SSHTunnel *currentTunnel;
    NSEnumerator *e;
    NSDictionary *currentTunnelDictionary;
    
    newArray = [ NSMutableArray array ];
    e = [ anArray objectEnumerator ];
    while (currentTunnelDictionary = [ e nextObject ])
    {
	currentTunnel = [ SSHTunnel tunnelFromDictionary: currentTunnelDictionary ];
	[ newArray addObject: currentTunnel ];
    }
    return [[ newArray copy ] autorelease ];
}

#pragma mark -
#pragma mark Adding and removing port redir.
-(void)addLocalTunnel:(NSDictionary*)aDictionary;
{
    NSMutableArray *tempTunnelsLocal = [ NSMutableArray arrayWithArray: tunnelsLocal ];
    [ tempTunnelsLocal addObject: aDictionary ];
    [ tunnelsLocal release ];
    tunnelsLocal = [ tempTunnelsLocal copy ];
}
- (void)removeLocal:(int)index
{
    NSMutableArray *tempLocalTunnels = [ tunnelsLocal mutableCopy ];
    [ tempLocalTunnels removeObjectAtIndex: index ];
    [ tunnelsLocal release ];
    tunnelsLocal = [ tempLocalTunnels copy ];
    [ tempLocalTunnels release ];
}
-(void)addRemoteTunnel:(NSDictionary*)aDictionary;
{
    NSMutableArray *tempTunnelsRemote = [ NSMutableArray arrayWithArray: tunnelsRemote ];
    [ tempTunnelsRemote addObject: aDictionary ];
    [ tunnelsRemote release ];
    tunnelsRemote = [ tempTunnelsRemote copy ];
}
- (void)removeRemote:(int)index
{
    NSMutableArray *tempRemoteTunnels = [ tunnelsRemote mutableCopy ];
    [ tempRemoteTunnels removeObjectAtIndex: index ];
    [ tunnelsRemote release ];
    tunnelsRemote = [ tempRemoteTunnels copy ];
    [ tempRemoteTunnels release ];
}
- (void)setLocalValue:(NSString*)aValue ofTunnel:(int)index forKey:(NSString*)key
{
    NSMutableArray *tempLocalTunnel;
    NSMutableDictionary *tempCurrentTunnel;
    
    tempLocalTunnel = [tunnelsLocal mutableCopy];
    tempCurrentTunnel = [[ tempLocalTunnel objectAtIndex: index ] mutableCopy ];
    
    [ tempCurrentTunnel setObject: aValue forKey: key ];
    [ tempLocalTunnel replaceObjectAtIndex:index withObject:[tempCurrentTunnel copy ]];
    [ tempCurrentTunnel release ];
    [ tunnelsLocal release ];
    tunnelsLocal = [ tempLocalTunnel copy ];
}
- (void)setRemoteValue:(NSString*)aValue ofTunnel:(int)index forKey:(NSString*)key
{
    NSMutableArray *tempRemoteTunnel;
    NSMutableDictionary *tempCurrentTunnel;
    
    tempRemoteTunnel = [tunnelsRemote mutableCopy];
    tempCurrentTunnel = [[ tempRemoteTunnel objectAtIndex: index ] mutableCopy ];
    
    [ tempCurrentTunnel setObject: aValue forKey: key ];
    [ tempRemoteTunnel replaceObjectAtIndex:index withObject:[tempCurrentTunnel copy ]];
    [ tempCurrentTunnel release ];
    [ tunnelsRemote release ];
    tunnelsRemote = [ tempRemoteTunnel copy ];
}

#pragma mark -
#pragma mark Execution related
- (void)startTunnel
{    
//    NSDictionary *t;
//    NSEnumerator *e;
//    BOOL asRoot = NO;
    
    if ([ self isRunning ])
	return;
    
    shouldStop = NO;
    /*
    [ arguments addObject: @"-N" ];
    [ arguments addObject: @"-v" ];
    [ arguments addObject: @"-p" ];
    if ([ connPort length ])
	[ arguments addObject: connPort];
    else
	[ arguments addObject: @"22" ];
    
    if (connRemote)
	[ arguments addObject: @"-g" ];
    if (compression)
	[ arguments addObject: @"-C" ];
    if (v1)
	[ arguments addObject: @"-1" ];
    
    [ arguments addObject: @"-c"];
    if (encryption)
	[ arguments addObject: encryption];
    else
	[ arguments addObject: @"3des"];
    
    if (socks4 && socks4p != nil)
    {
	[ arguments addObject: @"-D" ];
	[ arguments addObject: [ socks4p stringValue ]];
    }
    [ arguments addObject: [ NSString stringWithFormat: @"%@@%@",
	connUser, connHost ]
	];
    
    NSString *hostPort;
    e = [ tunnelsLocal objectEnumerator ];
    while (t = [ e nextObject ])
    {
	[ arguments addObject: @"-L" ];
	if ([[ t objectForKey:@"hostport"] isEqualTo: @"" ])
	    hostPort = [ t objectForKey:@"port" ];
	else
	    hostPort = [ t objectForKey:@"hostport" ];
	[ arguments addObject: [NSString stringWithFormat:@"%@/%@/%@",
	    [ t objectForKey:@"port"],
	    [ t objectForKey:@"host"],
	    hostPort
	    ] ];
	if ([[ t objectForKey:@"port"] intValue] < 1024)
	    asRoot=YES;
    }
    
    e = [ tunnelsRemote objectEnumerator ];
    while (t = [ e nextObject ])
    {
	[ arguments addObject: @"-R" ];
	if ([[ t objectForKey:@"hostport"] isEqualTo: @"" ])
	    hostPort = [ t objectForKey:@"port" ];
	else
	    hostPort = [ t objectForKey:@"hostport" ];
	[ arguments addObject: [NSString stringWithFormat:@"%@/%@/%@",
	    [ t objectForKey:@"port"],
	    [ t objectForKey:@"host"],
	    hostPort
	    ]];
    }
    args = [ NSMutableDictionary dictionary ];
    [ args setObject: arguments forKey:@"arguments" ];
    [ args setObject: [ NSNumber numberWithBool: connAuth ] forKey: @"handleAuth" ];
    [ args setObject: connName forKey:@"tunnelName" ];
    [ args setObject: [ NSNumber numberWithBool: asRoot ] forKey: @"asRoot" ];
    
    
     [ NSThread detachNewThreadSelector:@selector(launchTunnel:)
			       toTarget: self
			     withObject: args ];
     */
    [ NSThread detachNewThreadSelector:@selector(launchTunnel:)
			      toTarget: self
			    withObject: nil ];
//    [ arguments release ];
    
}
- (void)stopTunnel
{
    if (! [ self isRunning ])
	return;
    shouldStop=YES;
    [ self setValue: nil forKey: @"status" ];
    [ task terminate ];
    code = 0;
    [[ NSNotificationCenter defaultCenter]  postNotificationName:@"STMStatusChanged" object:self ];
}

- (void)toggleTunnel
{
    if ([ self isRunning ])
	[ self stopTunnel ];
    else
	[ self startTunnel ];
}

- (void)launchTunnel:(id)foo;
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    if (task)
	[ task release ];
    task = [[ NSTask alloc ] init ];
    NSMutableDictionary *environment = [ NSMutableDictionary dictionaryWithDictionary: [[ NSProcessInfo processInfo ] environment ]];
    NSString *pathToAuthentifier = [[ NSBundle mainBundle ] pathForResource: @"askForPass" ofType: @"sh" ];
    if (socks4)
	[ task setLaunchPath: [[ NSBundle mainBundle ] pathForResource: @"ssh" ofType: @"" ]];
    else
	[ task setLaunchPath: @"/usr/bin/ssh" ];
    [ task setArguments: [ self arguments ]];
    if (connAuth)
    {
        [ environment removeObjectForKey: @"SSH_AGENT_PID" ];
        [ environment removeObjectForKey: @"SSH_AUTH_SOCK" ];
        [ environment setObject: pathToAuthentifier forKey: @"SSH_ASKPASS" ];
        [ environment setObject:@":0" forKey:@"DISPLAY" ];
    }
    [ environment setObject: connName forKey: @"TUNNEL_NAME" ];
    [ task setEnvironment: environment ];

    stdErrPipe = [[ NSPipe alloc ] init ];
    [ task setStandardError: stdErrPipe ];
    
    [[ NSNotificationCenter defaultCenter] addObserver:self 
					      selector:@selector(stdErr:) 
						  name: @"NSFileHandleDataAvailableNotification"
						object:[ stdErrPipe fileHandleForReading]];
    
    [[ stdErrPipe fileHandleForReading] waitForDataInBackgroundAndNotify ];

    NSLog(T_START,connName);
    [ self setValue: S_CONNECTING forKey: @"status" ];
    code = 1;
    [ task launch ];
    [[ NSNotificationCenter defaultCenter]  postNotificationName:@"STMStatusChanged" object:self ];
    [ task waitUntilExit ];
    sleep(1);
    code = 0;
    [ self setValue: S_IDLE forKey: @"status" ];
    NSLog(T_STOP,connName);
    [[ NSNotificationCenter defaultCenter] removeObserver:self 
						     name: @"NSFileHandleDataAvailableNotification"
						   object:[ stdErrPipe fileHandleForReading]];    
    [ task release ];
    task = nil;
    [ stdErrPipe release ];
    stdErrPipe = nil;
    [[ NSNotificationCenter defaultCenter]  postNotificationName:@"STMStatusChanged" object:self ];
    if (! shouldStop)
	[ self startTunnel ];
    [ pool release ];
}

- (void)stdErr:(NSNotification*)aNotification
{
    NSData *data = [[ aNotification object ] availableData ];
    NSString *log = [[ NSString alloc ] initWithData: data encoding: NSASCIIStringEncoding ];
    BOOL wait = YES;
    if ([ log length ])
    {
	//NSLog(log);
	NSArray *lines = [ log componentsSeparatedByString:@"\n" ];
	NSEnumerator *e = [ lines objectEnumerator ];
	NSString *line;
	while (line = [ e nextObject ])
	{
	    if ([ line rangeOfString:@"Entering interactive session." ].location != NSNotFound)
	    {
		code = 2;   
		[ self setValue: S_CONNECTED  forKey: @"status"];
	    }
	    if ([ line rangeOfString:@"Authentication succeeded" ].location != NSNotFound)
		[ self setValue: S_AUTH forKey: @"status"];
	    if ([ line rangeOfString:@"Connections to local port" ].location != NSNotFound)
	    {
		NSScanner *s;
		NSString *port;
		s = [ NSScanner scannerWithString:log];
		[ s scanUpToString: @"Connections to local port " intoString: nil ];
		[ s scanString: @"Connections to local port " intoString: nil ];
		[ s scanUpToString: @"forwarded" intoString:&port];
		[ self setValue: [ NSString stringWithFormat: @"Port %@ forwarded", port ] forKey: @"status"];
	    }
	    if ([ line rangeOfString:@"closed by remote host." ].location != NSNotFound)
	    {
		[ task terminate];
		[ self setValue: @"Connection closed" forKey: @"status"];
		wait = NO;
	    }
	    [[ NSNotificationCenter defaultCenter]  postNotificationName:@"STMStatusChanged" object:self ];
	}
	if (wait)
	    [[ stdErrPipe fileHandleForReading ] waitForDataInBackgroundAndNotify ];
    }
    [ log release] ;
}

- (BOOL)isRunning
{
    if ([ task isRunning ])
	return YES;
    return NO;
}

#pragma mark -
#pragma mark Getting tunnel informations
- (NSString *)status
{
    if (status)
	return status;
    return S_IDLE;
}
- (NSArray*)arguments
{
    NSMutableArray *arguments;
    NSEnumerator *e;
    NSDictionary *t;
    BOOL asRoot;
    
    arguments = [ NSMutableArray array ];
    [ arguments addObject: @"-N" ];
    [ arguments addObject: @"-v" ];
    [ arguments addObject: @"-p" ];
    if ([ connPort length ])
	[ arguments addObject: connPort];
    else
	[ arguments addObject: @"22" ];
    
    if (connRemote)
	[ arguments addObject: @"-g" ];
    if (compression)
	[ arguments addObject: @"-C" ];
    if (v1)
	[ arguments addObject: @"-1" ];
    
    [ arguments addObject: @"-c"];
    if (encryption)
	[ arguments addObject: encryption];
    else
	[ arguments addObject: @"3des"];
    
    if (socks4 && socks4p != nil)
    {
	[ arguments addObject: @"-D" ];
	[ arguments addObject: [ socks4p stringValue ]];
    }
    [ arguments addObject: [ NSString stringWithFormat: @"%@@%@",
	connUser, connHost ]
	];
    
    NSString *hostPort;
    e = [ tunnelsLocal objectEnumerator ];
    while (t = [ e nextObject ])
    {
	[ arguments addObject: @"-L" ];
	if ([[ t objectForKey:@"hostport"] isEqualTo: @"" ])
	    hostPort = [ t objectForKey:@"port" ];
	else
	    hostPort = [ t objectForKey:@"hostport" ];
	[ arguments addObject: [NSString stringWithFormat:@"%@/%@/%@",
	    [ t objectForKey:@"port"],
	    [ t objectForKey:@"host"],
	    hostPort
	    ] ];
	if ([[ t objectForKey:@"port"] intValue] < 1024)
	    asRoot=YES;
    }
    
    e = [ tunnelsRemote objectEnumerator ];
    while (t = [ e nextObject ])
    {
	[ arguments addObject: @"-R" ];
	if ([[ t objectForKey:@"hostport"] isEqualTo: @"" ])
	    hostPort = [ t objectForKey:@"port" ];
	else
	    hostPort = [ t objectForKey:@"hostport" ];
	[ arguments addObject: [NSString stringWithFormat:@"%@/%@/%@",
	    [ t objectForKey:@"port"],
	    [ t objectForKey:@"host"],
	    hostPort
	    ]];
    }
    
    return [[ arguments copy ] autorelease ];
}

- (NSDictionary*)dictionary
{
    return [ NSDictionary dictionaryWithObjectsAndKeys:
	[ NSNumber numberWithBool: compression ],@"compression",
	[ NSNumber numberWithBool: connAuth ],@"connAuth",
	[ NSNumber numberWithBool: autoConnect ],@"autoConnect",
	connHost, @"connHost",
	connName, @"connName",
	connPort, @"connPort",
	[ NSNumber numberWithBool: connRemote ],@"connRemote",
	connUser, @"connUser",
	encryption, @"encryption",
	[ NSNumber numberWithBool: socks4 ],@"socks4",
	socks4p, @"socks4p",
	tunnelsLocal, @"tunnelsLocal",
	tunnelsRemote, @"tunnelsRemote",
	[ NSNumber numberWithBool: v1 ],@"v1", nil
	];
}


#pragma mark -
#pragma mark Key/Value coding
- (NSImage*)icon
{
    switch (code)
    {
	case 0:
	    return [ NSImage imageNamed: @"offState" ];
	    break;
	case 1:
	    return [ NSImage imageNamed: @"middleState" ];
	    break;
	case 2:
	    return [ NSImage imageNamed: @"onState" ];
	    break;
    }
    return [ NSImage imageNamed: @"offState" ];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    NSLog(@"key %@ undefined",key);
}
- (id)valueForUndefinedKey:(NSString *)key
{
    return nil;
}

#pragma mark -
#pragma mark Misc.
-(void)dealloc
{
    [ self stopTunnel ];
    [ tunnelsLocal release ];
    [ tunnelsRemote release ];
    
    [ task release ];
    [ stdErrPipe release ];
    [ connName release ];
    [ status release ];
    [ connPort release ];
    [ encryption release ];
    [ socks4p release ];
    [ connUser release ];
    [ connHost release ];
    
	// start diff lorenz textor
	[super dealloc];
	// end diff lorenz textor
}
@end