API in C using winsock
From MikroTik Wiki
This is an implementation of RouterOS API written on C that using winsock2 for compatibility with Windows operation systems. It's based on API in C (You should use all sources from here, except mikrotik-api.c, that incompatible with Windows). Use compatible mikrotik-api.c source from below.
If your compiler does not support "#pragma comment", add ws2_32.lib to your linker manually.
RouterOS API Source file (mikrotik-api.c)
/********************************************************************
* Some definitions
* Word = piece of API code
* Sentence = multiple words
* Block = multiple sentences (usually in response to a sentence request)
*
int fdSock;
int iLoginResult;
struct Sentence stSentence;
struct Block stBlock;
fdSock = apiConnect("10.0.0.1", 8728);
// attempt login
iLoginResult = login(fdSock, "admin", "adminPassword");
if (!iLoginResult)
{
apiDisconnect(fdSock);
printf("Invalid username or password.\n");
exit(1);
}
// initialize, fill and send sentence to the API
initializeSentence(&stSentence);
addWordToSentence(&stSentence, "/interface/getall");
writeSentence(fdSock, &stSentence);
// receive and print block from the API
stBlock = readBlock(fdSock);
printBlock(&stBlock);
apiDisconnect(fdSock);
********************************************************************/
// C implementation of Mikrotik's API rewritten for Winsock2 (for windows)
// Updated 28 August 2011 by hel
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include "md5.h"
#include "mikrotik-api.h"
/********************************************************************
* Connect to API
* Returns a socket descriptor
********************************************************************/
int apiConnect(char *szIPaddr, int iPort)
{
int fdSock;
struct sockaddr_in address;
int iConnectResult;
int iLen;
WORD versionWanted = MAKEWORD(1,1);
WSADATA wsaData;
WSAStartup(versionWanted, &wsaData);
fdSock = socket(AF_INET, SOCK_STREAM, 0);
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(szIPaddr);
address.sin_port = htons(iPort);
iLen = sizeof(address);
DEBUG ? printf("Connecting to %s\n", szIPaddr) : 0;
iConnectResult = connect(fdSock, (struct sockaddr *)&address, iLen);
if(iConnectResult==-1)
{
perror ("Connection problem");
exit(1);
}
else
{
DEBUG ? printf("Successfully connected to %s\n", szIPaddr) : 0;
}
// determine endianness of this machine
// iLittleEndian will be set to 1 if we are
// on a little endian machine...otherwise
// we are assumed to be on a big endian processor
iLittleEndian = isLittleEndian();
return fdSock;
}
/********************************************************************
* Disconnect from API
* Close the API socket
********************************************************************/
void apiDisconnect(int fdSock)
{
DEBUG ? printf("Closing socket\n") : 0;
closesocket(fdSock);
}
/********************************************************************
* Login to the API
* 1 is returned on successful login
* 0 is returned on unsuccessful login
********************************************************************/
int login(int fdSock, char *username, char *password)
{
struct Sentence stReadSentence;
struct Sentence stWriteSentence;
char *szMD5Challenge;
char *szMD5ChallengeBinary;
char *szMD5PasswordToSend;
char *szLoginUsernameResponseToSend;
char *szLoginPasswordResponseToSend;
md5_state_t state;
md5_byte_t digest[16];
char cNull[1] = {0};
writeWord(fdSock, "/login");
writeWord(fdSock, "");
stReadSentence = readSentence(fdSock);
DEBUG ? printSentence (&stReadSentence) : 0;
if (stReadSentence.iReturnValue != DONE)
{
printf("error.\n");
exit(0);
}
// extract md5 string from the challenge sentence
szMD5Challenge = strtok(stReadSentence.szSentence[1], "=");
szMD5Challenge = strtok(NULL, "=");
DEBUG ? printf("MD5 of challenge = %s\n", szMD5Challenge) : 0;
// convert szMD5Challenge to binary
szMD5ChallengeBinary = md5ToBinary(szMD5Challenge);
// get md5 of the password + challenge concatenation
md5_init(&state);
md5_append(&state, cNull, 1);
md5_append(&state, (const md5_byte_t *)password, strlen(password));
md5_append(&state, (const md5_byte_t *)szMD5ChallengeBinary, 16);
md5_finish(&state, digest);
// convert this digest to a string representation of the hex values
// digest is the binary format of what we want to send
// szMD5PasswordToSend is the "string" hex format
szMD5PasswordToSend = md5DigestToHexString(digest);
DEBUG ? printf("szPasswordToSend = %s\n", szMD5PasswordToSend) : 0;
// put together the login sentence
initializeSentence(&stWriteSentence);
addWordToSentence(&stWriteSentence, "/login");
addWordToSentence(&stWriteSentence, "=name=");
addPartWordToSentence(&stWriteSentence, username);
addWordToSentence(&stWriteSentence, "=response=00");
addPartWordToSentence(&stWriteSentence, szMD5PasswordToSend);
DEBUG ? printSentence(&stWriteSentence) : 0;
writeSentence(fdSock, &stWriteSentence);
stReadSentence = readSentence(fdSock);
DEBUG ? printSentence (&stReadSentence) : 0;
if (stReadSentence.iReturnValue == DONE)
{
return 1;
}
else
{
return 0;
}
}
/********************************************************************
* Encode message length and write it out to the socket
********************************************************************/
void writeLen(int fdSock, int iLen)
{
char *cEncodedLength; // encoded length to send to the api socket
char *cLength; // exactly what is in memory at &iLen integer
cLength = calloc(sizeof(int), 1);
cEncodedLength = calloc(sizeof(int), 1);
// set cLength address to be same as iLen
cLength = (char *)&iLen;
DEBUG ? printf("length of word is %d\n", iLen) : 0;
// write 1 byte
if (iLen < 0x80)
{
cEncodedLength[0] = (char)iLen;
send (fdSock, cEncodedLength, 1, 0);
}
// write 2 bytes
else if (iLen < 0x4000)
{
DEBUG ? printf("iLen < 0x4000.\n") : 0;
if (iLittleEndian)
{
cEncodedLength[0] = cLength[1] | 0x80;
cEncodedLength[1] = cLength[0];
}
else
{
cEncodedLength[0] = cLength[2] | 0x80;
cEncodedLength[1] = cLength[3];
}
send (fdSock, cEncodedLength, 2, 0);
}
// write 3 bytes
else if (iLen < 0x200000)
{
DEBUG ? printf("iLen < 0x200000.\n") : 0;
if (iLittleEndian)
{
cEncodedLength[0] = cLength[2] | 0xc0;
cEncodedLength[1] = cLength[1];
cEncodedLength[2] = cLength[0];
}
else
{
cEncodedLength[0] = cLength[1] | 0xc0;
cEncodedLength[1] = cLength[2];
cEncodedLength[2] = cLength[3];
}
send (fdSock, cEncodedLength, 3, 0);
}
// write 4 bytes
// this code SHOULD work, but is untested...
else if (iLen < 0x10000000)
{
DEBUG ? printf("iLen < 0x10000000.\n") : 0;
if (iLittleEndian)
{
cEncodedLength[0] = cLength[3] | 0xe0;
cEncodedLength[1] = cLength[2];
cEncodedLength[2] = cLength[1];
cEncodedLength[3] = cLength[0];
}
else
{
cEncodedLength[0] = cLength[0] | 0xe0;
cEncodedLength[1] = cLength[1];
cEncodedLength[2] = cLength[2];
cEncodedLength[3] = cLength[3];
}
send (fdSock, cEncodedLength, 4, 0);
}
else // this should never happen
{
printf("length of word is %d\n", iLen);
printf("word is too long.\n");
exit(1);
}
}
/********************************************************************
* Write a word to the socket
********************************************************************/
void writeWord(int fdSock, char *szWord)
{
DEBUG ? printf("Word to write is %s\n", szWord) : 0;
writeLen(fdSock, strlen(szWord));
send(fdSock, szWord, strlen(szWord), 0);
}
/********************************************************************
* Write a sentence (multiple words) to the socket
********************************************************************/
void writeSentence(int fdSock, struct Sentence *stWriteSentence)
{
int iIndex;
if (stWriteSentence->iLength == 0)
{
return;
}
DEBUG ? printf("Writing sentence\n"): 0;
DEBUG ? printSentence(stWriteSentence) : 0;
for (iIndex=0; iIndex<stWriteSentence->iLength; iIndex++)
{
writeWord(fdSock, stWriteSentence->szSentence[iIndex]);
}
writeWord(fdSock, "");
}
/********************************************************************
* Read a message length from the socket
*
* 80 = 10000000 (2 character encoded length)
* C0 = 11000000 (3 character encoded length)
* E0 = 11100000 (4 character encoded length)
*
* Message length is returned
********************************************************************/
int readLen(int fdSock)
{
char cFirstChar; // first character read from socket
char *cLength; // length of next message to read...will be cast to int at the end
int *iLen; // calculated length of next message (Cast to int)
cLength = calloc(sizeof(int), 1);
DEBUG ? printf("start readLen()\n") : 0;
recv(fdSock, &cFirstChar, 1, 0);
DEBUG ? printf("byte1 = %#x\n", cFirstChar) : 0;
// read 4 bytes
// this code SHOULD work, but is untested...
if ((cFirstChar & 0xE0) == 0xE0)
{
DEBUG ? printf("4-byte encoded length\n") : 0;
if (iLittleEndian)
{
cLength[3] = cFirstChar;
cLength[3] &= 0x1f; // mask out the 1st 3 bits
recv(fdSock, &cLength[2], 1, 0);
recv(fdSock, &cLength[1], 1, 0);
recv(fdSock, &cLength[0], 1, 0);
}
else
{
cLength[0] = cFirstChar;
cLength[0] &= 0x1f; // mask out the 1st 3 bits
recv(fdSock, &cLength[1], 1, 0);
recv(fdSock, &cLength[2], 1, 0);
recv(fdSock, &cLength[3], 1, 0);
}
iLen = (int *)cLength;
}
// read 3 bytes
else if ((cFirstChar & 0xC0) == 0xC0)
{
DEBUG ? printf("3-byte encoded length\n") : 0;
if (iLittleEndian)
{
cLength[2] = cFirstChar;
cLength[2] &= 0x3f; // mask out the 1st 2 bits
recv(fdSock, &cLength[1], 1, 0);
recv(fdSock, &cLength[0], 1, 0);
}
else
{
cLength[1] = cFirstChar;
cLength[1] &= 0x3f; // mask out the 1st 2 bits
recv(fdSock, &cLength[2], 1, 0);
recv(fdSock, &cLength[3], 1, 0);
}
iLen = (int *)cLength;
}
// read 2 bytes
else if ((cFirstChar & 0x80) == 0x80)
{
DEBUG ? printf("2-byte encoded length\n") : 0;
if (iLittleEndian)
{
cLength[1] = cFirstChar;
cLength[1] &= 0x7f; // mask out the 1st bit
recv(fdSock, &cLength[0], 1, 0);
}
else
{
cLength[2] = cFirstChar;
cLength[2] &= 0x7f; // mask out the 1st bit
recv(fdSock, &cLength[3], 1, 0);
}
iLen = (int *)cLength;
}
// assume 1-byte encoded length...same on both LE and BE systems
else
{
DEBUG ? printf("1-byte encoded length\n") : 0;
iLen = malloc(sizeof(int));
*iLen = (int)cFirstChar;
}
return *iLen;
}
/********************************************************************
* Read a word from the socket
* The word that was read is returned as a string
********************************************************************/
char *readWord(int fdSock)
{
int iLen = readLen(fdSock);
int iBytesToRead = 0;
int iBytesRead = 0;
char *szWord;
char *szRetWord;
char *szTmpWord;
DEBUG ? printf("readWord iLen=%x\n", iLen) : 0;
if (iLen > 0)
{
// allocate memory for strings
szRetWord = calloc(sizeof(char), iLen + 1);
szTmpWord = calloc(sizeof(char), 1024 + 1);
while (iLen != 0)
{
// determine number of bytes to read this time around
// lesser of 1024 or the number of byes left to read
// in this word
iBytesToRead = iLen > 1024 ? 1024 : iLen;
// read iBytesToRead from the socket
iBytesRead = recv(fdSock, szTmpWord, iBytesToRead, 0);
// terminate szTmpWord
szTmpWord[iBytesRead] = 0;
// concatenate szTmpWord to szRetWord
strcat(szRetWord, szTmpWord);
// subtract the number of bytes we just read from iLen
iLen -= iBytesRead;
}
// deallocate szTmpWord
free(szTmpWord);
DEBUG ? printf("word = %s\n", szRetWord) : 0;
return szRetWord;
}
else
{
return NULL;
}
}
/********************************************************************
* Read a sentence from the socket
* A Sentence struct is returned
********************************************************************/
struct Sentence readSentence(int fdSock)
{
struct Sentence stReturnSentence;
char *szWord;
int i=0;
int iReturnLength=0;
DEBUG ? printf("readSentence\n") : 0;
initializeSentence(&stReturnSentence);
while (szWord = readWord(fdSock))
{
addWordToSentence(&stReturnSentence, szWord);
// check to see if we can get a return value from the API
if (strstr(szWord, "!done") != NULL)
{
DEBUG ? printf("return sentence contains !done\n") : 0;
stReturnSentence.iReturnValue = DONE;
}
else if (strstr(szWord, "!trap") != NULL)
{
DEBUG ? printf("return sentence contains !trap\n") : 0;
stReturnSentence.iReturnValue = TRAP;
}
else if (strstr(szWord, "!fatal") != NULL)
{
DEBUG ? printf("return sentence contains !fatal\n") : 0;
stReturnSentence.iReturnValue = FATAL;
}
}
// if any errors, get the next sentence
if (stReturnSentence.iReturnValue == TRAP || stReturnSentence.iReturnValue == FATAL)
{
readSentence(fdSock);
}
if (DEBUG)
{
for (i=0; i<stReturnSentence.iLength; i++)
{
printf("stReturnSentence.szSentence[%d] = %s\n", i, stReturnSentence.szSentence[i]);
}
}
return stReturnSentence;
}
/********************************************************************
* Read sentence block from the socket...keeps reading sentences
* until it encounters !done, !trap or !fatal from the socket
********************************************************************/
struct Block readBlock(int fdSock)
{
struct Sentence stSentence;
struct Block stBlock;
initializeBlock(&stBlock);
DEBUG ? printf("readBlock\n") : 0;
do
{
stSentence = readSentence(fdSock);
DEBUG ? printf("readSentence succeeded.\n") : 0;
addSentenceToBlock(&stBlock, &stSentence);
DEBUG ? printf("addSentenceToBlock succeeded\n") : 0;
} while (stSentence.iReturnValue == 0);
DEBUG ? printf("readBlock completed successfully\n") : 0;
return stBlock;
}
/********************************************************************
* Initialize a new block
* Set iLength to 0.
********************************************************************/
void initializeBlock(struct Block *stBlock)
{
DEBUG ? printf("initializeBlock\n") : 0;
stBlock->iLength = 0;
}
/********************************************************************
* Clear an existing block
* Free all sentences in the Block struct and set iLength to 0.
********************************************************************/
void clearBlock(struct Block *stBlock)
{
DEBUG ? printf("clearBlock\n") : 0;
free(stBlock->stSentence);
initializeBlock(&stBlock);
}
/********************************************************************
* Print a block.
* Output a Block with printf.
********************************************************************/
void printBlock(struct Block *stBlock)
{
int i;
DEBUG ? printf("printBlock\n") : 0;
DEBUG ? printf("block iLength = %d\n", stBlock->iLength) : 0;
for (i=0; i<stBlock->iLength; i++)
{
printSentence(stBlock->stSentence[i]);
}
}
/********************************************************************
* Add a sentence to a block
* Allocate memory and add a sentence to a Block.
********************************************************************/
void addSentenceToBlock(struct Block *stBlock, struct Sentence *stSentence)
{
int iNewLength;
iNewLength = stBlock->iLength + 1;
DEBUG ? printf("addSentenceToBlock iNewLength=%d\n", iNewLength) : 0;
// allocate mem for the new Sentence position
if (stBlock->iLength == 0)
{
stBlock->stSentence = malloc(1 * sizeof stBlock->stSentence);
}
else
{
stBlock->stSentence = realloc(stBlock->stSentence, iNewLength * sizeof stBlock->stSentence + 1);
}
// allocate mem for the full sentence struct
stBlock->stSentence[stBlock->iLength] = malloc(sizeof *stSentence);
// copy actual sentence struct to the block position
memcpy(stBlock->stSentence[stBlock->iLength], stSentence, sizeof *stSentence);
// update iLength
stBlock->iLength = iNewLength;
DEBUG ? printf("addSentenceToBlock stBlock->iLength=%d\n", stBlock->iLength) : 0;
}
/********************************************************************
* Initialize a new sentence
********************************************************************/
void initializeSentence(struct Sentence *stSentence)
{
DEBUG ? printf("initializeSentence\n") : 0;
stSentence->iLength = 0;
stSentence->iReturnValue = 0;
}
/********************************************************************
* Clear an existing sentence
********************************************************************/
void clearSentence(struct Sentence *stSentence)
{
DEBUG ? printf("initializeSentence\n") : 0;
free(stSentence->szSentence);
initializeSentence(stSentence);
}
/********************************************************************
* Add a word to a sentence struct
********************************************************************/
void addWordToSentence(struct Sentence *stSentence, char *szWordToAdd)
{
int iNewLength;
iNewLength = stSentence->iLength + 1;
// allocate mem for the new word position
if (stSentence->iLength == 0)
{
stSentence->szSentence = malloc(1 * sizeof stSentence->szSentence);
}
else
{
stSentence->szSentence = realloc(stSentence->szSentence, iNewLength * sizeof stSentence->szSentence + 1);
}
// allocate mem for the full word string
stSentence->szSentence[stSentence->iLength] = malloc(strlen(szWordToAdd) + 1);
// copy word string to the sentence
strcpy(stSentence->szSentence[stSentence->iLength], szWordToAdd);
// update iLength
stSentence->iLength = iNewLength;
}
/********************************************************************
* Add a partial word to a sentence struct...useful for concatenation
********************************************************************/
void addPartWordToSentence(struct Sentence *stSentence, char *szWordToAdd)
{
int iIndex;
iIndex = stSentence->iLength - 1;
// reallocate memory for the new partial word
stSentence->szSentence[iIndex] = realloc(stSentence->szSentence[iIndex], strlen(stSentence->szSentence[iIndex]) + strlen(szWordToAdd) + 1);
// concatenate the partial word to the existing sentence
strcat (stSentence->szSentence[iIndex], szWordToAdd);
}
/********************************************************************
* Print a Sentence struct
********************************************************************/
void printSentence(struct Sentence *stSentence)
{
int i;
DEBUG ? printf("Sentence iLength = %d\n", stSentence->iLength) : 0;
DEBUG ? printf("Sentence iReturnValue = %d\n", stSentence->iReturnValue) : 0;
printf("Sentence iLength = %d\n", stSentence->iLength);
printf("Sentence iReturnValue = %d\n", stSentence->iReturnValue);
for (i=0; i<stSentence->iLength; i++)
{
printf(">>> %s\n", stSentence->szSentence[i]);
}
printf("\n");
}
/********************************************************************
* MD5 helper function to convert an md5 hex char representation to
* binary representation.
********************************************************************/
char *md5ToBinary(char *szHex)
{
int di;
char cBinWork[3];
char *szReturn;
// allocate 16 + 1 bytes for our return string
szReturn = malloc((16 + 1) * sizeof *szReturn);
// 32 bytes in szHex?
if (strlen(szHex) != 32)
{
return NULL;
}
for (di=0; di<32; di+=2)
{
cBinWork[0] = szHex[di];
cBinWork[1] = szHex[di + 1];
cBinWork[2] = 0;
DEBUG ? printf("cBinWork = %s\n", cBinWork) : 0;
szReturn[di/2] = hexStringToChar(cBinWork);
}
return szReturn;
}
/********************************************************************
* MD5 helper function to calculate and return hex representation
* of an MD5 digest stored in binary.
********************************************************************/
char *md5DigestToHexString(md5_byte_t *binaryDigest)
{
int di;
char *szReturn;
// allocate 32 + 1 bytes for our return string
szReturn = malloc((32 + 1) * sizeof *szReturn);
for (di = 0; di < 16; ++di)
{
sprintf(szReturn + di * 2, "%02x", binaryDigest[di]);
}
return szReturn;
}
/********************************************************************
* Quick and dirty function to convert hex string to char...
* the toConvert string MUST BE 2 characters + null terminated.
********************************************************************/
char hexStringToChar(char *cToConvert)
{
char cConverted;
unsigned int iAccumulated=0;
char cString0[2] = {cToConvert[0], 0};
char cString1[2] = {cToConvert[1], 0};
// look @ first char in the 16^1 place
if (cToConvert[0] == 'f' || cToConvert[0] == 'F')
{
iAccumulated += 16*15;
}
else if (cToConvert[0] == 'e' || cToConvert[0] == 'E')
{
iAccumulated += 16*14;
}
else if (cToConvert[0] == 'd' || cToConvert[0] == 'D')
{
iAccumulated += 16*13;
}
else if (cToConvert[0] == 'c' || cToConvert[0] == 'C')
{
iAccumulated += 16*12;
}
else if (cToConvert[0] == 'b' || cToConvert[0] == 'B')
{
iAccumulated += 16*11;
}
else if (cToConvert[0] == 'a' || cToConvert[0] == 'A')
{
iAccumulated += 16*10;
}
else
{
iAccumulated += 16 * atoi(cString0);
}
// now look @ the second car in the 16^0 place
if (cToConvert[1] == 'f' || cToConvert[1] == 'F')
{
iAccumulated += 15;
}
else if (cToConvert[1] == 'e' || cToConvert[1] == 'E')
{
iAccumulated += 14;
}
else if (cToConvert[1] == 'd' || cToConvert[1] == 'D')
{
iAccumulated += 13;
}
else if (cToConvert[1] == 'c' || cToConvert[1] == 'C')
{
iAccumulated += 12;
}
else if (cToConvert[1] == 'b' || cToConvert[1] == 'B')
{
iAccumulated += 11;
}
else if (cToConvert[1] == 'a' || cToConvert[1] == 'A')
{
iAccumulated += 10;
}
else
{
iAccumulated += atoi(cString1);
}
DEBUG ? printf("%d\n", iAccumulated) : 0;
return (char)iAccumulated;
}
/********************************************************************
* Test whether or not this system is little endian at RUNTIME
* Courtesy: http://download.osgeo.org/grass/grass6_progman/endian_8c_source.html
********************************************************************/
int isLittleEndian(void)
{
union
{
int testWord;
char testByte[sizeof(int)];
} endianTest;
endianTest.testWord = 1;
if (endianTest.testByte[0] == 1)
return 1; /* true: little endian */
return 0; /* false: big endian */
}