/** * astriumnet protocoll. This is the protocoll understood by the newest version * of Astrium choppers with the Beckhoff server backend. There are a number of * peculaties here: * * On connecting there is an authorisation exchange. This, the client, has to send a * challenge. The server responds with a random hash code. Then a password is encrypted * with the hashcode and sent to the server. Who, hopefully, will respond with an AuthOK * message. * * Furtheron the message exchange happens in an XML like format. But implemented in a * slightly strange way in that everything is done through attributes. * * For more information, see the documentation by Astrium. * * copyright: see file COPYRIGHT * * Mark Koennecke, April 2014 */ #include #include #include #include #include #include #include extern char *trim(char *); typedef struct { char password[132]; int authState; pDynString buffer; }Astriumnet, *pAstriumnet; #define AUTHSEND 4 #define AUTHSTART 0 #define WAITHASH 1 #define WAITOK 2 #define AUTHOK 3 /*-----------------------------------------------------------------------*/ const char *AstriumWS(mxml_node_t* node, int where) { /* * do not want whitespace in there anywhere */ return NULL; } /*-----------------------------------------------------------------------*/ void ptToKomma(char *txt) { int i; for(i = 0; i < strlen(txt); i++){ if(txt[i] == '.'){ txt[i] = ','; } } } /*-----------------------------------------------------------------------*/ void kommaToPt(char *txt) { int i; for(i = 0; i < strlen(txt); i++){ if(txt[i] == ','){ txt[i] = '.'; } } } /*-----------------------------------------------------------------------*/ static void encodeXML(Ascon *a) { char *pPtr, *equal, *close; mxml_node_t *root, *msg, *sys, *param; char token[132]; pPtr = GetCharArray(a->wrBuffer); root = mxmlNewXML("1.0"); msg = mxmlNewElement(root,"Msg"); /* * The first element must be the system name */ pPtr = stptok(pPtr,token,sizeof(token),":"); sys = mxmlNewElement(msg,"SysName"); mxmlNewText(sys,0,token); param = mxmlNewElement(msg,"Param"); while((pPtr = stptok(pPtr,token,sizeof(token),":")) != NULL){ equal = strchr(token,(int)'='); if(equal != NULL){ *equal = '\0'; equal++; /* ptToKomma(equal);*/ mxmlElementSetAttr(param,token,equal); } else { mxmlElementSetAttr(param,token,NULL); } } mxmlSetWrapMargin(0); pPtr = mxmlSaveAllocString(root,AstriumWS); /* * Skip the string */ close = strchr(pPtr,(int)'>'); DynStringCopy(a->wrBuffer,"#CRQ#"); DynStringConcat(a->wrBuffer,trim(close+1)); free(pPtr); mxmlDelete(root); } /*-----------------------------------------------------------------------*/ static int decodeXML(Ascon *a) { pDynString xmlBuffer; mxml_node_t *root, *param; mxml_attr_t attr; int i; /* * prepend the XML prefix to make mxml a happy library */ xmlBuffer = CreateDynString(128,128); DynStringCopy(xmlBuffer,""); DynStringConcat(xmlBuffer,GetCharArray(a->rdBuffer)+5); root = mxmlLoadString(NULL,GetCharArray(xmlBuffer),MXML_TEXT_CALLBACK); if(root == NULL){ AsconError(a,"Astrium returned invalid XML",0); return 0; } DynStringClear(a->rdBuffer); param = mxmlFindElement(root,root,"Param",NULL,NULL,MXML_DESCEND); if(param == NULL){ AsconError(a,"No Param node found",0); return 0; } for(i = 0; i < param->value.element.num_attrs; i++){ attr = param->value.element.attrs[i]; DynStringConcat(a->rdBuffer,attr.name); if(attr.value != NULL){ DynStringConcat(a->rdBuffer,"="); kommaToPt(attr.value); DynStringConcat(a->rdBuffer,attr.value); } DynStringConcat(a->rdBuffer,":"); } DeleteDynString(xmlBuffer); mxmlDelete(root); return 1; } /*----------------------------------------------------------------------- This code and the function below is sensitive to int sizes. The hash must be a 64bit int. This is long on OSX and long long on SL6 32 bit. Setting this to int64_t as defined in stdint.h does not solve the problem because of the conversions to and from strings involved. ------------------------------------------------------------------------- */ static long long calculateToken(char *password, long long hash) { long long tmp; int i; assert(sizeof(hash) == 8); tmp = hash ^ 0x80AA80AA; for(i = 0; i < strlen(password); i++){ tmp += password[i] * (long long)pow(2,i); tmp -= password[i] * (long long)pow(2,31-i); } return tmp ^ hash; } /*------------------------------------------------------------------------*/ static int doWaitHash(Ascon *a) { pAstriumnet priv = NULL; int ret; char chr; long long hash; char token[50], *pPtr, *endptr; priv = (pAstriumnet)a->private; ret = AsconReadChar(a->fd,&chr); if(ret < 0){ AsconError(a, "ASC5", errno); return 0; } if(ret == 1 && (int)chr != 0x16) { DynStringConcatChar(a->rdBuffer,chr); } if(strstr(GetCharArray(a->rdBuffer),"") != NULL ) { pPtr = GetCharArray(a->rdBuffer); pPtr += 6; endptr = strchr(pPtr,(int)'<'); *endptr = '\0'; hash = strtoll(pPtr, &endptr,0); hash = calculateToken(priv->password,hash); DynStringCopy(priv->buffer,""); snprintf(token,sizeof(token),"%lld",hash); DynStringConcat(priv->buffer,token); ret = AsconWriteChars(a->fd, GetCharArray(priv->buffer), GetDynStringLength(priv->buffer)); if (ret < 0) { AsconError(a, "ASC4", errno); /* sets state to AsconFailed */ return 0; } DynStringClear(a->rdBuffer); priv->authState = WAITOK; } return 1; } /*------------------------------------------------------------------------*/ static int doWaitOK(Ascon *a) { pAstriumnet priv = NULL; int ret; char chr; priv = (pAstriumnet)a->private; ret = AsconReadChar(a->fd,&chr); if(ret < 0){ AsconError(a, "ASC5", errno); return 0; } if(ret == 1 && (int)chr != 0x16) { DynStringConcatChar(a->rdBuffer,chr); } if(strchr(GetCharArray(a->rdBuffer),(int)'>') != NULL) { if(strstr(GetCharArray(a->rdBuffer),"AuthOK") == NULL){ AsconError(a,"Token authorisation failed",0); return 0; } priv->authState = AUTHOK; a->state = AsconConnectDone; } return 1; } /*------------------------------------------------------------------------*/ static int AstriumnetHandler(Ascon *a) { pAstriumnet priv = NULL; int ret; char chr; priv = (pAstriumnet)a->private; switch(a->state){ case AsconConnectStart: AsconBaseHandler(a); if(a->state == AsconConnecting){ DynStringCopy(priv->buffer,""); priv->authState = AUTHSEND; return 1; } else { return 0; } break; case AsconConnecting: if(priv->authState == AUTHSEND){ ret = AsconWriteChars(a->fd, GetCharArray(priv->buffer), GetDynStringLength(priv->buffer)); if (ret < 0) { if(errno == EINPROGRESS){ return 1; } AsconError(a, "ASC4", errno); /* sets state to AsconFailed */ return 0; } priv->authState = WAITHASH; DynStringClear(a->rdBuffer); return 1; } else if(priv->authState == WAITHASH){ return doWaitHash(a); } else if(priv->authState == WAITOK){ return doWaitOK(a); } else { AsconError(a,"Invalid authentication state",0); return 0; } break; case AsconWriteStart: encodeXML(a); a->state = AsconWriting; a->wrPos = 0; return 1; break; case AsconReading: ret = AsconReadChar(a->fd,&chr); if(ret < 0){ AsconError(a, "ASC5", errno); return 0; } if(ret == 1 && (int)chr != 0x16) { DynStringConcatChar(a->rdBuffer,chr); } if(strstr(GetCharArray(a->rdBuffer),"") != NULL) { decodeXML(a); a->state = AsconReadDone; } return 1; break; } return AsconBaseHandler(a); } /*------------------------------------------------------------------------*/ static int AstriumnetInit(Ascon * a, SConnection * con, int argc, char *argv[]) { pAstriumnet priv = NULL; if(argc < 3){ SCPrintf(con,eError,"ERROR: missing password argument to Astriumnet"); return 0; } priv = calloc(sizeof(Astriumnet), 1); a->fd = -1; a->state = AsconConnectStart; a->reconnectInterval = 10; a->hostport = strdup(argv[1]); a->private = priv; a->killPrivate = free; strncpy(priv->password,argv[2],sizeof(priv->password)); priv->authState = AUTHSTART; priv->buffer = CreateDynString(128,128); if(priv->buffer == NULL){ return 0; } return 1; } /*------------------------------------------------------------------------*/ void AddAstriumnetProtocoll() { AsconProtocol *prot = NULL; prot = calloc(sizeof(AsconProtocol), 1); prot->name = strdup("astriumnet"); prot->init = AstriumnetInit; prot->handler = AstriumnetHandler; AsconInsertProtocol(prot); }