You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
10 KiB

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <openssl/md5.h>
#include <unistd.h>
#include <iconv.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include "fbsh.h"
std::string generateMD5 (char* input, int size) {
int i;
MD5_CTX md5;
unsigned char out[MD5_DIGEST_LENGTH];
char txt[32];
std::string md5sum = "";
MD5_Init(&md5);
MD5_Update(&md5, input, size);
MD5_Final(out, &md5);
for ( i = 0; i < MD5_DIGEST_LENGTH; i++) {
snprintf (txt, 32, "%02x", out[i]);
md5sum += txt;
}
return md5sum;
}
FBSmartHome::FBSmartHome() {
hostname = "";
SID = "00 not yet set 00";
fsidfile = NULL;
}
FBSmartHome::~FBSmartHome() {
if (fsidfile != NULL) CloseSIDFile();
}
/*
* try to connect with the last known SID.
*/
int FBSmartHome::Connect(std::string host) {
int len;
xmlDocPtr xmldoc;
xmlNodePtr xmlnode;
hostname = host;
// is sid working?
len = tcp.WebGetFile(hostname+"/login_sid.lua?sid="+SID, inbuffer, FB_BUFFER, NULL);
if (len > FB_BUFFER) len = FB_BUFFER;
else if (len < 0) {
fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__);
errno = EAI_FAIL;
return -1;
}
inbuffer[len] = '\0';
XMLPrepare(inbuffer, len, &xmldoc, &xmlnode);
//
// parse for SID and Challange
while (xmlnode) {
if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"SessionInfo"))){
xmlNodePtr xmlchild = xmlnode->xmlChildrenNode;
while (xmlchild) {
if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"SID")))
SID = (char *)xmlNodeListGetString(xmldoc, xmlchild->children, 1);
xmlchild = xmlchild->next;
}
}
xmlnode = xmlnode->next;
}
xmlFreeDoc(xmldoc);
if (SID.compare("0000000000000000") == 0) {
hostname = "";
errno = ENOTCONN;
return -1;
}
errno = 0;
return 0;
}
int FBSmartHome::XMLPrepare(char *buffer, int len, xmlDocPtr *xmldoc, xmlNodePtr *xmlnode) {
//
// read the xml data from memory and select root node.
if (xmldoc == NULL || xmlnode == NULL) return -1;
*xmldoc = xmlReadMemory(inbuffer, len, "hostname", NULL, 0);
if (xmldoc == NULL) {
fprintf (stderr, "%s: Could not read XML Document\n", __FUNCTION__);
errno = EAI_FAIL;
return -1;
}
*xmlnode = xmlDocGetRootElement(*xmldoc);
if (*xmlnode == NULL) {
fprintf (stderr, "%s: Empty XML Document\n", __FUNCTION__);
xmlFreeDoc(*xmldoc);
errno = EOF;
return -1;
}
return 0;
}
/*
* connect with username and password, if connect is successfull the
* SID will be returned.
*/
#define PWSSIZE 512
int FBSmartHome::Connect (std::string host, std::string username, std::string password) {
int len;
xmlDocPtr xmldoc;
xmlNodePtr xmlnode;
std::string challenge;
std::string formdata;
hostname = host;
SID = "";
tcp.Close();
//
// get challenge from FritzBox
len = tcp.WebGetFile(hostname+"/login_sid.lua", inbuffer, FB_BUFFER, NULL);
if (len > FB_BUFFER) len = FB_BUFFER;
else if (len < 0) {
fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__);
errno = EAI_FAIL;
return -1;
}
inbuffer[len] = '\0';
XMLPrepare(inbuffer, len, &xmldoc, &xmlnode);
//
// parse for SID and Challange
while (xmlnode) {
if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"SessionInfo"))){
xmlNodePtr xmlchild = xmlnode->xmlChildrenNode;
while (xmlchild) {
if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"SID")))
SID = (char *)xmlNodeListGetString(xmldoc, xmlchild->children, 1);
else if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"Challenge")))
challenge = (char*) xmlNodeListGetString(xmldoc, xmlchild->children, 1);
xmlchild = xmlchild->next;
}
}
xmlnode = xmlnode->next;
}
xmlFreeDoc(xmldoc);
//
// create Challenge-PSW -> MD5 sum
//
//
// convert to UTF16_LE
std::string convertfrom = challenge+"-"+password;
char *conv_input = (char *)convertfrom.c_str();
size_t conv_inbytes = strlen (conv_input);
size_t conv_size;
char convertedpsw[PWSSIZE];
char *conv_output = (char*)convertedpsw;
size_t conv_outbytes = PWSSIZE;
iconv_t ic = iconv_open("UTF-16LE", "UTF-8");
if ((long int)ic == -1) {
fprintf (stderr, "iconv_open error: %s\n", strerror(errno));
return -1;
}
conv_size = iconv(ic, &conv_input, &conv_inbytes, (char**)&conv_output, &conv_outbytes);
if ((long int)conv_size == -1) {
fprintf (stderr, "iconv error: %s\n", strerror(errno));
return -1;
}
iconv_close(ic);
//
// create password
formdata = "response="+challenge+"-"+generateMD5(convertedpsw, conv_output-convertedpsw)+"&username="+username;
len = tcp.WebGetFile(hostname+"/login_sid.lua", inbuffer, FB_BUFFER, (char*)formdata.c_str());
if (len > FB_BUFFER) len = FB_BUFFER;
else if (len < 0) {
errno = EAI_FAIL;
return -1;
}
inbuffer[len] = '\0';
//
// parse for SID and Challange
XMLPrepare(inbuffer, len, &xmldoc, &xmlnode);
while (xmlnode) {
if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"SessionInfo"))){
xmlNodePtr xmlchild = xmlnode->xmlChildrenNode;
while (xmlchild) {
if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"SID")))
SID = (char *)xmlNodeListGetString(xmldoc, xmlchild->children, 1);
else if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"Challenge")))
challenge = (char*) xmlNodeListGetString(xmldoc, xmlchild->children, 1);
xmlchild = xmlchild->next;
}
}
xmlnode = xmlnode->next;
}
xmlFreeDoc(xmldoc);
errno = 0;
return 0;
};
/*
* read sidfile, if it does not exist create file.
*/
#define BUFSIZE 1024
int FBSmartHome::UseSIDFile(std::string fn) {
char buffer[BUFSIZE];
int i;
if ((fsidfile = fopen (fn.c_str(), "r+")) == NULL) {
// file not found create file
if ((fsidfile = fopen (fn.c_str(), "w")) == NULL) {
fprintf (stderr, "error on writing sidfile '%s' : %s\n", fn.c_str(),
strerror(errno));
return -1;
}
}
else {
// sidfile found and open
fseek (fsidfile, 0, SEEK_SET);
if (fgets(buffer, BUFSIZE, fsidfile) != NULL) {
buffer[BUFSIZE] = '\0';
i = strlen (buffer);
if (i > 0 && buffer[i-1] == '\n') buffer[i-1] = '\0';
SID = buffer;
}
}
return 0;
}
/*
* if the sid file is open save current sid.
*/
int FBSmartHome::CloseSIDFile() {
if (fsidfile != NULL) {
fseek (fsidfile, 0, SEEK_SET);
fprintf (fsidfile, "%s", SID.c_str());
fclose(fsidfile);
fsidfile = NULL;
}
return 0;
}
int FBSmartHome::XMLReadAttr(FBSmartHomeDevice *dev, std::string objPrefix, xmlDocPtr xmldoc, xmlAttrPtr xmlattr) {
char *txt = NULL;
while (xmlattr) {
if (dev->others.length() > 0) dev->others += "\t";
txt = (char*) xmlNodeListGetString(xmldoc, xmlattr->children, 1);
if (txt != NULL)
dev->others += objPrefix + "." + (std::string)(char*) xmlattr->name + "=" + txt;
else
dev->others += (std::string)(char*) xmlattr->name + "=";
xmlattr = xmlattr->next;
}
return 0;
}
int FBSmartHome::XMLReadOBJ(FBSmartHomeDevice *dev, std::string objPrefix, xmlDocPtr xmldoc, xmlNodePtr xmlobj) {
std::string tmp = "";
char *txt = NULL;
while (xmlobj) {
if (dev->others.length() > 0) dev->others += "\t";
txt = (char*) xmlNodeListGetString(xmldoc, xmlobj->children, 1);
if (txt != NULL)
dev->others += objPrefix + "." + (std::string)(char*) xmlobj->name + "=" + txt;
else if (xmlobj->children) {
XMLReadAttr(dev, objPrefix+"."+(std::string)(char*)xmlobj->name, xmldoc, xmlobj->properties);
XMLReadOBJ(dev, objPrefix+"."+(std::string)(char*)xmlobj->name, xmldoc, xmlobj->children);
}
else
dev->others += (std::string)(char*) xmlobj->name + "=";
xmlobj = xmlobj->next;
}
return 0;
}
int FBSmartHome::XMLReadAndAddDevice(std::list<FBSmartHomeDevice> *devlist, std::string defType,
xmlDocPtr xmldoc, xmlNodePtr xmlnode) {
xmlAttrPtr xmlattr;
xmlNodePtr child;
std::string tmp = "";
char *txt = NULL;
FBSmartHomeDevice fbdev;
//
// reset all fields
fbdev.others = "";
fbdev.id = "";
fbdev.name = "";
fbdev.type = defType;
//
// read attributes
xmlattr = xmlnode->properties;
while (xmlattr) {
if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"identifier"))
fbdev.id = (char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1);
else {
txt = (char*) xmlNodeListGetString(xmldoc, xmlattr->children, 1);
if (txt != NULL) fbdev.others += (std::string)(char*) xmlattr->name + "=" + txt + "\t";
else fbdev.others += (std::string)(char*) xmlattr->name + "=\t";
}
xmlattr = xmlattr->next;
}
//
// read subelements
child = xmlnode->children;
while (child) {
if (!xmlStrcmp(child->name, (const xmlChar *)"name"))
fbdev.name = (char *) xmlNodeListGetString(xmldoc, child->children, 1);
else if (!xmlStrcmp(child->name, (const xmlChar *)"button")) {
fbdev.type = "button";
XMLReadAndAddDevice(devlist, "button", xmldoc, child);
}
else if (!xmlStrcmp(child->name, (const xmlChar *)"hkr")) {
fbdev.type = (char*)child->name;
XMLReadOBJ(&fbdev, "hkr" , xmldoc, child->children);
}
else {
txt = (char*) xmlNodeListGetString(xmldoc, child->children, 1);
if (txt != NULL) {
fbdev.others += (std::string)(char*) child->name + "=" + txt + "\t";
} else {
if (child->children) {
XMLReadAttr(&fbdev, (std::string)(char*)child->name, xmldoc, child->properties);
XMLReadOBJ(&fbdev, (std::string)(char*)child->name, xmldoc, child->children);
}
else {
fbdev.others += (std::string)(char*) child->name + "=\t";
}
}
}
child = child->next;
}
devlist->push_back(fbdev);
return 0;
}
/*
* retrieve the device list from the fritzbox
*/
std::list<FBSmartHomeDevice> *FBSmartHome::GetDevices() {
std::list<FBSmartHomeDevice> *devlist = NULL;
FBSmartHomeDevice smd;
xmlDocPtr xmldoc;
xmlNodePtr xmlnode;
int len;
devlist = new std::list<FBSmartHomeDevice>;
// retrieve data
len = tcp.WebGetFile(hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=getdevicelistinfos", inbuffer, FB_BUFFER, NULL);
if (len > FB_BUFFER) len = FB_BUFFER;
else if (len < 0) {
fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__);
errno = EAI_FAIL;
return NULL;
}
inbuffer[len] = '\0';
XMLPrepare(inbuffer, len, &xmldoc, &xmlnode);
//
// parse for SID and Challange
while (xmlnode) {
if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"devicelist"))){
xmlNodePtr xmlchild = xmlnode->xmlChildrenNode;
//
// read each device details
//
while (xmlchild) {
if (!xmlStrcmp(xmlchild->name, (const xmlChar *)"device"))
XMLReadAndAddDevice(devlist, "", xmldoc, xmlchild);
xmlchild = xmlchild->next;
}
}
xmlnode = xmlnode->next;
}
xmlFreeDoc(xmldoc);
if (SID.compare("0000000000000000") == 0) {
hostname = "";
errno = ENOTCONN;
return NULL;
}
return devlist;
}