//---------------------------------------------------------------------------- // // Copyright (C) Intel Corporation, 2003 - 2005. // // File: httpDigest.cpp // // Contents: Sample code for a gSOAP plugin to implement HTTP Digest // authentication. // // Limitations: // - MIME, DIME and HTTP chunks (SOAP_IO_CHUNK) are not supported. // - This implementationn will internally buffer the entire outgoing // message before sending // - This implementation will fail if challenge isn't received within // SOAP_BUFLEN bytes read. // - This implementation will fail if challenge or response are larger // than the constants we used. // - This implementation calculates the digest response for each call // and doesn't save information. // - This implementation assumes that the algorithm is MD5 and that // qop="auth". // // Usage: Add the httpDigest.h and httpDigest.cpp files to your project // // In your source, just after calling soap_init(), register this // plugin with soap_register_plugin( soap, http_digest ). // Use soap.userid and soap.passwd for the username and password. // As in gSOAP, username and password have to be provided for each call. // // e.g. // struct soap soap; // soap_init( &soap ); // soap_register_plugin( &soap, http_digest ); // soap.userid = "admin"; // soap.passwd = "admin"; // ... // soap_done(&soap); // //---------------------------------------------------------------------------- #include "httpDigest.h" #include "digcalc.h" #define HDR_SIZE 1024 const char HTTP_DIGEST_ID[12] = "HTTP-Digest"; // plugin identification struct http_digest_data { struct soap *soap; // back pointer // send buffer parameters char *sendBuffer; // send buffer size_t sendMsgSize; // total length of the message size_t sendBufferLength; // length of data in buffer // receive buffer parameters char *rcvBuffer; // receive buffer size_t rcvSize; // length of buffer size_t rcvIdx; // currecnt index size_t rcvRead; // amount of data read // open parameters char *endpoint; char *host; int port; // function pointers int (*fopen)(struct soap*, const char*, const char*, int); // open function int (*fsend)(struct soap*, const char*, size_t); // send function size_t (*frecv)(struct soap*, char*, size_t); // receive function int (*fposthdr)(struct soap*, const char*, const char*); // post header function char *username; char *password; char requestUrl[128]; char method[5]; }; static int http_digest_init(struct soap *soap, struct http_digest_data *data); static int http_digest_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src); static void http_digest_delete(struct soap *soap, struct soap_plugin *p); static int http_digest_fopen(struct soap *soap, const char *endpoint, const char *host, int port); static int http_digest_fsend(struct soap *soap, const char *buf, size_t size); static size_t http_digest_frecv(struct soap *soap, char *buf, size_t size); static int http_digest_fposthdr(struct soap *soap, const char *key, const char *value); /* * The entry point for HTTP digest plugin * Arguments: * soap - pointer to the soap runtime environment * p - pointer to the soap plugin * arg - reserved. Should be NULL * * Returns SOAP_OK for suceess. Other values for error */ int http_digest(struct soap *soap, struct soap_plugin *p, void *arg) { p->id = HTTP_DIGEST_ID; p->data = (void*)malloc(sizeof(struct http_digest_data)); p->fcopy = http_digest_copy; p->fdelete = http_digest_delete; if (p->data) { memset(p->data, 0, sizeof(struct http_digest_data)); if (http_digest_init(soap, (struct http_digest_data*)p->data)) { free(p->data); return SOAP_EOM; } return SOAP_OK; } return SOAP_EOM; } /* * Initializes the http digest data structure. * Arguments: * soap - pointer to the soap runtime environment * data - pointer to the http digest data structure * * Returns SOAP_OK for suceess. Other values for error */ static int http_digest_init(struct soap *soap, struct http_digest_data *data) { data->soap = soap; data->fopen = soap->fopen; soap->fopen = http_digest_fopen; data->fsend = soap->fsend; soap->fsend = http_digest_fsend; data->frecv = soap->frecv; soap->frecv = http_digest_frecv; data->fposthdr = soap->fposthdr; soap->fposthdr = http_digest_fposthdr; return SOAP_OK; } /* * Creates the HTTP digest response * Arguments: * userName - the user name * password - the password * method - the HTTP method ("GET", "POST) * realm - the realm for the authentication * uri - the URI from the HTTP request * nonce - the nonce from the challenge * cnonce - client generated nonce * digestResponse - The result authorization string * length - size of buffer for response * * Returns 0 for suceess. -1 for error */ int CalculateResponse(char *userName, char *password, char *method, char *realm, char *uri, char *nonce, char *cnonce, char *digestResponse, size_t *length) { size_t currOffset = 0, segmentLength; static const char *INITIAL_HDR = "Authorization: Digest username=\""; static const char *REALEM_HDR = "\", realm=\""; static const char *ALGO_HDR = "\", qop=\"auth\", algorithm=\"MD5\", uri=\""; static const char *NONCE_HDR = "\", nonce=\""; static const char *NC_HDR = "\", nc=00000002, cnonce=\""; static const char *RSP_HDR = "\", response=\""; HASHHEX HA1; HASHHEX HA2 = ""; HASHHEX response; //"Authorization: Digest username=" segmentLength = strlen(INITIAL_HDR); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, INITIAL_HDR, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username segmentLength = strlen(userName); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, userName, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm=" segmentLength = strlen(REALEM_HDR); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, REALEM_HDR, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="realm segmentLength = strlen(realm); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, realm, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri=" segmentLength = strlen(ALGO_HDR); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, ALGO_HDR, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service segmentLength = strlen(uri); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, uri, segmentLength); currOffset+= segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service", nonce=" segmentLength = strlen(NONCE_HDR); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, NONCE_HDR, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service", nonce="7a5c... segmentLength = strlen(nonce); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, nonce, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service", nonce="7a5c...", nc=00000002, //cnonce=" segmentLength = strlen(NC_HDR); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, NC_HDR, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service", nonce="7a5c...", nc=00000002, //cnonce="ab341... segmentLength = strlen(cnonce); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, cnonce, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service", nonce="7a5c...", nc=00000002, //cnonce="ab341...", response=" segmentLength = strlen(RSP_HDR); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, RSP_HDR, segmentLength); currOffset += segmentLength; //calc response DigestCalcHA1("MD5", userName, realm, password, nonce, cnonce, HA1); DigestCalcResponse(HA1, nonce, "00000002", cnonce, "auth", method, uri, HA2, response); //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service", nonce="7a5c...", nc=00000002, //cnonce="ab341...", response="8bbf2... segmentLength = strlen(response); if (*length < (currOffset + segmentLength)) { return -1; } memcpy(digestResponse + currOffset, response, segmentLength); currOffset += segmentLength; //"Authorization: Digest username="username", realm="myRealm", qop="auth", //algorithm="MD5", uri="/....Service", nonce="7a5c...", nc=00000002, //cnonce="ab341...", response="8bbf2..." if (*length < (currOffset + 2)) { return -1; } memcpy(digestResponse + currOffset, "\"", 1); currOffset += 1; //add null termination *(digestResponse+currOffset) = 0; *length = currOffset; return 0; } /* * generate a 32 byte random hexadecimal string such as "4f6ba982..." * Arguments: * outbuff - buffer to fill */ void GenerateCNonce(char *outbuff) { srand((unsigned int)time(NULL)); for(int i = 0; i < 32; i++) { int num = (int)(((double) rand()/ ((double)RAND_MAX+1)) * 16); switch(num) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: outbuff[i] = '0' + num; break; case 10: case 11: case 12: case 13: case 14: case 15: outbuff[i] = 'a' + (num-10); break; default: outbuff[i] = 'f'; } } outbuff[32] = 0; } /* * Creates the HTTP digest response * Arguments: * data - the HTTP digest structure * authHeader - the HTTP digest challenge * responseLength - size of response buffer * digestResponse - buffer for HTTP digest response * * Returns 0 for suceess. -1 for error */ int CreateDigestResponse(struct http_digest_data *data, char *authHeader, size_t *responseLength, char *digestResponse) { char cnonce[33]; char *realmPtrStart, *realmPtrEnd, *noncePtrStart, *noncePtrEnd; size_t segmentLength; char realm[HDR_SIZE], nonce[HDR_SIZE];; if (digestResponse == NULL || authHeader == NULL) { return -1; } GenerateCNonce(cnonce); //grep realm from challange realmPtrStart = strstr(authHeader, "realm="); if (realmPtrStart == NULL) { return -1; } //point to start of realm realmPtrStart += 7; realmPtrEnd = strstr(realmPtrStart, "\""); segmentLength = realmPtrEnd - realmPtrStart; memcpy(realm, realmPtrStart, segmentLength); //add NULL termination realm[segmentLength] = 0; //grep nonce from challange noncePtrStart = strstr(authHeader, "nonce="); if (noncePtrStart == NULL) { return -1; } //point to start of nonce noncePtrStart += 7; noncePtrEnd = strstr(noncePtrStart, "\""); segmentLength = noncePtrEnd - noncePtrStart; memcpy(nonce, noncePtrStart, segmentLength); //add NULL termination nonce[segmentLength]=0; return CalculateResponse(data->username, data->password, data->method, realm, data->requestUrl, nonce, cnonce,digestResponse, responseLength); } /* * Copies the contents of the plugin * Arguments: * soap - pointer to the soap runtime environment * dst - the destination plugin * src - the original plugin * * Returns SOAP_OK for suceess. Error value for error */ static int http_digest_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src) { *dst = *src; dst->data = (void*)malloc(sizeof(struct http_digest_data)); if (!dst->data) { return SOAP_EOM; } memcpy(dst->data, src->data, sizeof(struct http_digest_data)); ((struct http_digest_data*)dst->data)->sendBuffer = NULL; return SOAP_OK; } /* * Deletes the contents of the plugin * Arguments: * soap - pointer to the soap runtime environment * p - the plugin */ static void http_digest_delete(struct soap *soap, struct soap_plugin *p) { struct http_digest_data *data = (struct http_digest_data*)soap_lookup_plugin(soap, HTTP_DIGEST_ID); if (data) { if (data->sendBuffer) { free(data->sendBuffer); } free(data); } } /* * Open function. Will be called when connection opened. Used for saving parameters. * Arguments: * soap - pointer to the soap runtime environment * endpoint - the URL * host - machine to connect to * port - port on the host * * Returns SOAP_OK for suceess. Error value for error */ static int http_digest_fopen(struct soap *soap, const char *endpoint, const char *host, int port) { struct http_digest_data *data = (struct http_digest_data *)soap_lookup_plugin(soap, HTTP_DIGEST_ID); data->endpoint = (char*)endpoint; data->host = (char*)host; data->port = port; return data->fopen(soap, endpoint, host, port); } /* * Post header function. Used to identify parameters of the HTTP message * Arguments: * soap - pointer to the soap runtime environment * key - the header key * value - the header value * * Returns SOAP_OK for suceess. Error value for error */ static int http_digest_fposthdr(struct soap *soap, const char *key, const char *value) { struct http_digest_data *data = (struct http_digest_data *)soap_lookup_plugin(soap, HTTP_DIGEST_ID); char *s1, *s2; // if this is the initial POST header then we initialize our send buffer if (key && !value) { data->sendMsgSize = 0; data->sendBufferLength = 0; if (data->sendBuffer) { free(data->sendBuffer); data->sendBuffer = NULL; } data->password = soap->passwd; data->username = soap->userid; soap->passwd = soap->userid = NULL; soap->frecv = http_digest_frecv; // read the method and URI form the key if (!strncmp(key, "GET", 3)) memcpy(data->method, "GET", 4); else if (!strncmp(key, "POST", 4)) memcpy(data->method, "POST", 5); else return soap->error = SOAP_EOM; s1 = strstr(key, " "); if (!s1) { return soap->error = SOAP_EOM; } s1++; s2 = strstr(s1, " "); if (!s2) { return soap->error = SOAP_EOM; } if (sizeof(data->requestUrl) <= (size_t)(s2 - s1)) { return soap->error = SOAP_EOM; } memcpy(data->requestUrl, s1, s2-s1); data->requestUrl[s2-s1] = '\0'; } else if (value) { // determine the maximum length of this message so that we can // correctly determine when we have completed the send if (!strcmp(key, "Content-Length" )) { data->sendMsgSize += strtoul(value, NULL, 10); } } // calculate the header size data->sendMsgSize += 2; if (key) { data->sendMsgSize += strlen(key); if (value) { data->sendMsgSize += (strlen(value) + 2); } } return data->fposthdr(soap, key, value); } /* * Send function. Used to buffer the sent data and save for resends * Arguments: * soap - pointer to the soap runtime environment * buf - buffer to be sent * size - size of data to send * * Returns SOAP_OK for suceess. Error value for error */ static int http_digest_fsend(struct soap *soap, const char *buf, size_t size) { struct http_digest_data *data = (struct http_digest_data *)soap_lookup_plugin(soap, HTTP_DIGEST_ID); size_t newBufferLen = data->sendBufferLength + size; if (!data->sendBuffer || (newBufferLen > data->sendMsgSize)) { if (newBufferLen > data->sendMsgSize) { data->sendMsgSize = newBufferLen; } data->sendBuffer = (char *)realloc(data->sendBuffer, data->sendMsgSize); if (!data->sendBuffer) { return SOAP_EOM; } } memcpy(data->sendBuffer + data->sendBufferLength, buf, size); data->sendBufferLength = newBufferLen; // if we haven't got the entire length of the message yet, then // we return to gsoap and let it continue if (data->sendBufferLength < data->sendMsgSize) { return SOAP_OK; } // we've now got the entire message, now we can send the buffer return data->fsend(soap, data->sendBuffer, data->sendBufferLength); } /* * Reads the next character. May need to read from the network * Arguments: * data - pointer to the http digest structure * * Returns the next character or EOF for failure. */ static char http_digest_getchar(struct http_digest_data *data) { size_t res; if (data->rcvIdx < data->rcvRead) return data->rcvBuffer[data->rcvIdx++]; res = data->frecv(data->soap, (data->rcvBuffer + data->rcvRead), (data->rcvSize - data->rcvRead)); if (res <= 0) return EOF; data->rcvRead += res; return data->rcvBuffer[data->rcvIdx++]; } /* * Reads the next HTTP header line. * Arguments: * data - pointer to the http digest structure * line - buffer to store the line read * len - length of the line buffer * * Returns SOAP_OK for suceess. Error value for error. */ static int http_digest_getline(struct http_digest_data *data, char *line, size_t len) { unsigned int i = len; char c = 0, *s = line; for (;;) { while (--i > 0) { c = http_digest_getchar(data); if (c == '\r') break; if (c == EOF) return SOAP_EOF; *s++ = c; } c = http_digest_getchar(data); if (c == '\n') { *s = '\0'; if (i+1 == len) // empty line: end of HTTP header break; c = http_digest_getchar(data); data->rcvIdx--; // return to previous character if (c != ' ' && c != '\t') // HTTP line continuation? break; } else if ((int)c == EOF) return SOAP_EOF; } return SOAP_OK; } /* * receive function. Used to look for digest authentication challenge. * If the challenge is found will calculate the response and resend. * Arguments: * soap - pointer to the soap runtime environment * buf - buffer read data into * size - size of buf * * Returns number of characters read. 0 for error. */ static size_t http_digest_frecv(struct soap *soap, char *buf, size_t size) { struct http_digest_data *data = (struct http_digest_data *)soap_lookup_plugin(soap, HTTP_DIGEST_ID); char header[HDR_SIZE], authResponse[HDR_SIZE]; static const char *CHALLANGE = "WWW-Authenticate: Digest"; static const unsigned int SYN_DELAY = 400000; char *s; unsigned long httpStatus; size_t len; int found = 0; data->rcvBuffer = buf; data->rcvSize = size; data->rcvIdx = 0; data->rcvRead = 0; do { if (http_digest_getline(data, header, HDR_SIZE)) goto _out; if ((s = strchr(header, ' '))) httpStatus = soap_strtoul(s, NULL, 10); else httpStatus = 0; if ((httpStatus != 100) && (httpStatus != 401)) goto _out; for (;;) { if (http_digest_getline(data, header, SOAP_HDRLEN)) goto _out; if (!*header) break; if ((httpStatus == 401) && !strncmp(header, CHALLANGE, strlen(CHALLANGE))) { found = 1; break; } } } while (httpStatus == 100); // if we got here httpStatus==401 if (!found) goto _out; // header is HTTP digest challenge len = HDR_SIZE; if (CreateDigestResponse(data, header, &len, authResponse)) goto _out; s = strstr(data->sendBuffer, "\r\n"); if (!s) goto _out; s += 2; // point to the start of second line // reset rcvRead so that if error occurs will return 0. data->rcvRead = 0; //delay sending SYN to allow AMT to close connection gracefully usleep(SYN_DELAY); soap->socket = data->fopen(soap, data->endpoint, data->host, data->port); if (soap->error || !soap_valid_socket(soap->socket)) goto _out; if (data->fsend(soap, data->sendBuffer, s-data->sendBuffer) || data->fsend(soap, authResponse, len) || data->fsend(soap, "\r\n", 2) || data->fsend(soap, s, data->sendBufferLength - (s-data->sendBuffer))) goto _out; // after send - send FIN if needed #ifdef WITH_OPENSSL if (!soap->ssl && soap_valid_socket(soap->socket) && !soap->keep_alive) soap->fshutdownsocket(soap, (SOAP_SOCKET)soap->socket, 1); // Send TCP FIN #else if (soap_valid_socket(soap->socket) && !soap->keep_alive) soap->fshutdownsocket(soap, (SOAP_SOCKET)soap->socket, 1); // Send TCP FIN #endif data->rcvRead = data->frecv(soap, buf, size); _out: if (data->sendBuffer) { free(data->sendBuffer); data->sendBuffer = NULL; } soap->frecv = data->frecv; return data->rcvRead; }