ExportController.mm   [plain text]


/*
 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 *  ExportController.m
 *  DSTools
 */

#import "ExportController.h"

#include <errno.h>
#include <sys/termios.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#define kMinNumArgs					4
#define SizeQuantum                 64
#define kProxyAddressArg			"-a"
#define kProxyUserArg				"-u"
#define kProxyPassArg				"-p"
#define kRecordNamesArg				"-r"
#define kExcludeAttribute           "-e"
#define kExportNativeAttributes     "--N"
#define kRecordNamesSeparator		","

#define kHeaderDelimiterChars		"0x0A 0x5C 0x3A 0x2C"

@interface ExportController (PrivateMethods)

- (void)parseArgs:(const char**)argv numArgs:(int)argc;
- (void)showUsage;
- (void)writeStringToOutputFile:(NSString*)theString;
- (void)writeStringToTempFile:(NSString*)theString;
- (void)writeHeaderToOutputFile;
- (void)exportDataToTempFile;
- (void)copyTempFileToOutputFile;
- (NSString*)getSecretString:(NSString*)prompt;
    BOOL create_tempdir(char *temp_dirname_pattern);
@end


@implementation ExportController

// ----------------------------------------------------------------------------
// initializes the class with arguments
// ----------------------------------------------------------------------------

- (ExportController*)initWithArgs:(const char**)argv numArgs:(int)argc
{
	self = [super init];
    _returnAttributes =@kDSAttributesStandardAll;
    
    // create array of attributes that should not be exported
    _attributesToBeExcluded = [[NSMutableArray alloc] initWithObjects:@"dsAttrTypeStandard:AppleMetaNodeLocation",@"dsAttrTypeStandard:RecordType",@"dsAttrTypeNative:objectClass",nil];
	
    // parse arguments given to the tool
    [self parseArgs:argv numArgs:argc];
    //if something about the args was bad, then show the usage
	if( _showUsage )
	{
		[self showUsage];
		exit(0);
	}
    
    // create a secure temp directory
    char str[] = "/tmp/dsexportXXXXXX";
    
    if (!create_tempdir(str)){
        NSLog([NSString stringWithFormat:@"ERROR: DSExport: Failed to create temporary directory.(Error %d)",errno]);
        return nil;
    }
    
    NSMutableString* tempPath = [[NSMutableString alloc] initWithUTF8String:str];
    _tmpDir = [[NSString alloc] initWithUTF8String:str];
    
    // create the path to the temp file (with random number suffix
    int rand = random();
    [tempPath appendString:[NSString stringWithFormat:@"/exportfile%d",rand]];
    [tempPath appendString:@".tmp"];
    _tmpFilePath  = [tempPath copy];
    [tempPath release];
	
	return self;
}

- (void)export
{
    //if something about the args was bad, then show the usage
	if( _showUsage )
	{
		[self showUsage];
		return;
	}
	
    @try
    { 
        
		// First we create a session (locally or on a proxy machine)
        ODSession *mySession;
		NSError *error;
		
		if( _remoteNetAddress == nil ) {  
            //local direct connection = default session
            mySession = [ODSession defaultSession];
            
        } else{     
            //proxy connection
            //create an ODSession with proxy information
            NSDictionary *myOptions      = [[NSDictionary alloc] initWithObjectsAndKeys:    _remoteNetAddress,  ODSessionProxyAddress,
                                                                                                    _userName,          ODSessionProxyUsername,
                                                                                                    _userPass,          ODSessionProxyPassword,
                                                                                                        NULL ];
            mySession = [ODSession sessionWithOptions:myOptions error: &error];
        }   

        // check to see that the connection to the proxy worked and session creation worked
        if (mySession == nil) {
           
            //if not we treat the exception
            NSException *exception;
            if ([error code] == (int)eDSAuthFailed) {
                exception = [NSException exceptionWithName:@"DSExportException"
                                                    reason:[NSString stringWithFormat:@"Authentification failed when trying to establish proxy connection to '%@'.",_remoteNetAddress]  userInfo:[error userInfo]];
                
            } else if ([error code] == (int)eDSUnknownHost){
                exception = [NSException exceptionWithName:@"DSExportException"
                                                    reason:[NSString stringWithFormat:@"'%@' is an unknown host.",_remoteNetAddress]  userInfo:[error userInfo]];
                
            } else if ([error code] == (int)eDSIPUnreachable){
                exception = [NSException exceptionWithName:@"DSExportException"
                                                    reason:[NSString stringWithFormat:@"'%@' is unreachable. Make sure that port 625 is open on this machine.",_remoteNetAddress]  userInfo:[error userInfo]];
                
                
            } else {
                exception = [NSException exceptionWithName:@"DSExportException"
                                                    reason:[NSString stringWithFormat:@"Directory Services returned following error:%@",[error localizedDescription]]  userInfo:[error userInfo]];
                
            }
            
            @throw exception;
        }
        
        //if the session create went fine we make the node
        _node   = [ODNode nodeWithSession:  mySession
                                     name: _nodePath
                                    error: &error ];
        //We check to see that the node create did not provoke any errors
        if (_node == nil) {
            NSException *exception;
            if ([error code] == (int)eDSNodeNotFound) {
                exception = [NSException exceptionWithName:@"DSExportException"
                                                    reason:[NSString stringWithFormat:@"No node was found at the following path:'%@'",_nodePath]  userInfo:[error userInfo]];
                
            }else{
                exception = [NSException exceptionWithName:@"DSExportException"
                                                    reason:[NSString stringWithFormat:@"Directory Services returned following error:%@",[error localizedDescription]]  userInfo:[error userInfo]];
                
            }
            @throw exception;
        }
        
        // We check the record type to see if it has a prefix a 'dsRecTypeStandard' or 'dsRecTypeNative' prefix
        if ((![_recordType hasPrefix:@"dsRecTypeStandard:"])&&(![_recordType hasPrefix:@"dsRecTypeNative:"])){
            // if not we check to see if the node supports the recordtype prefixed by 'dsRecTypeStandard
            // in this case we add the prefix
            // if the not does not support such a prefix we default to the 'dsRecTypeNative' prefix
            NSMutableString* tempRecordType = [[NSMutableString alloc] initWithString:@"dsRecTypeStandard:"];
            [tempRecordType appendString:_recordType];
            NSArray* supportedRecordTypes = [[NSArray alloc] init];
            supportedRecordTypes = [_node supportedRecordTypes: nil]; 
            
            if ([supportedRecordTypes containsObject:tempRecordType]) {
                
                _recordType = [tempRecordType description];
                NSLog([NSString stringWithFormat:@"The specified record type did not contain a valid prefix, the specified type corresponds to a supported standard type. The tool will be using the following record type: %@",_recordType]);
                
            } else {
                
                [tempRecordType setString:@"dsRecTypeNative:"];
                [tempRecordType appendString:_recordType];
                _recordType = [tempRecordType description];
                NSLog([NSString stringWithFormat:@"The specified record type did not contain a valid prefix, the specified type does not corresponds to a supported standard type. The tool therfore defaulted to a native type: %@",_recordType]);
                
            }
            
        }
        
        //delete any temp file that was there before
        if( [[NSFileManager defaultManager] fileExistsAtPath:_tmpFilePath] )
            [[NSFileManager defaultManager] removeFileAtPath:_tmpFilePath handler:nil];
        
        //secure the temp file
        NSDictionary *myFileAttr = [[NSDictionary alloc] initWithObjectsAndKeys: [[NSNumber alloc] initWithInt:S_IRWXU], NSFilePosixPermissions,  NULL];
        
        //create the temp file
        if( ![[NSFileManager defaultManager] createFileAtPath:_tmpFilePath contents:[NSData data] attributes:myFileAttr] ){
        }
        
        //open the temp file for writing
        _tmpFile = [[NSFileHandle fileHandleForWritingAtPath:_tmpFilePath] retain];
        
        //make sure the temp file was created properly
        if( _tmpFile == nil ){
            NSException *exception = [NSException exceptionWithName:@"DSExportException"
                                                             reason:@"Failed to create the temp file"  userInfo:nil];
            @throw exception; 
        }
        
        
        //write data to the temp file
        [self exportDataToTempFile];
        
        // Close _tmpFile for writing
        [_tmpFile closeFile];
        
        //open the temp file for reading
        [_tmpFile release];
        _tmpFile = [[NSFileHandle fileHandleForReadingAtPath:_tmpFilePath] retain];
        
        //make sure we can read the temp file
        if( _tmpFile == nil ){
            NSException *exception = [NSException exceptionWithName:@"DSExportException"
                                                             reason:@"Failed to reopen the temporary file"  userInfo:nil];
            @throw exception;
        }
        
        if( _tmpFile != nil )
        {
            //delete any outputfile that was there before
            if( [[NSFileManager defaultManager] fileExistsAtPath:_filePath] )
                [[NSFileManager defaultManager] removeFileAtPath:_filePath handler:nil];
            
            //create the output file
            if( ![[NSFileManager defaultManager] createFileAtPath:_filePath contents:[NSData data] attributes:nil] ){
                NSException *exception = [NSException exceptionWithName:@"DSExportException"
                                                                 reason:@"Failed to create output file."  userInfo:nil];
                @throw exception;
            }
            
            //open the output file for writing
            _outputFile = [[NSFileHandle fileHandleForWritingAtPath:_filePath] retain];
            if( _outputFile == nil ){
                NSException *exception = [NSException exceptionWithName:@"DSExportException"
                                                                 reason:@"Failed to open the output file for writing."  userInfo:nil];
                @throw exception;
            }
            
            if (_outputFile != nil){
                //Write the header line
                [self writeHeaderToOutputFile];
                //copy data from temp file to output file
                [self copyTempFileToOutputFile];
                
                //close these files
                [_outputFile closeFile];
                [_tmpFile closeFile];
                
            }
        }
        
    }
    
    @catch (NSException *exception) 
    {
        NSLog(@"%@: %@", [exception name], [exception  reason]);
        
        // If an exception is caught we close the output file and then delete it
        if( _outputFile != nil )
        {
            [_outputFile closeFile];
            _outputFile = nil;
        }
        
        //delete the outputfile if it exists
        if( [[NSFileManager defaultManager] fileExistsAtPath:_filePath] )
            [[NSFileManager defaultManager] removeFileAtPath:_filePath handler:nil];
        
        
    }
    
    @finally {
        
        //delete the tempFile if it exists
        //if( [[NSFileManager defaultManager] fileExistsAtPath:_tmpFilePath] )
        // [[NSFileManager defaultManager] removeFileAtPath:_tmpFilePath handler:nil];
        if( [[NSFileManager defaultManager] directoryContentsAtPath:_tmpDir] != nil)
            [[NSFileManager defaultManager] removeFileAtPath:_tmpDir handler:nil];
    }
}

- (NSString*)filePath
{
	return _filePath;
}

- (NSString*)nodePath
{
	return _nodePath;
}

- (NSString*)remoteNetAddress
{
	return _remoteNetAddress;
}

- (NSString*)userName
{
	return _userName;
}

- (NSString*)userPass
{
	return _userPass;
}

- (NSArray*)recordsToExport
{
	return _recordsToExport;
}

@end

@implementation ExportController (PrivateMethods)

// ----------------------------------------------------------------------------
// Parses arguments
// ----------------------------------------------------------------------------

- (void)parseArgs:(const char**)argv numArgs:(int)argc
{
	int argIndex = 1;
    
	if( argc < kMinNumArgs )
	{
		_showUsage = YES;
		return;
	}
	_filePath = [[NSString alloc] initWithUTF8String:argv[argIndex++]];		//file path is first argument
	_nodePath = [[NSString alloc] initWithUTF8String:argv[argIndex++]];		//node path is 2nd argument
    _recordType = [[NSString alloc] initWithUTF8String:argv[argIndex++]];	//record type is 3rd argument
    
	
	BOOL isProxy = NO;
	for( int i = argIndex; !_showUsage && ( i < argc ); i++ )
	{
		if ( strcmp( argv[i], kProxyAddressArg) == 0)		// proxy address
		{
			isProxy = YES;
            
            if ( ++i >= argc)
				_showUsage = YES;
			else
				_remoteNetAddress = [[NSString alloc] initWithUTF8String:argv[i]];
		}
		else if ( strcmp( argv[i], kProxyUserArg) == 0)		// proxy user
		{
			isProxy = YES;
            
            if ( ++i >= argc)
				_showUsage = YES;
			else
				_userName = [[NSString alloc] initWithUTF8String:argv[i]];
		}
		else if ( strcmp( argv[i], kProxyPassArg) == 0)		// proxy password
		{
			isProxy = YES;
            
            if ( ++i >= argc)
				_showUsage = YES;
			else {
                _userPass = [[NSString alloc] initWithUTF8String:argv[i]];
                memset((void*)argv[i],0,strlen(argv[i])); // erase password to reduce exposure to sniffing
            }
            
		}
		else if ( strcmp( argv[i], kRecordNamesArg) == 0)	// record names
		{
            if ( ++i >= argc)
				_showUsage = YES;
            
			else
			{
				_exportAll = NO;
                NSString* recordNamesString = [NSString stringWithUTF8String:argv[i]];
				_recordsToExport = [[recordNamesString componentsSeparatedByString:@kRecordNamesSeparator] retain];
			}
		}
        else if (strcmp( argv[i],kExcludeAttribute) ==0)
        {
            if ( ++i >= argc)
				_showUsage = YES;
			else{
                NSString* attributeNamesString = [NSString stringWithUTF8String:argv[i]];
				[_attributesToBeExcluded addObject:attributeNamesString];
            }
            
        }
        else if (strcmp( argv[i],kExportNativeAttributes) ==0)
        {
            _returnAttributes = @kDSAttributesAll;
            
        }
	}
	
	if( isProxy )   //if any of the proxy options were supplied, make sure they all were
	{
		if( ( _remoteNetAddress == nil ) || ( _userName == nil )  ){
            _showUsage = YES;
        } else {
            if ( ( _userPass == nil )){
                // If the user specified remote address and username we prompt him or her for password
                _userPass = [[self getSecretString: [NSString stringWithFormat:@"%@'s password:", _userName]] retain];
            }
        }
			
	}
        
        if(  _recordType == nil  ){
            _showUsage = YES;
        }
        if (_recordsToExport == nil) {
            _exportAll = YES;
        }
}

// ----------------------------------------------------------------------------
// Usage statement
// ----------------------------------------------------------------------------
- (void)showUsage
{
    fprintf( stdout,"\ndsexport - A tool for exporting records from Open Directory\n\n"
             "Usage:  dsexport filePath DSNodePath recordType [options] [DS proxy]\n"
             "flags:\n"
             "   --N exports Native attributes, by default only standard attributes\n"
             "       are exported.\n"
             "options: (comma delimited lists)\n"
             "    -r <recordNames>\n"
             "    -e <attributesToBeExcluded>\n"
             "\n"
             "proxy:   (when using DS proxy password prompt provided if none specified)\n"
             "    -a <proxyAddress>\n"
             "    -u <proxyUsername>\n"
             "    -p <proxyPassword>\n"
             "\n");
}

// ----------------------------------------------------------------------------
// Copies temp file to output file
// ----------------------------------------------------------------------------
- (void)copyTempFileToOutputFile{
    
    [_outputFile writeData:[_tmpFile readDataToEndOfFile]];
    
}

// ----------------------------------------------------------------------------
// Writes string to output file
// ----------------------------------------------------------------------------
- (void)writeStringToOutputFile:(NSString*)theString
{
	[_outputFile writeData:[theString dataUsingEncoding:NSUTF8StringEncoding]];
}

// ----------------------------------------------------------------------------
// Writes string to temp file
// ----------------------------------------------------------------------------
- (void)writeStringToTempFile:(NSString*)theString
{
	[_tmpFile writeData:[theString dataUsingEncoding:NSUTF8StringEncoding]];
}

// ----------------------------------------------------------------------------
// Write header to output file 
// ----------------------------------------------------------------------------
- (void)writeHeaderToOutputFile{
    
    //write the character constants part of the header
	[self writeStringToOutputFile:@kHeaderDelimiterChars];
    
	//write the record type and number of attribute columns
	[self writeStringToOutputFile:[NSString stringWithFormat:@" %@ %d ", _recordType, [_attributesForHeader count]]];
	
	//now write the column headers
	[self writeStringToOutputFile:[_attributesForHeader componentsJoinedByString:@" "]];
	[self writeStringToOutputFile:@"\n"];
}

// ----------------------------------------------------------------------------
// Export node data to the temp file
// ----------------------------------------------------------------------------
- (void)exportDataToTempFile
{
	ODQuery         *search;
	NSError			*error;
    
    search  = [ODQuery queryWithNode: _node
                      forRecordTypes: _recordType
                           attribute: @kDSNAttrRecordName
                           matchType: kODMatchInsensitiveEqualTo
                         queryValues: _recordsToExport
                    returnAttributes: _returnAttributes
                      maximumResults: 0
                               error: nil ];
    
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    NSArray* recordsAttributesValues = nil;
    recordsAttributesValues = [search resultsAllowingPartial: NO error: nil];
    
    NSLog( @"Exporting %d records", [recordsAttributesValues count] );
    
    // In case Open Directory framework returns an error
    if (recordsAttributesValues == nil) {
        NSException *exception = [NSException exceptionWithName:@"DSExportException"
                                                         reason:[NSString stringWithFormat:@"Directory Services returned following error:%@",[error localizedDescription]]  userInfo:[error userInfo]];
        @throw exception;
    }
    
    // Here we have a mutable array in which we will build up the Header line by determining all the Attribute types and adding them to this array
    NSMutableArray* mutableHeaderAttributes = [[NSMutableArray alloc] init];
    // we store the attribute values in this array
    NSMutableArray* currentAttributes = [[NSMutableArray alloc] init];
    
    // For every record returned by the search we check to see if there are new attribute types and get the values for every attribute type
    ODRecord *record = nil;
    for( NSEnumerator* recordEnum = [recordsAttributesValues objectEnumerator]; record = [recordEnum nextObject]; record != nil )
    {
        NSDictionary* attrsValues = [record recordDetailsForAttributes: nil error: &error];
        
        // Are we reading the first record or not
        if ([mutableHeaderAttributes count] == 0) {
            // If so we allocate and create the array by getting all the keys from the attrValues dictionary
            [mutableHeaderAttributes addObjectsFromArray:[attrsValues allKeys]];
            // We filter out the attributes we don't want to export
            [mutableHeaderAttributes removeObjectsInArray:_attributesToBeExcluded];
        } else {
            // If not we search for new attribute types that aren't already in the attribute array
            // We get all the keys (attribute types) for this record
            [currentAttributes addObjectsFromArray:[attrsValues allKeys]];
            // We filter out the attributes we don't want to export
            [currentAttributes removeObjectsInArray:_attributesToBeExcluded];
            // We subtract all the attribute types that already exist in the mutableHeaderAttribute array
            [currentAttributes removeObjectsInArray:mutableHeaderAttributes];
            // We then add these new attribute types to the mutableHeaderAttribute array
            [mutableHeaderAttributes addObjectsFromArray:currentAttributes];
        }
        
        
        // Here we write out the values that we just found to the temp file
        NSMutableArray* mutableAttrValues = [NSMutableArray array];
        for( NSEnumerator* headerEnum = [mutableHeaderAttributes objectEnumerator]; NSString* headerAttr = [headerEnum nextObject]; )
        {
            NSString* attrValuesString = @"";
            NSArray* attrValues = [attrsValues objectForKey:headerAttr];
            
            if( attrValues != nil )
            {
                NSMutableArray* escapedAttrValues = [NSMutableArray array];
                for( NSEnumerator* valueEnum = [attrValues objectEnumerator]; NSString* value = [valueEnum nextObject]; )
                {
                    if( [value isKindOfClass:[NSString class]] )
                    {
                        //escape any commas
                        NSMutableString* mutableValue = [NSMutableString stringWithString:value];
                        [mutableValue replaceOccurrencesOfString:@"," withString:@"\\," options:0 range:NSMakeRange( 0, [mutableValue length] )];
                        [escapedAttrValues addObject:mutableValue];
                    }
                    else
                    {
                        if ([value isKindOfClass:[NSData class]]) {
                            NSLog( @"Skipping binary data attribute `%@` in record `%@`. ", headerAttr,  [record recordName] );
                        } else {
                            NSLog( @"Bad value in record %@ - %@ - %@", [record recordName], headerAttr, value );
                        }                    }
                }
                attrValuesString = [escapedAttrValues componentsJoinedByString:@","];
            }
            //escape any colons
            NSMutableString* mutableAttrValuesString = [NSMutableString stringWithString:attrValuesString];
            [mutableAttrValuesString replaceOccurrencesOfString:@":" withString:@"\\:" options:0 range:NSMakeRange( 0, [mutableAttrValuesString length] )];
            [mutableAttrValues addObject:mutableAttrValuesString];
        }
        
        NSString* recordString = [mutableAttrValues componentsJoinedByString:@":"];
        NSMutableString* mutableRecordString = [NSMutableString stringWithString:recordString];
        //escape any newlines
        [mutableRecordString replaceOccurrencesOfString:@"\n" withString:@"\\\n" options:0 range:NSMakeRange( 0, [mutableRecordString length] )];
        [mutableRecordString appendString:@"\n"];
        
        [self writeStringToTempFile:mutableRecordString];
    }
    _attributesForHeader = mutableHeaderAttributes;
    [pool release];
    
}
// ----------------------------------------------------------------------------
// Prompts user for password
// ----------------------------------------------------------------------------
- (NSString*)getSecretString:(NSString*)prompt
{
#ifdef _OS_VERSION_NEXTSTEP_
	struct sgttyb iobasic;
#else
	struct termios term;
#endif
	char ch, *buf;
	FILE *fp = NULL, *outfp;
	long omask;
	int echo, len, buflen;
	char* out;
	const char* promptCStr = NULL;
	NSString* returnString = nil;
	
	if( prompt == nil )
		prompt = @"";
    
	promptCStr = [prompt UTF8String];
    
	/*
	 * read and write to /dev/tty if possible and there is a prompt string;
	 * else read from stdin and write to stderr.
	 */
	if (prompt != NULL && promptCStr[0] != '\0')
	{
		fp = fopen("/dev/tty", "w+");
		outfp = fp;
	}
	if (fp == NULL)
	{
		outfp = stderr;
		fp = stdin;
	}
    
	/*
	 * note - blocking signals isn't necessarily the
	 * right thing, but we leave it for now.
	 */
	omask = sigblock(sigmask(SIGINT) | sigmask(SIGTSTP));
    
#ifdef _OS_VERSION_NEXTSTEP_
	ioctl(fileno(fp), TIOCGETP, &iobasic);
	echo = iobasic.sg_flags & ECHO;
#else
	tcgetattr(fileno(fp), &term);
	echo = term.c_lflag & ECHO;
#endif
    
	if (echo != 0)
	{
#ifdef _OS_VERSION_NEXTSTEP_
		iobasic.sg_flags &= ~ECHO;
		ioctl(fileno(fp), TIOCSETN, &iobasic);
#else
		term.c_lflag &= ~ECHO;
		tcsetattr(fileno(fp), (TCSAFLUSH | TCSASOFT), &term);
#endif
	}
    
	fputs(promptCStr, outfp);
	rewind(outfp);
    
	len = 0;
	buflen = SizeQuantum;
	buf = (char*)malloc(buflen);
    
	ch = getc(fp);
	while ((ch != EOF) && (ch != '\n'))
	{
		if (len >= buflen)
		{
			buflen += SizeQuantum;
			buf = (char*)realloc(buf, buflen);
		}
        
		buf[len++] = ch;
		ch = getc(fp);
	}
	
	write(fileno(outfp), "\n", 1);
    
	if (echo)
	{
#ifdef _OS_VERSION_NEXTSTEP_
		iobasic.sg_flags |= ECHO;
		ioctl(fileno(fp), TIOCSETN, &iobasic);
#else
		term.c_lflag |= ECHO;
		tcsetattr(fileno(fp), (TCSAFLUSH | TCSASOFT), &term);
#endif
	}
    
	sigsetmask(omask);
	if (fp != stdin) fclose(fp);
    
	out = (char*)calloc(len+1,sizeof(char));
	strncpy(out,buf,len);
	free(buf);
	returnString = [NSString stringWithUTF8String:out];
	free(out);
	
	return returnString;
}

// ----------------------------------------------------------------------------
// creates secure temp directory
// ----------------------------------------------------------------------------
BOOL create_tempdir(char *temp_dirname_pattern)
{
    mode_t old_mode;
    
    temp_dirname_pattern = mkdtemp(temp_dirname_pattern);
    
    return (temp_dirname_pattern != NULL);
}


@end