Need create VPN connect L2TP on osx - objective-c

Need create VPN connection L2TP on osx without Shared Secret
NSString *server = #"serverIP";
NSString *username = #"user";
NSString *password = #"pass";
const void* passwordData = [[password dataUsingEncoding:NSUTF8StringEncoding] bytes];
[vpnManager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Load config failed [%#]", error.localizedDescription);
return;
}
NEVPNProtocol *p = (NEVPNProtocol *)vpnManager.protocolConfiguration;
if (!p) {
p = [[NEVPNProtocol alloc] init];
}
p.username = username;
p.serverAddress = server;
p.passwordReference = (__bridge NSData * _Nullable)(passwordData);
p.disconnectOnSleep = NO;
vpnManager.protocolConfiguration = p;
vpnManager.localizedDescription = #"L2TPOverIPSec";
vpnManager.enabled = YES;
[vpnManager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Save config failed [%#]", error.localizedDescription);
}
}];
}];
NEVPNConnection *connect = [vpnManager connection];
NSError *error1;
if ([connect startVPNTunnelAndReturnError: &error1]) {
NSLog(#"connect");
} else {
NSLog(#"not connect");
}
after building i get this error Missing protocol or protocol has invalid type, and other 2 protocols use IKE tunnel, what can u advice to me? other option is run terminal from code and add this string networksetup -connectpppoeservice VPNConnect but i dont know if it possible

I know this is the old question, but I'm here to note, that Network Extension framework (where NEVPNProtocol is from) can't go on with OSI level 2 protocols, which is L2TP. (https://forums.developer.apple.com/thread/29909)
It seems now (starting from iOS 8 and os x 10.10) the recommended way on Apple devices is to use built-in protocols, or implement your own but on L3 / L4: https://developer.apple.com/documentation/networkextension
(And so there is no public API for using L2TP)

No, you need use SCNetwork and Helper Tool (to get root access) With this link you will be able to create L2TP Protocol and connect with it.
This works even on the latest version MacOS 11 "Big Sure"

Related

Programmatically creating an ad-hoc network in Big Sur

Before Mac OS Big Sur, one could create an ad-hoc network by calling the startIBSSModeWithSSID:security:channel:password:error: function of a CWInterface obtained from a CWWifiClient. It seems that after an update to Big Sur, the above function is deprecated and throws a kCWOperationNotPermittedErr (-3930) error every time.
I tried launching the application from root, and it still refused to create an ad-hoc network. Meanwhile, using the "Create Network" option in the WiFi dropdown menu works with an administrator password.
A previous answer on this site I have come across is outdated and the code does not work anymore. There is a post on the Apple Developer forums created 5 months ago but it remains unanswered, with the "solution" being to file a tech support incident.
This is the code I am using:
#import <Foundation/Foundation.h>
#import <CoreWLAN/CoreWLAN.h>
#import <SecurityFoundation/SFAuthorization.h>
#import <objc/message.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
bool success = 0;
CWWiFiClient* wifiClient = [CWWiFiClient sharedWiFiClient];
CWInterface* interface = [wifiClient interface];
NSString* namestr = #"very_creative_ssid";
NSData* name = [namestr dataUsingEncoding:NSUTF8StringEncoding];
NSString* pass = #"very_cruel_framework"; // not used
NSError* err = nil;
success = [interface startIBSSModeWithSSID:name
security:kCWIBSSModeSecurityNone
channel:11
password:nil
error:&err];
if (!success) {
NSLog(#"%#", err);
return 1;
}
[NSRunLoop.currentRunLoop run];
}
return 0;
}
Is there a way to programmatically create an ad-hoc network in Big Sur without throwing an error?
Edit: Here is the console output (1 line):
2022-01-12 05:25:03.723 cwlantest[15305:448617] Error Domain=com.apple.coreWLAN.error Code=-3930 "(null)"
I'm going to put this as an answer, if anyone finds anything new or Apple adds this feature in the future, I'll be very happy to be wrong.
TLDR: Not anymore!
Since Apple removed the "Create network..." option from the wifi menubar, the only way to create an ad-hoc network is through Network Sharing. I followed https://www.makeuseof.com/how-to-create-a-secure-ad-hoc-network-in-macos/ under the How to Create a Secure Ad Hoc Network section to make a network:
sudo networksetup -createnetworkservice AdHoc lo0
sudo networksetup -setmanual AdHoc 192.168.1.88 255.255.255.255
And in System Preferences, share your network connection from AdHoc over WiFi.
With that on, I checked the CWInterface.interfaceMode() and it was in HostAP mode. Pure speculation, but I think IBSS was removed completely, it's marked as Deprecated in the developer documentation. -3930 is kCWOperationNotPermittedErr, so I'm not 100% sure that's accurate, but it's possible.
There are private interfaces to set HostAP mode in CoreWLAN:
https://github.com/onmyway133/Runtime-Headers/blob/master/macOS/10.13/CoreWLAN.framework/CWInterface.h https://medium.com/swlh/calling-ios-and-macos-hidden-api-in-style-1a924f244ad1 https://gist.github.com/wolever/4418079
After replacing objc_msgsend with NSInvocation in the last link since objc_msgsend seems to have been removed:
#import <CoreWLAN/CoreWLAN.h>
#import <objc/message.h>
int main(int argc, char* argv[]) {
#autoreleasepool {
int ch;
NSString *ssid = nil, *password = nil;
while((ch = getopt(argc, argv, "s:p:h")) != -1) {
switch(ch) {
case 's':
ssid = [NSString stringWithUTF8String:optarg];
break;
case 'p':
password = [NSString stringWithUTF8String:optarg];
break;
case '?':
case 'h':
default:
printf("USAGE: %s [-s ssid] [-p password] [-h] command\n", argv[0]);
printf("\nOPTIONS:\n");
printf(" -s ssid SSID\n");
printf(" -p password WEP password\n");
printf(" -h Print help\n");
printf("\nCOMMAND:\n");
printf(" status Print interface mode\n");
printf(" start Start Host AP mode\n");
printf(" stop Stop Host AP mode\n");
return 0;
}
}
NSString *command = nil;
if(argv[optind]) {
command = [NSString stringWithUTF8String:argv[optind]];
}
CWInterface *iface = [[CWWiFiClient sharedWiFiClient] interface];
if(!command || [command isEqualToString:#"status"]) {
NSString *mode = nil;
switch(iface.interfaceMode) {
case kCWInterfaceModeStation:
mode = #"Station";
break;
case kCWInterfaceModeIBSS:
mode = #"IBSS";
break;
case kCWInterfaceModeHostAP:
mode = #"HostAP";
break;
case kCWInterfaceModeNone:
default:
mode = #"None";
}
printf("%s\n", [mode UTF8String]);
} else if([command isEqualToString:#"stop"]) {
// Stop Host AP mode
if(getuid() != 0) {
printf("this may need root (trying anyway)...\n");
}
SEL selector = #selector(stopHostAPMode);
NSMethodSignature *signature = [iface methodSignatureForSelector: selector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = iface;
invocation.selector = selector;
[invocation invoke];
printf("Done?");
//objc_msgSend(iface, #selector(stopHostAPMode));
} else if([command isEqualToString:#"start"]) {
if(!ssid) {
printf("error: an ssid must be specified\n");
return 1;
}
// known security types:
// 2: no securiry
// 16: wep
// Note: values [-127..127] have been tried, and all but these return errors.
unsigned long long securityType = 2;
if(password) {
if([password length] < 10) {
printf("error: password too short (must be >= 10 characters)\n");
return 1;
}
securityType = 16;
}
NSSet *chans = [iface supportedWLANChannels];
//printf("chan count: %lu\n", [chans count]);
NSEnumerator *enumerator = [chans objectEnumerator];
CWChannel *channel;
while ((channel = [enumerator nextObject])) {
//printf("channel: %lu\n", [channel channelNumber]);
if ([channel channelNumber] == 11)
break;
}
printf("Found Channel: %d\n", channel.channelNumber);
// Start Host AP mode
NSError *error = nil;
NSError **errorptr = &error;
SEL selector = #selector(startHostAPModeWithSSID:securityType:channel:password:error:);
NSMethodSignature *signature = [iface methodSignatureForSelector: selector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = iface;
invocation.selector = selector;
NSString * ssidstr = #"Test";
NSString * pass = #"barbarbarr";
NSData * ssidArg = [ssidstr dataUsingEncoding:NSUTF8StringEncoding];
[invocation setArgument: &ssidArg atIndex:2];
[invocation setArgument: &securityType atIndex:3];
[invocation setArgument: &channel atIndex:4];
[invocation setArgument: &pass atIndex:5];
[invocation setArgument: &errorptr atIndex:6];
[invocation invoke];
BOOL success;
[invocation getReturnValue:&success];
if (!success) {
printf("startHostAPModeWithSSID error: %s\n", [(*errorptr).localizedDescription UTF8String]);
return 1;
} else {
printf("Success?\n");
return 0;
}
}
return 0;
}
}
./hostap stop does successfully kick me out of hostap mode started from network sharing, but ./hostap start fails with -3903 kCWNotSupportedErr.
Also, using startHostAPMode: without other settings does succeed, but the wifi menu shows WiFi: Internet Sharing, so I think this is a private api meant specifically for network sharing and will likely need other configuration to get working. You could potentially continue down that road, but it didn't look very promising. The best bet is to just use network sharing or potentially look into scripting System Preferences with AppleScript if you really want a scripted approach.

How can programmatically we know file sharing is enabled or not in Mac OS X using objective c?

I have spent 2 days to find the status of file sharing programmatically using Objective C. We can enable the file sharing using system preferences using SharingPreferences. File Sharing Preference pane store at /System/Library/PreferencePanes location. Can we retrieve file sharing status information from PreferencePanes classes?. if then how can we retrieve?.
Can we get file sharing status from system stored plist file, in which plist file status maintains?
OR Any other API of Objective C through we can get the file sharing status?
I don't think there is a direct api to get this status but it can be obtained by other means.
Check if port 548 and 445 are open. 548 is used for AFP which is not being used currently but when you turn on File Sharing it turns on AFP also. 445 is the new SMB sharing port.
Using NSNetServiceBrowser you can check the list of computers advertising _afpovertcp._tcp. or _smb._tcp. services and see if your computer is among them. You can use the IP or computer name to determine this.
You can do that with the ServiceManagement.framework by checking the status of the appropriate launchd daemons.
#import ServiceManagement;
NSArray *allJobs = (NSArray *)CFBridgingRelease(SMCopyAllJobDictionaries(kSMDomainSystemLaunchd));
NSArray *labels = [allJobs valueForKey:#"Label"];
BOOL AFPSharingIsEnabled = [labels containsObject:#"com.apple.AppleFileServer"];
BOOL SMBSharingIsEnabled = [labels containsObject:#"com.apple.smbd"];
NSLog(#"AFPSharingIsEnabled: %d - SMBSharingIsEnabled: %d", AFPSharingIsEnabled, SMBSharingIsEnabled);
SMCopyAllJobDictionaries passing kSMDomainSystemLaunchd is similar to sudo launchctl list in the shell.
Although SMCopyAllJobDictionaries is deprecated, there is no replacement and the API works in El Capitan.
My quick and dirty implementation of Pradeeps suggestion above.
Basically I want to determine quickly (in 1 second or less) which
to attempt to mount first, afp or smb.
Note that these objects/classes are specific to my implementation:
// my specific server implementation details
MboxObject *mbox = [RemoteFileManager mboxForHostString: [aNetService name]];
task.targetServerName
NSString *mountedBasePath
static BOOL hasAfp;
static BOOL hasSmb;
// delegate callback
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
{
MboxObject *mbox = [RemoteFileManager mboxForHostString: [aNetService name]];
if ( mbox && netServiceMboxName && [[mbox name] isEqualToString:netServiceMboxName] )
{
if ( [[aNetService type] rangeOfString:#"_afpovertcp"].length )
hasAfp = YES;
if ( [[aNetService type] rangeOfString:#"_smb"].length )
hasSmb = YES;
}
}
kicking off the services...
/////////////////////////// AFP or SMB? //////////////////////////////////
// quickly determine if afp or smb is enabled
if ( !mountedBasePath && task.targetServerName )
{
netServiceMboxName = [task.targetServerName copy];
if ( !netServiceBrowserAfp )
{
netServiceBrowserAfp = [[NSNetServiceBrowser alloc] init];
if ( !netServices )
netServices = [[NSMutableArray alloc] init];
[netServiceBrowserAfp setDelegate:self];
netServiceBrowserSmb = [[NSNetServiceBrowser alloc] init];
if ( !netServices )
netServices = [[NSMutableArray alloc] init];
[netServiceBrowserSmb setDelegate:self];
}
hasAfp = hasSmb = NO;
[netServiceBrowserAfp searchForServicesOfType:#"_afpovertcp._tcp." inDomain:#""];
[netServiceBrowserSmb searchForServicesOfType:#"_smb._tcp." inDomain:#""];
for (int i = 0; i < 10; i++ ) // afp is preferred over smb because it is less restrictive (no date/clock restriction)
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
if (hasAfp) break;
}
NSLog( #"hasAfp %d hasSmb %d", hasAfp, hasSmb );
if ( hasSmb && !hasAfp )
tryingAfp = NO; // don't bother trying afp if only smb found
[netServiceBrowserAfp stop];
[netServiceBrowserSmb stop];
/////////////////////////// AFP or SMB? ///////////////////////////////
}

associate arbitrary information with the socket using GCDAyncSocket

I have created a simple client server application with GCDAsyncSocket.
host broadcast its service via bonjour, clients get connected to it, then each client send specific information to host, after completing requests by host, it should now be able to send each request to its corresponding client.
Here when client disconnected (maybe rebooting WiFi) before It gets its completed request and connected again its connected host and port can change.
so for I denitrifying clients I want to store list of connected clients in a dictionary
key : vendorID (specific to each client and can't change)
value : connected host and port (gcdAsyncSocket instance)
how can I send arbitrary information (vendorID) with socket?
it seems GCDAsyncSocket has a property named UserData, I set it with vendorID in the client
but in the host it's NULL.
client:
- (BOOL)connectWithService:(NSNetService *)service {
BOOL _isConnected = NO;
// Copy Service Addresses
NSArray *addresses = [[service addresses] mutableCopy];
if (!self.socket || ![self.socket isConnected]) {
// Initialize Socket
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
//adding deviceID to sockect to identify connected clients in the host
self.socket.userData = #"vendorID2308746238764021";
// Connect
while (!_isConnected && [addresses count]) {
NSData *address = [addresses objectAtIndex:0];
NSError *error = nil;
if ([self.socket connectToAddress:address error:&error]) {
_isConnected = true;
}
else if (error) {
NSLog(#"Unable to connect to address. Error %# with user info %#.", error, [error userInfo]);
}
}
} else {
_isConnected = [self.socket isConnected];
}
return _isConnected;
}
Host:
- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
NSLog(#"Accepted New Socket from %#:%hu", [newSocket connectedHost], [newSocket connectedPort]);
//both userData is null!
NSLog(#"%#",newSocket.userData);
NSLog(#"%#",socket.userData);
//adding new socket to the list of connected clients
//using ip and port of the scoket as key
[self.clients setObject:newSocket forKey:[NSString stringWithFormat:#"%#-%d", [newSocket connectedHost], [newSocket connectedPort]]];
// Notify Delegate
[self.delegate tcpHost:self didHostClientOnSocket:newSocket];
}
does anyone has any idea how should I solve this kind of client-host information exchange?
ClientSide: In my TCPClient class when client connected to the server, the first packet I send is Identity packet:
//server can identify device
- (void)sendIdentity
{
NSData *identifcationData = [[UIDevice deviceID] dataUsingEncoding:NSUTF8StringEncoding];
HAPacket *packet = [[HAPacket alloc] initWithData:identifcationData type:HAPacketTypeIdentity action:HAPacketActionUnknown];
[self sendPacket:packet];
}
Server: gets this packet and identify the Client
In TCPHost: after client connected I store it in a temporary array.
in socket:didReadData I check:
if (tag == TAG_BODY) {
//first packet is always for identifaction. if client has not identified, its id is empty
Client *unknownClient = [[self.temp linq_where:^BOOL(Client *client)
{
return client.ID == nil || [client.ID isEqualToString: #""];
}] linq_firstOrNil];
if (unknownClient && (unknownClient.ID == nil || [unknownClient.ID isEqualToString: #""])) {
NSLog(#"identifying client...");
[self identifyClient:unknownClient withData:data];
}
else
{
// parse the packet as usual
}

How to store blocks in Objective C?

I started writing a simple JSON RPC TCP library in Objective C.
I have a method that invokes a RPC Method:
- (void)invokeMethod:(NSString *)method
withParameters:(id)parameters
requestId:(id)requestId
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure
{
NSAssert(NSClassFromString(#"NSJSONSerialization"), #"NSJSONSerialization not found!");
NSDictionary *requestObject = #{#"jsonrpc": #"2.0",
#"method": method,
#"params": parameters,
#"id": requestId};
NSError *error = nil;
NSData *jsondData = [NSJSONSerialization dataWithJSONObject:requestObject options:0 error:&error];
if (error){
return failure(error);
}
[self->callbacks setObject:#{#"success": success ? [success copy] : [NSNull null],
#"failure": failure ? [failure copy] : [NSNull null]}
forKey:requestId];
NSString *str = [[NSString alloc] initWithData:jsondData encoding:NSUTF8StringEncoding];
NSLog(#"Sending: %#", str);
[self.socket writeData:jsondData withTimeout:-1 tag:1];
}
The class basically represents a TCP connection, when calling the above method, the JSON data is sent with an id over TCP to the server which either returns a success or a failure:
- (void) socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
NSError *error = nil;
[self.socket readDataWithTimeout:-1 tag:2];
// … rpc response parsing code here, removed for simplicity …
// detect if error or success
NSDictionary *cbs = [self->callbacks objectForKey:JSONRPCObjectId];
void(^success)(id resultObject) = [cbs objectForKey:#"success"];
success ? success(JSONRPCObjectResult) : nil;
return;
}
Now, I am unsure how to keep track of the success and failure blocks, currently I am storing them in an NSMutableDict, using the requestId as key. Is it fine to do this or is there a better approach that I should use?
Blocks in objective-c are objects and you can treat the same way as other object, so storing them in NSDictionarys, NSArrays etc is perfectly fine. The only catch is that blocks when initially created exist in the same memory scope as local variable do and so they are no longer valid when the method that the block is defined in returns, just like all other local variables so you have to copy them first, just copy them and put the copy in the collection. There is a block copy function but you can just send them a copy message [myBlock copy];
Quick answer, seeing as you don't have anything workable yet...
This is more than you asked for; so, you'll probably have to pair it down to meet your specific need. Basically, it stores as many blocks as you specify at contiguous memory addresses. Paste this into a header file or somewhere global to the method from which you will call these:
typedef const typeof(id(^)(void)) retained_object;
static id (^retainable_object)(id(^)(void)) = ^ id (id(^object)(void)) {
return ^{
return object();
};
};
typeof (retained_object) *(^(^retain_object)(id (^__strong)(void)))(void) = ^ (id(^retainable_object)(void)) {
typeof(retained_object) * object_address;
object_address = &retainable_object;
typeof(retained_object) * persistent_object = (typeof(retained_object) *)CFBridgingRetain(retainable_object);
return ^ typeof(retained_object) * {
return persistent_object;
};
};
static void (^(^iterator)(const unsigned long))(id(^)(void)) = ^ (const unsigned long object_count) {
id const * retained_objects_ref[object_count];
return ^ (id const * retained_objects_t[]) {
return ^ (id(^object)(void)) {
object();
int index = 0UL;
int * index_t = &index;
for (; (*index_t) < object_count; ((*index_t) = (*index_t) + 1UL)) printf("retained_object: %p\n", (*((id * const)retained_objects_t + (object_count - index)) = retain_object(retainable_object(object()))));
};
}(retained_objects_ref);
};
From some method, add:
iterator(1000)(^ id { return (^{ printf("stored block\n"); }); });
This should store 1,000 blocks at as many unique memory addresses.

Get a list of unmountable drives using Cocoa

I would like to obtain a list of drives that are unmountable/ejectable using Cocoa/Objective-C under OS X.
I was hoping that NSWorkspace getFileSystemInfoForPath::::: would help me:
NSArray* listOfMedia = [[NSWorkspace sharedWorkspace] mountedLocalVolumePaths];
NSLog(#"%#", listOfMedia);
for (NSString* volumePath in listOfMedia)
{
BOOL isRemovable = NO;
BOOL isWritable = NO;
BOOL isUnmountable = NO;
NSString* description = [NSString string];
NSString* type = [NSString string];
BOOL result = [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:volumePath
isRemovable:&isRemovable
isWritable:&isWritable
isUnmountable:&isUnmountable
description:&description
type:&type];
NSLog(#"Result:%i Volume: %#, Removable:%i, W:%i, Unmountable:%i, Desc:%#, type:%#", result, volumePath, isRemovable, isWritable, isUnmountable, description, type);
}
Output:
...
Result:1 Volume: /Volumes/LR Photos, Removable:0, W:1, Unmountable:0, Desc:hfs, type:hfs
...
"LR Photos" is an external drive (connected via Thunderbolt) that should be removable and/or unmountable (or, at least I think it should be). :)
Should I be going about this a different way?
Thanks in advance!
You can use diskArbitration framework.
#import <DiskArbitration/DiskArbitration.h>
+(NSMutableArray *)getListOfEjectableMedia
{
NSArray *mountedRemovableMedia = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:nil options:NSVolumeEnumerationSkipHiddenVolumes];
NSMutableArray *result = [NSMutableArray array];
for(NSURL *volURL in mountedRemovableMedia)
{
int err = 0;
DADiskRef disk;
DASessionRef session;
CFDictionaryRef descDict;
session = DASessionCreate(NULL);
if (session == NULL) {
err = EINVAL;
}
if (err == 0) {
disk = DADiskCreateFromVolumePath(NULL,session,(CFURLRef)volURL);
if (session == NULL) {
err = EINVAL;
}
}
if (err == 0) {
descDict = DADiskCopyDescription(disk);
if (descDict == NULL) {
err = EINVAL;
}
}
if (err == 0) {
CFTypeRef mediaEjectableKey = CFDictionaryGetValue(descDict,kDADiskDescriptionMediaEjectableKey);
CFTypeRef deviceProtocolName = CFDictionaryGetValue(descDict,kDADiskDescriptionDeviceProtocolKey);
if (mediaEjectableKey != NULL)
{
BOOL op = CFEqual(mediaEjectableKey, CFSTR("0")) || CFEqual(deviceProtocolName, CFSTR("USB"));
if (op) {
[result addObject:volURL];
}
}
}
if (descDict != NULL) {
CFRelease(descDict);
}
if (disk != NULL) {
CFRelease(disk);
}
if (session != NULL) {
CFRelease(session);
}
}
return result;
}
Unfortunately getFileSystemInfoForPath: is not really the right way to do this. What removable means is that the volume is on removable media such as a CD or DVD. In practice unmountable seems to give the same results as removable. See for example, this post on results using getFileSystemInfoForPath. So unless you want to simply know if a volume is on removable media, you'll need to use another technique.
What you really want to check is the connection bus type of the volume. Firewire, USB, Thunderbolt, etc. are unmountable in the sense you mean. You can see this information in Disk Utility if you select the volume and push the "Info" button under "Connection Bus". Getting this information programmatically is much harder and as far as I can tell is only possible using the IOKit. Details are in Apple's documentation on Accessing Hardware from Applications.
you can use command line version of Disk Utility app that is "diskutil", run it with parameter "list" and pipe output and get it in your program ( don't need to use cocoa ).