Finding DNS server settings programmatically on Mac OS X - macos

I have some cross platform DNS client code that I use for doing end to end SMTP and on windows I can find the current DNS server ip addresses by looking in the registry. On the Mac I can probably use the SystemConfiguration framework as mentioned in the first answer, however the exact method of doing so is not immediately obvious.
For instance SCDynamicStoreCopyDHCPInfo returns some of the dynamic DHCP related data but not the DNS server addresses.

I know its very late to answer this question but may be helpful for the others.
This Code will help out for this task ..
SCPreferencesRef prefsDNS = SCPreferencesCreate(NULL, CFSTR("DNSSETTING"), NULL);
CFArrayRef services = SCNetworkServiceCopyAll(prefsDNS);
long servicesCount = CFArrayGetCount(services);
for (long i = 0; i < servicesCount; i++) {
const SCNetworkServiceRef service = (const SCNetworkServiceRef)CFArrayGetValueAtIndex(services, i);
CFStringRef interfaceServiceID = SCNetworkServiceGetServiceID(service);
CFStringRef primaryservicepath = CFStringCreateWithFormat(NULL,NULL,CFSTR("State:/Network/Service/%#/DNS"),interfaceServiceID);
SCDynamicStoreRef dynRef = SCDynamicStoreCreate(kCFAllocatorSystemDefault, CFSTR("DNSSETTING"), NULL, NULL);
CFPropertyListRef propList = SCDynamicStoreCopyValue(dynRef,primaryservicepath);
if (propList) {
CFDictionaryRef dict = (CFDictionaryRef)propList;
CFArrayRef addresses = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("ServerAddresses"));
long addressesCount = CFArrayGetCount(addresses);
for (long j = 0; j < addressesCount; j++) {
CFStringRef address = (CFStringRef)CFArrayGetValueAtIndex(addresses, j);
// Print address
CFShow(address);
}
CFRelease(propList);
}
CFRelease(dynRef);
CFRelease(primaryservicepath);
}
CFRelease(services);
CFRelease(prefsDNS);

I know it's been a long time since you needed this, but there is nothing worse than a old unsolved answer. You can't access them from "/etc/resolv.conf" because of permission issues. After much searching, and a little luck I discovered you can get it via res_ninit() function.
// Get native iOS System Resolvers
res_ninit(&_res);
res_state res = &_res;
for (int i = 0; i < res->nscount; i++) {
sa_family_t family = res->nsaddr_list[i].sin_family;
int port = ntohs(res->nsaddr_list[i].sin_port);
if (family == AF_INET) { // IPV4 address
char str[INET_ADDRSTRLEN]; // String representation of address
inet_ntop(AF_INET, & (res->nsaddr_list[i].sin_addr.s_addr), str, INET_ADDRSTRLEN);
} else if (family == AF_INET6) { // IPV6 address
char str[INET6_ADDRSTRLEN]; // String representation of address
inet_ntop(AF_INET6, &(res->nsaddr_list [i].sin_addr.s_addr), str, INET6_ADDRSTRLEN);
}
}
res_ndestroy(res);

You can use the SystemConfiguration framework. It's in C.
Update: apparently the rest of the web is harder to use than I thought. Search for the key "State:/Network/Service/ServiceID/DNS" where ServiceID is the ID of the service.

They are also available from
/etc/resolv.conf

You could read from /etc/resolv.conf.

Related

Is there something special about Sending to Outlook 2013 with MAPI that I'm missing?

I am working on a Simple MAPI interface with Visual Studio 2008 to send email and attachments from an application. Works great with Thunderbird and Outlook 6 but Outlook 2013 is giving me all sorts of grief.
There are two key issues:
1) The email goes to Outlook's outbox but when it sends it bounces back (or appears to as I think this is internal) with the message "None of your e-mail accounts could send to this recipient."
If I compose a new message in Outlook with that exact email address it works fine. I've tried two different outbound SMTP accounts that I know both work, and work manually, but with IMAP they are stuck. The IMAP code I used didn't have originator data and I've hacked that in to try to make it work (the code I'm posting is a bit raw because I'm still trying to figure this out)
2) Outlook will display "A program is trying to send an e-mail message on your behalf".
I've set the the Outlook Trust Centre access to "Never warn about activity" as described in loads of online help but the problem persists. I can't help but think these might be related?
I'm wondering if Outlook 2013 is needing more data than I'm providing or if I'm making some cockeyed assumption.
Note: these test are on three different machines - Thunderbird on my main Win 10 development machine, Outlook 6 on a XP virtual box, and Outlook 2013 is on another Windows 10 machine.
Note on the code: I'm using CPtrArrays to store the data passed by the calling function. You'll see the GetAt() in setting up the recipients.
Thanks!
MapiRecipDesc sender[1];
MapiRecipDesc recipient[50];
MapiFileDesc fileDesc[20];
sender[0].ulRecipClass = MAPI_ORIG;
sender[0].lpszAddress = "me#me.net";
sender[0].lpszName = "Me";
sender[0].lpEntryID = 0;
sender[0].ulEIDSize = 0;
sender[0].ulReserved = 0;
iToCount = 0;
iIndex = 0;
while (iIndex < m_paTo.GetCount()) {
recipient[iToCount].ulRecipClass = MAPI_TO;
recipient[iToCount].lpszAddress = (char *) m_paTo.GetAt(iToCount);
recipient[iToCount].lpszName = (char *) m_paTo.GetAt(iToCount);
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
iIndex++;
iToCount++;
}
iIndex = 0;
while (iIndex < m_paCC.GetCount()) {
recipient[iToCount].ulRecipClass = MAPI_CC;
recipient[iToCount].lpszAddress = (char *) m_paCC.GetAt(iIndex);
recipient[iToCount].lpszName = (char *) m_paCC.GetAt(iIndex);
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
iIndex++;
iToCount++;
}
iIndex = 0;
while (iIndex < m_paBCC.GetCount()) {
recipient[iToCount].ulRecipClass = MAPI_BCC;
recipient[iToCount].lpszAddress = (char *) m_paBCC.GetAt(iIndex);
recipient[iToCount].lpszName = (char *) m_paBCC.GetAt(iIndex);
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
iIndex++;
iToCount++;
}
iFileCount = 0;
iIndex = 0;
while (iIndex < m_paAttachments.GetCount()) {
fileDesc[iFileCount].flFlags = 0;
fileDesc[iFileCount].lpFileType = 0;
fileDesc[iFileCount].lpszFileName = (char *) m_paAttachments.GetAt(iIndex);
fileDesc[iFileCount].lpszPathName = (char *) m_paAttachments.GetAt(iIndex);
fileDesc[iFileCount].nPosition = -1;
fileDesc[iFileCount].ulReserved = 0;
iIndex++;
iFileCount++;
}
TCHAR szSubject[_MAX_PATH];
TCHAR szMessage[5001];
::StrCpy(szSubject, m_sSubject);
::StrCpy(szMessage, m_sMessage);
MapiMessage message;
::ZeroMemory(&message, sizeof(message));
message.lpszSubject = szSubject;
message.nRecipCount = iToCount;
message.lpRecips = recipient;
message.nFileCount = iFileCount;
message.lpFiles = fileDesc;
message.lpszNoteText = szMessage;
message.flFlags = MAPI_SENT | MAPI_UNREAD;
message.lpszConversationID = "123";
message.lpOriginator = sender;
//int nError = SendMail(0, (ULONG_PTR)hWndParent, &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);
int nError = SendMail(0, (ULONG_PTR)hWndParent, &message, MAPI_LOGON_UI, 0);
if (nError != SUCCESS_SUCCESS &&
nError != MAPI_USER_ABORT &&
nError != MAPI_E_LOGIN_FAILURE) {
CString sMessage;
CString sTest = recipient[0].lpszAddress;
sMessage.Format("MapiMail:: SendMail Error code %d Recip count %d: first Recip: %s", nError, message.nRecipCount, sTest);
AfxMessageBox(sMessage);
lLog.WriteString(sMessage);
return false;
}
With Barmak Shemirani's advice I'm posting my own answer. If anyone else is looking for this info, maybe I can save them some time by posting it here in one place.
The "None of your e-mail accounts could send to this recipient." problem can be solved by putting the email address of the recipient in pointy brackets in the name field in the recipient structure. Leave the address field blank. So, for example
::StrCpy(szTo, "<address#email.com>");
recipient[iToCount].ulRecipClass = MAPI_TO;
recipient[iToCount].lpszAddress = 0;
recipient[iToCount].lpszName = szTo;
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
I've tested this with Outlook 2013 and 2016 and with Outlook 6, Thunderbird, and EM Client and they're all happy with it. Apparently you can put the name as well PersonName <name#email.com>, but I haven't tested that.
The issue of Outlook displaying:
A program is trying to send an e-mail message on your behalf
is a software configuration issue. Most websites advise using the Trust Centre to set the Programmatic Access to allow other applications access via MAPI. You have to set this running the program as administrator but you also have to run Outlook as administrator for it to work
The workaround is to edit the registry and add a DWORD PromptSimpleMAPISend with a value of 2 in:
Computer\HKEY_CURRENT_USER\Software\Policies\Microsoft\Office\x.0\Outlook\Security
Bonus answer. If anyone is wondering about HTML in MAPI, it isn't supported. MAPI apparently pre-dates the common usage of HTML email.
There is a workaround. You can leave your message body blank and instead put your message in html in a file and put that file as the first attachment in the MAPI message. I named my file with an .html extension.
It is a bit of fluke - the email clients pick it up and display the HTML in the body of the email. Your html file will still be an attachment followed by any other attachments.
I've tested with Thunderbird, Outlook, and EM Client. I took a quick look at one web email reader and it did not display the html text (although the attachment was available to read).

Convert Network Interface Name

I am trying to get a list of active network interfaces with end user understandable names. Like the names listed in System Preferences instead of en0 en5.
I have the raw interfaces using getifaddrs but haven't been able to find how to take those and get the system names of Ethernet or Wifi.
Anyone know how to do this? This would be for macOS.
What I have now:
struct ifaddrs *ifap;
if( getifaddrs(&ifap) == 0 ){
struct ifaddrs *interface;
for (interface = ifap; interface != NULL; interface = interface->ifa_next) {
unsigned int flags = interface->ifa_flags;
struct sockaddr *addr = interface->ifa_addr;
// Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
if ((flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING)) {
if (addr->sa_family == AF_INET || addr->sa_family == AF_INET6) {
// Convert interface address to a human readable string:
char host[NI_MAXHOST];
getnameinfo(addr, addr->sa_len, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
printf("interface:%s, address:%s\n", interface->ifa_name, host);
// MAGIC HERE TO CONVERT ifa_name to "Ethernet" or something
}
}
}
freeifaddrs(ifap);
This is possible with System Configuration on macOS. In Objective-C like so:
CFArrayRef ref = SCNetworkInterfaceCopyAll();
NSArray* networkInterfaces = (__bridge NSArray *)(ref);
for(int i = 0; i < networkInterfaces.count; i += 1) {
SCNetworkInterfaceRef interface = (__bridge SCNetworkInterfaceRef)(networkInterfaces[i]);
CFStringRef displayName = SCNetworkInterfaceGetLocalizedDisplayName(interface);
CFStringRef bsdName = SCNetworkInterfaceGetBSDName(interface);
NSLog(#"Name:%# \ninterface: %#\nbsd:%#",displayName, SCNetworkInterfaceGetInterfaceType(interface), bsdName);
}
The localized display name will be something like Display Ethernet or WiFi and the BSD name will be something like en5 which will allow matching to the above code.
This approach doesn't work on iOS, but there aren't really any other configurations on iOS anyway.

kv_do() not working as expected

This is in my init.c:
server_data_t **data = (server_data_t**)get_env(argv, US_SERVER_DATA);
data[0] = (server_data_t*)calloc(1, sizeof(server_data_t));
kv_t *channels = &data[0]->channels;
kv_t *users = &data[0]->users;
kv_init(channels, "channels.dat", 10*1024, 0, 0, 0);
kv_init(users, "users.dat", 10*1024, 0, 0, 0);
These initializations were only for testing puproses:
channel_t *channel = (channel_t*)calloc(1, sizeof(channel_t));
channel->name = strdup("Test channel");
channel->id = 1;
kv_item channel_item;
channel_item.key = (char*)&channel->id;
channel_item.klen = sizeof(u32);
channel_item.val = (char*)channel;
channel_item.in_use = 0;
kv_add(channels, &channel_item);
channel_t *channel2 = (channel_t*)calloc(1, sizeof(channel_t));
channel2->name = strdup("Test channel2");
channel2->id = 2;
kv_item channel_item2;
channel_item2.key = (char*)&channel2->id;
channel_item2.klen = sizeof(u32);
channel_item2.val = (char*)channel2;
channel_item2.in_use = 0;
kv_add(channels, &channel_item2);
kv_do(channels, NULL, sizeof(u32), test_proc, 0);
The user defined process kv_do process:
static int test_proc(const kv_item *item, const void *ctx)
{
return 1;
}
Starting the server segfaults after the kv_do test process starts an infinite loop when trying to visit all items. It works fine with one item in the list, then it just visits the first item and quits. I can also visit the items one by one with the ID using kv_get.
I found an edge case where it worked with two items if my key was "Test Channel" key length was strlen("Test Channel") and then the next channel key was "Test Channel2" where key-length was as long as the first items key-length. Pretty confusing.
Is the mistake in the code (pointers and such) or how the process is supposed to work with the return value of 1?
I know that gwan sometimes have trouble with KVMs so if it could be that, I'm running Oracle VM VirtualBox v5.0.24 with Ubuntu.
Are the kv.c and persistence.c G-WAN examples working for you?
If any of them crashes, then you are probably using out-of-sync. G-WAN headers (gwan.h) with a recent ./gwan executable.

ZeroMQ's EPGM not working in weather PUB-SUB demo

I have compiled libzmq with openpgm with no changes under windows. Code here is taken from ZeroMQ Guide ("weather publisher" server/client). But if i change "tcp" to "epgm" it doesn't work any more (data is not received, but connection is established).
void test_serv()
{
// Prepare our context and publisher
void *context = zmq_ctx_new();
void *publisher = zmq_socket(context, ZMQ_PUB);
int rc = zmq_bind(publisher, "epgm://127.0.0.1:5556");
assert(rc == 0);
// Initialize random number generator
srandom((unsigned)time(NULL));
while (!stop_server)
{
// Get values that will fool the boss
int zipcode, temperature, relhumidity;
zipcode = randof(1000) + 600;
temperature = randof(215) - 80;
relhumidity = randof(50) + 10;
// Send message to all subscribers
char update[20];
sprintf(update, "%d %d %d", zipcode, temperature, relhumidity);
s_send(publisher, update);
}
LOG("END Server shutdown");
Sleep(500);
zmq_close(publisher);
zmq_ctx_destroy(context);
}
void test_sock()
{
// Socket to talk to server
LOG("Collecting updates from weather server...");
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
int rc = zmq_connect(subscriber, "epgm://127.0.0.1:5556");
assert(rc == 0);
// Subscribe to zipcode, default is NYC, 10001
char *filter = "1001 ";
rc = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE,
filter, strlen(filter));
assert(rc == 0);
// Process 100 updates
int update_nbr;
long total_temp = 0;
for (update_nbr = 0; update_nbr < 10; update_nbr++) {
char *string = s_recv(subscriber);
int zipcode, temperature, relhumidity;
sscanf(string, "%d %d %d",
&zipcode, &temperature, &relhumidity);
total_temp += temperature;
LOG(">> " << string);
free(string);
}
LOG("Average temperature for zipcode "<< filter << "was " << (int)(total_temp / update_nbr) << 'F');
zmq_close(subscriber);
zmq_ctx_destroy(context);
}
I run two functions in different threads, with tcp anything works as expected.
I have tried doing "route print 0.0.0.0" with cmd.exe and using interface IP (192.168.137.64) as prefix instead of "eth0" like shown in RFC: epgm://192.168.137.64;127.0.0.1:5556 on connect and/or bind, but this brokes my socket and raises error.
Also "PGM" requires administrator rights and i cannot test it now.
The error IS NOT "protocol not supported" errno is set to B (11) and i don't understand what does it mean (no docs on it).
EPGM is a bit finicky. According to this list post, if you're using EPGM your publisher and subscriber must be on separate hosts. More details here, it looks like this was a deliberate choice by the ZMQ team.
So, try it by spinning up your PUB and SUB on separate machines (changing the network addresses accordingly, of course).
The reason might be that windows does not support loopback capture interface. I tried weather example with protocol changed to epgm on linux and it works fine (well, shows some warnings about loopback, but the messages are transfered correctly)

serial COM port issue # reading

Here is the COM port opening part:
portHandle=CreateFileA(portName, GENERIC_READ|GENERIC_WRITE,0, NULL, OPEN_EXISTING, 0, NULL);
if (portHandle == INVALID_HANDLE_VALUE)
{
return -1;
}
COMMCONFIG Win_CommConfig;
COMMTIMEOUTS Win_CommTimeouts;
unsigned long confSize = sizeof(COMMCONFIG);
Win_CommConfig.dwSize = confSize;
GetCommConfig(portHandle, &Win_CommConfig, &confSize);
Win_CommConfig.dcb.Parity = 0;
Win_CommConfig.dcb.fRtsControl = RTS_CONTROL_DISABLE;
Win_CommConfig.dcb.fOutxCtsFlow = FALSE;
Win_CommConfig.dcb.fOutxDsrFlow = FALSE;
Win_CommConfig.dcb.fDtrControl = DTR_CONTROL_DISABLE;
Win_CommConfig.dcb.fDsrSensitivity = FALSE;
Win_CommConfig.dcb.fNull=FALSE;
Win_CommConfig.dcb.fTXContinueOnXoff = FALSE;
Win_CommConfig.dcb.fInX=FALSE;
Win_CommConfig.dcb.fOutX=FALSE;
Win_CommConfig.dcb.fBinary=TRUE;
Win_CommConfig.dcb.DCBlength = sizeof(DCB);
if (baudrate != -1)
{
Win_CommConfig.dcb.BaudRate = baudrate;
}
Win_CommConfig.dcb.ByteSize = 8;
Win_CommTimeouts.ReadIntervalTimeout = 50;
Win_CommTimeouts.ReadTotalTimeoutMultiplier = 0;
Win_CommTimeouts.ReadTotalTimeoutConstant = 110;
Win_CommTimeouts.WriteTotalTimeoutMultiplier = 0;
Win_CommTimeouts.WriteTotalTimeoutConstant = 110;
SetCommConfig(portHandle, &Win_CommConfig, sizeof(COMMCONFIG));
SetCommTimeouts(portHandle,&Win_CommTimeouts);
return 0;
It connects OK, I manage to issue some AT comamnds and read back OK\n> responses, even one of the upper level protocol (OBD2: the command is 0100\r) gets a proper answer. But when I attempt other commands such as scanning of supported pids (e.g 0000\n, 0101\n, 0202\n etc) the whole thing either echoes back whatever I write to it or just times out. Issuing the same sequence of commands from a hyperterminal works properly. These serial ports are virtual simulated ports should it matter - http://com0com.sourceforge.net/.
What am I missing ? Perhaps some reading / setting / resetting of someof the pins ? It has been a while since I last mingled with RS232... Thanks!
EDIT: just tried the scantool at https://www.scantool.net/downloads/diagnostic-software/ and it worked ok too.
e.g 0000\n, 0101\n, 0202\n
This was the issue. It should have been \r at the end, not \n. Hyperterminal worked because the key would insert a \r here on Windows. Probablysome validation of the input was done by the device connected and so it got to work even with the wrong terminator character fed in.

Resources