/* --- BEGIN COPYRIGHT BLOCK --- * This Program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; version 2 of the License. * * This Program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. * * In addition, as a special exception, Red Hat, Inc. gives You the additional * right to link the code of this Program with code not covered under the GNU * General Public License ("Non-GPL Code") and to distribute linked combinations * including the two, subject to the limitations in this paragraph. Non-GPL Code * permitted under this exception must only link to the code of this Program * through those well defined interfaces identified in the file named EXCEPTION * found in the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline functions from * the Approved Interfaces without causing the resulting work to be covered by * the GNU General Public License. Only Red Hat, Inc. may make changes or * additions to the list of Approved Interfaces. You must obey the GNU General * Public License in all respects for all of the Program code and other code used * in conjunction with the Program except the Non-GPL Code covered by this * exception. If you modify this file, you may extend this exception to your * version of the file, but you are not obligated to do so. If you do not wish to * provide this exception without modification, you must delete this exception * statement from your version and license this file solely under the GPL without * exception. * * * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. * Copyright (C) 2005 Red Hat, Inc. * All rights reserved. * --- END COPYRIGHT BLOCK --- */ #ifdef HAVE_CONFIG_H # include #endif /** * Implementation of a Simple HTTP Client */ #include #include #include "nspr.h" #include "nss.h" #include "pk11func.h" #include "ssl.h" #include "prprf.h" #include "plstr.h" #include "slapi-plugin.h" #include "http_client.h" #include "secerr.h" #include "sslerr.h" #include "slap.h" #include "slapi-private.h" #include "slapi-plugin-compat4.h" /* get file mode flags for unix */ #ifndef _WIN32 #include #endif /*** from proto-slap.h ***/ int slapd_log_error_proc( char *subsystem, char *fmt, ... ); char *config_get_instancedir(); /*** from ldaplog.h ***/ /* edited ldaplog.h for LDAPDebug()*/ #ifndef _LDAPLOG_H #define _LDAPLOG_H #ifdef __cplusplus extern "C" { #endif #ifdef BUILD_STANDALONE #define slapi_log_error(a,b,c,d) printf((c),(d)) #define stricmp strcasecmp #endif #define LDAP_DEBUG_TRACE 0x00001 /* 1 */ #define LDAP_DEBUG_ANY 0x04000 /* 16384 */ #define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */ /* debugging stuff */ # ifdef _WIN32 extern int *module_ldap_debug; # define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ { \ if ( *module_ldap_debug & level ) { \ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ } \ } # else /* _WIN32 */ extern int slapd_ldap_debug; # define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \ { \ if ( slapd_ldap_debug & level ) { \ slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \ } \ } # endif /* Win32 */ #ifdef __cplusplus } #endif #endif /* _LDAP_H */ #define HTTP_PLUGIN_SUBSYSTEM "http-client-plugin" /* used for logging */ #define HTTP_IMPL_SUCCESS 0 #define HTTP_IMPL_FAILURE -1 #define HTTP_REQ_TYPE_GET 1 #define HTTP_REQ_TYPE_REDIRECT 2 #define HTTP_REQ_TYPE_POST 3 #define HTTP_GET "GET" #define HTTP_POST "POST" #define HTTP_PROTOCOL "HTTP/1.0" #define HTTP_CONTENT_LENGTH "Content-length:" #define HTTP_CONTENT_TYPE_URL_ENCODED "Content-type: application/x-www-form-urlencoded" #define HTTP_GET_STD_LEN 18 #define HTTP_POST_STD_LEN 85 #define HTTP_DEFAULT_BUFFER_SIZE 4096 #define HTTP_RESPONSE_REDIRECT (retcode == 302 || retcode == 301) /** * Error strings used for logging error messages */ #define HTTP_ERROR_BAD_URL " Badly formatted URL" #define HTTP_ERROR_NET_ADDR " NetAddr initialization failed" #define HTTP_ERROR_SOCKET_CREATE " Creation of socket failed" #define HTTP_ERROR_SSLSOCKET_CREATE " Creation of SSL socket failed" #define HTTP_ERROR_CONNECT_FAILED " Couldn't connect to remote host" #define HTTP_ERROR_SEND_REQ " Send request failed" #define HTTP_ERROR_BAD_RESPONSE " Invalid response from remote host" #define HTTP_PLUGIN_DN "cn=HTTP Client,cn=plugins,cn=config" #define CONFIG_DN "cn=config" #define ATTR_CONNECTION_TIME_OUT "nsHTTPConnectionTimeOut" #define ATTR_READ_TIME_OUT "nsHTTPReadTimeOut" #define ATTR_RETRY_COUNT "nsHTTPRetryCount" #define ATTR_DS_SECURITY "nsslapd-security" #define ATTR_INSTANCE_PATH "nsslapd-errorlog" /*static Slapi_ComponentId *plugin_id = NULL;*/ typedef struct { int retryCount; int connectionTimeOut; int readTimeOut; int nssInitialized; char *DS_sslOn; } httpPluginConfig; httpPluginConfig *httpConfig; /** * Public functions */ int http_impl_init(Slapi_ComponentId *plugin_id); int http_impl_get_text(char *url, char **data, int *bytesRead); int http_impl_get_binary(char *url, char **data, int *bytesRead); int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead); int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead); void http_impl_shutdown(); /** * Http handling functions */ static int doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType); static int doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType); static void setTCPNoDelay(PRFileDesc* fd); static PRStatus sendGetReq(PRFileDesc *fd, const char *path); static PRStatus sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body); static PRStatus processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType); static PRStatus getChar(PRFileDesc *fd, char *buf); static PRInt32 http_read(PRFileDesc *fd, char *buf, int size); static PRStatus getBody(PRFileDesc *fd, char **buf, int *actualBytesRead); static PRBool isWhiteSpace(char ch); static PRStatus sendFullData( PRFileDesc *fd, char *buf, int timeOut); /** * Helper functions to parse URL */ static PRStatus parseURI(const char *url, char **host, PRInt32 *port, char **path, int *sslOn); static void toLowerCase(char* str); static PRStatus parseAtPort(const char* url, PRInt32 *port, char **path); static PRStatus parseAtPath(const char *url, char **path); static PRInt32 getPort(const char* src); static PRBool isAsciiSpace(char aChar); static PRBool isAsciiDigit(char aChar); static char * isHttpReq(const char *url, int *sslOn); /*To get config from entry*/ static int readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn); static int parseHTTPConfigEntry(Slapi_Entry *e); static int parseConfigEntry(Slapi_Entry *e); /*SSL functions */ PRFileDesc* setupSSLSocket(PRFileDesc* fd); /*SSL callback functions */ SECStatus badCertHandler(void *arg, PRFileDesc *socket); SECStatus authCertificate(void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer); SECStatus getClientAuthData(void *arg, PRFileDesc *socket,struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey); SECStatus handshakeCallback(PRFileDesc *socket, void *arg); static int doRequestRetry(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType) { int status = HTTP_IMPL_SUCCESS; int retrycnt = 0; int i = 1; retrycnt = httpConfig->retryCount; if (retrycnt == 0) { LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Retry Count cannot be read. Setting to default value of 3 \n", 0,0,0); retrycnt = 3; } status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType); if (status != HTTP_IMPL_SUCCESS) { LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Failed to perform http request \n", 0,0,0); while (retrycnt > 0) { LDAPDebug( LDAP_DEBUG_PLUGIN, "doRequestRetry: Retrying http request %d.\n", i,0,0); status = doRequest(url, httpheaderArray, body, buf, bytesRead, reqType); if (status == HTTP_IMPL_SUCCESS) { break; } retrycnt--; i++; } if (status != HTTP_IMPL_SUCCESS) { LDAPDebug( LDAP_DEBUG_ANY, "doRequestRetry: Failed to perform http request after %d attempts.\n", i,0,0); LDAPDebug( LDAP_DEBUG_ANY, "doRequestRetry: Verify plugin URI configuration and contact Directory Administrator.\n",0,0,0); } } return status; } static int doRequest(const char *url, httpheader **httpheaderArray, char *body, char **buf, int *bytesRead, int reqType) { PRStatus status = PR_SUCCESS; char *host = NULL; char *path = NULL; PRFileDesc *fd = NULL; PRNetAddr addr; PRInt32 port; PRInt32 errcode = 0; PRInt32 http_connection_time_out = 0; PRInt32 sslOn; LDAPDebug( LDAP_DEBUG_PLUGIN, "--> doRequest -- BEGIN\n",0,0,0); LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> url=[%s] \n",url,0,0); /* Parse the URL and initialize the host, port, path */ if (parseURI(url, &host, &port, &path, &sslOn) == PR_FAILURE) { slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest: %s \n", HTTP_ERROR_BAD_URL); status = PR_FAILURE; goto bail; } LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> host=[%s] port[%d] path[%s] \n",host,port,path); /* Initialize the Net Addr */ if (PR_StringToNetAddr(host, &addr) == PR_FAILURE) { char buf[PR_NETDB_BUF_SIZE]; PRHostEnt ent; status = PR_GetIPNodeByName(host, PR_AF_INET, PR_AI_DEFAULT, buf, sizeof(buf), &ent); if (status == PR_SUCCESS) { PR_EnumerateHostEnt(0, &ent, (PRUint16)port, &addr); } else { slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest: %s\n", HTTP_ERROR_NET_ADDR); status = HTTP_CLIENT_ERROR_NET_ADDR; goto bail; } } else { addr.inet.port = (PRUint16)port; } LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully created NetAddr \n",0,0,0); /* open a TCP connection to the server */ fd = PR_NewTCPSocket(); if (!fd) { slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest: %s\n", HTTP_ERROR_SOCKET_CREATE); status = HTTP_CLIENT_ERROR_SOCKET_CREATE; goto bail; } LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully created New TCP Socket \n",0,0,0); /* immediately send the response */ setTCPNoDelay(fd); if (sslOn) { fd = setupSSLSocket(fd); if (fd == NULL) { slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest: %s\n", HTTP_ERROR_SSLSOCKET_CREATE); status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE; goto bail; } if (SSL_SetURL(fd, host) != 0) { errcode = PR_GetError(); slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest: SSL_SetURL -> NSPR Error code (%d) \n", errcode); status = HTTP_CLIENT_ERROR_SSLSOCKET_CREATE; goto bail; } } http_connection_time_out = httpConfig->connectionTimeOut; /* connect to the host */ if (PR_Connect(fd, &addr, PR_MillisecondsToInterval(http_connection_time_out)) == PR_FAILURE) { errcode = PR_GetError(); slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest: %s (%s:%d) -> NSPR Error code (%d)\n", HTTP_ERROR_CONNECT_FAILED, host, addr.inet.port, errcode); status = HTTP_CLIENT_ERROR_CONNECT_FAILED; goto bail; } LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully connected to host [%s] \n",host,0,0); /* send the request to the server */ if (reqType == HTTP_REQ_TYPE_POST) { if (sendPostReq(fd, path, httpheaderArray, body) == PR_FAILURE) { slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest-sendPostReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path); status = HTTP_CLIENT_ERROR_SEND_REQ; goto bail; } } else { if (sendGetReq(fd, path) == PR_FAILURE) { slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest-sendGetReq: %s (%s)\n", HTTP_ERROR_SEND_REQ, path); status = HTTP_CLIENT_ERROR_SEND_REQ; goto bail; } } LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully sent the request [%s] \n",path,0,0); /* read the response */ if (processResponse(fd, buf, bytesRead, reqType) == PR_FAILURE) { slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "doRequest: %s (%s)\n", HTTP_ERROR_BAD_RESPONSE, url); status = HTTP_CLIENT_ERROR_BAD_RESPONSE; goto bail; } LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Successfully read the response\n",0,0,0); bail: if (host) { PR_Free(host); } if (path) { PR_Free(path); } if (fd) { PR_Close(fd); fd = NULL; } LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- doRequest -- END\n",0,0,0); return status; } static PRStatus processResponse(PRFileDesc *fd, char **resBUF, int *bytesRead, int reqType) { PRStatus status = PR_SUCCESS; char *location = NULL; char *protocol = NULL; char *statusNum = NULL; char *statusString = NULL; char *headers = NULL; char tmp[HTTP_DEFAULT_BUFFER_SIZE]; int pos=0; char ch; int index; int retcode; PRBool doneParsing = PR_FALSE; PRBool isRedirect = PR_FALSE; char name[HTTP_DEFAULT_BUFFER_SIZE]; char value[HTTP_DEFAULT_BUFFER_SIZE]; PRBool atEOL = PR_FALSE; PRBool inName = PR_TRUE; /* PKBxxx: If we are getting a redirect and the response is more the * the HTTP_DEFAULT_BUFFER_SIZE, it will cause the server to crash. A 4k * buffer should be good enough. */ LDAPDebug( LDAP_DEBUG_PLUGIN, "--> processResponse -- BEGIN\n",0,0,0); headers = (char *)PR_Calloc(1, 4 * HTTP_DEFAULT_BUFFER_SIZE); /* Get protocol string */ index = 0; while (1) { status = getChar(fd, headers+pos); if (status == PR_FAILURE) { /* Error : */ goto bail; } ch = (char)headers[pos]; pos++; if (!isWhiteSpace(ch)) { tmp[index++] = ch; } else { break; } } tmp[index] = '\0'; protocol = PL_strdup(tmp); LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> protocol=[%s] \n",protocol,0,0); /* Get status num */ index = 0; while (1) { status = getChar(fd, headers+pos); if (status == PR_FAILURE) { /* Error : */ goto bail; } ch = (char)headers[pos]; pos++; if (!isWhiteSpace(ch)) { tmp[index++] = ch; } else { break; } } tmp[index] = '\0'; statusNum = PL_strdup(tmp); retcode=atoi(tmp); LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> statusNum=[%s] \n",statusNum,0,0); if (HTTP_RESPONSE_REDIRECT && (reqType == HTTP_REQ_TYPE_REDIRECT)) { isRedirect = PR_TRUE; } /* Get status string */ if (ch != '\r') { index = 0; while (ch != '\r') { status = getChar(fd, headers+pos); if (status == PR_FAILURE) { /* Error : */ goto bail; } ch = (char)headers[pos]; pos++; tmp[index++] = ch; } tmp[index] = '\0'; statusString = PL_strdup(tmp); LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> statusString [%s] \n",statusString,0,0); } /** * Skip CRLF */ status = getChar(fd, headers+pos); if (status == PR_FAILURE) { /* Error : */ goto bail; } ch = (char)headers[pos]; pos++; /** * loop over response headers */ index = 0; while (!doneParsing) { status = getChar(fd, headers+pos); if (status == PR_FAILURE) { /* Error : */ goto bail; } ch = (char)headers[pos]; pos++; switch(ch) { case ':': if (inName) { name[index] = '\0'; index = 0; inName = PR_FALSE; /* skip whitespace */ ch = ' '; /* status = getChar(fd, headers+pos); if (status == PR_FAILURE) { goto bail; } ch = (char)headers[pos]; pos++; */ while(isWhiteSpace(ch)) { status = getChar(fd, headers+pos); if (status == PR_FAILURE) { /* Error : */ goto bail; } ch = (char)headers[pos]; pos++; } value[index++] = ch; } else { value[index++] = ch; } break; case '\r': if (inName && !atEOL) { return PR_FALSE; } break; case '\n': if (atEOL) { doneParsing = PR_TRUE; break; } if (inName) { return PR_FALSE; } value[index] = '\0'; index = 0; inName = PR_TRUE; LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> name=[%s] value=[%s]\n",name,value,0); if (isRedirect && !PL_strcasecmp(name,"location")) { location = PL_strdup(value); } atEOL = PR_TRUE; break; default: atEOL = PR_FALSE; if (inName) { name[index++] = ch; } else { value[index++] = ch; } break; } } if (!isRedirect) { getBody(fd, resBUF, bytesRead); } else { *resBUF = PL_strdup(location); *bytesRead = strlen(location); } LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Response Buffer=[%s] bytesRead=[%d] \n",*resBUF,*bytesRead,0); bail: if (headers) { PR_Free(headers); } if (protocol) { PL_strfree(protocol); } if (statusNum) { PL_strfree(statusNum); } if (statusString) { PL_strfree(statusString); } if (location) { PL_strfree(location); } LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- processResponse -- END\n",0,0,0); return status; } static PRStatus sendGetReq(PRFileDesc *fd, const char *path) { PRStatus status = PR_SUCCESS; char *reqBUF = NULL; PRInt32 http_connection_time_out = 0; int buflen = (HTTP_GET_STD_LEN + strlen(path)); reqBUF = (char *)PR_Calloc(1, buflen); strcpy(reqBUF, HTTP_GET); strcat(reqBUF, " "); strcat(reqBUF, path); strcat(reqBUF, " "); strcat(reqBUF, HTTP_PROTOCOL); strcat(reqBUF, "\r\n\r\n\0"); http_connection_time_out = httpConfig->connectionTimeOut; status = sendFullData( fd, reqBUF, http_connection_time_out); if (reqBUF) { PR_Free(reqBUF); reqBUF = 0; } return status; } static PRStatus sendFullData( PRFileDesc *fd, char *buf, int timeOut) { int dataSent = 0; int bufLen = strlen(buf); int retVal = 0; PRInt32 errcode = 0; while (dataSent < bufLen) { retVal = PR_Send(fd, buf+dataSent, bufLen-dataSent, 0, PR_MillisecondsToInterval(timeOut)); if (retVal == -1 ) break; dataSent += retVal; } if (dataSent == bufLen ) return PR_SUCCESS; else { errcode = PR_GetError(); slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "sendFullData: dataSent=%d bufLen=%d -> NSPR Error code (%d)\n", dataSent, bufLen, errcode); LDAPDebug( LDAP_DEBUG_PLUGIN, "---------->NSPR Error code (%d) \n", errcode,0,0); return PR_FAILURE; } } static PRStatus sendPostReq(PRFileDesc *fd, const char *path, httpheader **httpheaderArray, char *body) { PRStatus status = PR_SUCCESS; char body_len_str[20]; char *reqBUF = NULL; PRInt32 http_connection_time_out = 0; int i = 0; int body_len, buflen = 0; if (body) { body_len = strlen(body); } else { body_len = 0; } PR_snprintf(body_len_str, sizeof(body_len_str), "%d", body_len); buflen = (HTTP_POST_STD_LEN + strlen(path) + body_len + strlen(body_len_str)); for (i = 0; httpheaderArray[i] != NULL; i++) { if (httpheaderArray[i]->name != NULL) { buflen += strlen(httpheaderArray[i]->name) + 2; if (httpheaderArray[i]->value != NULL) buflen += strlen(httpheaderArray[i]->value) + 2; } } reqBUF = (char *)PR_Calloc(1, buflen); strcpy(reqBUF, HTTP_POST); strcat(reqBUF, " "); strcat(reqBUF, path); strcat(reqBUF, " "); strcat(reqBUF, HTTP_PROTOCOL); strcat(reqBUF, "\r\n"); strcat(reqBUF, HTTP_CONTENT_LENGTH); strcat(reqBUF, " "); strcat(reqBUF, body_len_str); strcat(reqBUF, "\r\n"); strcat(reqBUF, HTTP_CONTENT_TYPE_URL_ENCODED); strcat(reqBUF, "\r\n"); for (i = 0; httpheaderArray[i] != NULL; i++) { if (httpheaderArray[i]->name != NULL) strcat(reqBUF, httpheaderArray[i]->name); strcat(reqBUF, ": "); if (httpheaderArray[i]->value != NULL) strcat(reqBUF, httpheaderArray[i]->value); strcat(reqBUF, "\r\n"); } strcat(reqBUF, "\r\n"); if (body) { strcat(reqBUF, body); } strcat(reqBUF, "\0"); LDAPDebug( LDAP_DEBUG_PLUGIN, "---------->reqBUF is %s \n",reqBUF,0,0); http_connection_time_out = httpConfig->connectionTimeOut; status = sendFullData( fd, reqBUF, http_connection_time_out); if (reqBUF) { PR_Free(reqBUF); reqBUF = 0; } return status; } static PRStatus getChar(PRFileDesc *fd, char *buf) { PRInt32 bytesRead = http_read(fd, buf, 1); if (bytesRead <=0) { PRInt32 errcode = PR_GetError(); slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "getChar: NSPR Error code (%d)\n", errcode); return PR_FAILURE; } return PR_SUCCESS; } static PRStatus getBody(PRFileDesc *fd, char **buf, int *actualBytesRead) { int totalBytesRead = 0; int size = 4 * HTTP_DEFAULT_BUFFER_SIZE; int bytesRead = size; char *data = (char *) PR_Calloc(1, size); while (bytesRead == size) { bytesRead = http_read(fd, (data+totalBytesRead), size); if (bytesRead <= 0) { /* Read error */ return PR_FAILURE; } if (bytesRead == size) { /* more data to be read so increase the buffer */ size = size * 2 ; data = (char *) PR_Realloc(data, size); } totalBytesRead += bytesRead; } *buf = data; *actualBytesRead = totalBytesRead; return PR_SUCCESS; } static PRInt32 http_read(PRFileDesc *fd, char *buf, int size) { PRInt32 http_read_time_out = 0; http_read_time_out = httpConfig->readTimeOut; return PR_Recv(fd, buf, size, 0, PR_MillisecondsToInterval(http_read_time_out)); } static PRBool isWhiteSpace(char ch) { PRBool b = PR_FALSE; if (ch == ' ') { b = PR_TRUE; } return b; } static PRStatus parseURI(const char *urlstr, char **host, PRInt32 *port, char **path, int *sslOn) { PRStatus status = PR_SUCCESS; char *brk; int len; static const char delimiters[] = ":/?#"; char *url = isHttpReq(urlstr, sslOn); if (*sslOn) { *port = 443; } else { *port = 80; } if (url == NULL) { /* Error : */ status = PR_FAILURE; goto bail; } len = PL_strlen(url); /* Currently we do not support Ipv6 addresses */ brk = PL_strpbrk(url, delimiters); if (!brk) { *host = PL_strndup(url, len); toLowerCase(*host); goto bail; } switch (*brk) { case '/' : case '?' : case '#' : /* Get the Host, the rest is Path */ *host = PL_strndup(url, (brk - url)); toLowerCase(*host); status = parseAtPath(brk, path); break; case ':' : /* Get the Host and process port, path */ *host = PL_strndup(url, (brk - url)); toLowerCase(*host); status = parseAtPort(brk+1, port, path); break; default: /* Error : HTTP_BAD_URL */ break; } bail: if (url) { PR_Free(url); } return status; } static PRStatus parseAtPort(const char* url, PRInt32 *port, char **path) { PRStatus status = PR_SUCCESS; static const char delimiters[] = "/?#"; char* brk = PL_strpbrk(url, delimiters); if (!brk) /* everything is a Port */ { *port = getPort(url); if (*port <= 0) { /* Error : HTTP_BAD_URL */ return PR_FAILURE; } else { return status; } } switch (*brk) { case '/' : case '?' : case '#' : /* Get the Port, the rest is Path */ *port = getPort(url); if (*port <= 0) { /* Error : HTTP_BAD_URL */ return PR_FAILURE; } status = parseAtPath(brk, path); break; default: /* Error : HTTP_BAD_URL */ break; } return status; } static PRStatus parseAtPath(const char *url, char **path) { PRStatus status = PR_SUCCESS; char *dir = "%s%s"; *path = (char *)PR_Calloc(1, strlen(dir) + strlen(url) + 2); /* Just write the path and check for a starting / */ if ('/' != *url) { sprintf(*path, dir, "/", url); } else { strcpy(*path, url); } if (!*path) { /* Error : HTTP_BAD_URL */ status = PR_FAILURE; } return status; } static void toLowerCase(char* str) { if (str) { char* lstr = str; PRInt8 shift = 'a' - 'A'; for(; (*lstr != '\0'); ++lstr) { if ((*(lstr) <= 'Z') && (*(lstr) >= 'A')) { *(lstr) = *(lstr) + shift; } } } } static PRInt32 getPort(const char* src) { /* search for digits up to a slash or the string ends */ const char* port = src; PRInt32 returnValue = -1; char c; /* skip leading white space */ while (isAsciiSpace(*port)) port++; while ((c = *port++) != '\0') { /* stop if slash or ? or # reached */ if (c == '/' || c == '?' || c == '#') break; else if (!isAsciiDigit(c)) return returnValue; } return (0 < PR_sscanf(src, "%d", &returnValue)) ? returnValue : -1; } static PRBool isAsciiSpace(char aChar) { if ((aChar == ' ') || (aChar == '\r') || (aChar == '\n') || (aChar == '\t')) { return PR_TRUE; } return PR_FALSE; } static PRBool isAsciiDigit(char aChar) { if ((aChar >= '0') && (aChar <= '9')) { return PR_TRUE; } return PR_FALSE; } static void setTCPNoDelay(PRFileDesc* fd) { PRStatus status = PR_SUCCESS; PRSocketOptionData opt; opt.option = PR_SockOpt_NoDelay; opt.value.no_delay = PR_FALSE; status = PR_GetSocketOption(fd, &opt); if (status == PR_FAILURE) { return; } opt.option = PR_SockOpt_NoDelay; opt.value.no_delay = PR_TRUE; status = PR_SetSocketOption(fd, &opt); if (status == PR_FAILURE) { return; } return; } static char * isHttpReq(const char *url, int *sslOn) { static const char http_protopol_header[] = "http://"; static const char https_protopol_header[] = "https://"; char *newstr = NULL; /* skip leading white space */ while (isAsciiSpace(*url)) url++; if (strncmp(url, http_protopol_header, strlen(http_protopol_header)) == 0) { newstr = (char *)PR_Calloc(1, (strlen(url)-strlen(http_protopol_header) + 1)); strcpy(newstr, url+7); strcat(newstr,"\0"); *sslOn = 0; } else if (strncmp(url, https_protopol_header, strlen(https_protopol_header)) == 0) { newstr = (char *)PR_Calloc(1, (strlen(url)-strlen(https_protopol_header) + 1)); strcpy(newstr, url+8); strcat(newstr,"\0"); *sslOn = 1; } return newstr; } PRFileDesc* setupSSLSocket(PRFileDesc* fd) { SECStatus secStatus; PRFileDesc* sslSocket; PRSocketOptionData socketOption; char *certNickname = NULL; socketOption.option = PR_SockOpt_Nonblocking; socketOption.value.non_blocking = PR_FALSE; if( PR_SetSocketOption(fd, &socketOption) != 0) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "Cannot set socket option NSS \n"); return NULL; } sslSocket = SSL_ImportFD(NULL, fd); if (!sslSocket) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: Cannot import to SSL Socket\n" ); goto sslbail; } slapi_log_error( SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: setupssl socket created\n" ); secStatus = SSL_OptionSet(sslSocket, SSL_SECURITY, 1); if (SECSuccess != secStatus) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: Cannot set SSL_SECURITY option\n"); goto sslbail; } secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, 1); if (SECSuccess != secStatus) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: CAnnot set SSL_HANDSHAKE_AS_CLIENT option\n"); goto sslbail; } /* Set SSL callback routines. */ secStatus = SSL_GetClientAuthDataHook(sslSocket, (SSLGetClientAuthData) getClientAuthData, (void *)certNickname); if (secStatus != SECSuccess) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: SSL_GetClientAuthDataHook Failed\n"); goto sslbail; } secStatus = SSL_AuthCertificateHook(sslSocket, (SSLAuthCertificate) authCertificate, (void *)CERT_GetDefaultCertDB()); if (secStatus != SECSuccess) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: SSL_AuthCertificateHook Failed\n"); goto sslbail; } secStatus = SSL_BadCertHook(sslSocket, (SSLBadCertHandler) badCertHandler, NULL); if (secStatus != SECSuccess) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: SSL_BadCertHook Failed\n"); goto sslbail; } secStatus = SSL_HandshakeCallback(sslSocket, (SSLHandshakeCallback) handshakeCallback, NULL); if (secStatus != SECSuccess) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "setupSSLSocket: SSL_HandshakeCallback Failed\n"); goto sslbail; } return sslSocket; sslbail: PR_Close(fd); return NULL; } SECStatus authCertificate(void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer) { SECCertUsage certUsage; CERTCertificate * cert; void * pinArg; char * hostName; SECStatus secStatus; if (!arg || !socket) { slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, " authCertificate: Faulty socket in callback function \n"); return SECFailure; } /* Define how the cert is being used based upon the isServer flag. */ certUsage = isServer ? certUsageSSLClient : certUsageSSLServer; cert = SSL_PeerCertificate(socket); pinArg = SSL_RevealPinArg(socket); secStatus = CERT_VerifyCertNow((CERTCertDBHandle *)arg, cert, checksig, certUsage, pinArg); /* If this is a server, we're finished. */ if (isServer || secStatus != SECSuccess) { return secStatus; } hostName = SSL_RevealURL(socket); if (hostName && hostName[0]) { secStatus = CERT_VerifyCertName(cert, hostName); } else { PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); secStatus = SECFailure; } if (hostName) PR_Free(hostName); return secStatus; } SECStatus badCertHandler(void *arg, PRFileDesc *socket) { SECStatus secStatus = SECFailure; PRErrorCode err; /* log invalid cert here */ if (!arg) { return secStatus; } *(PRErrorCode *)arg = err = PORT_GetError(); switch (err) { case SEC_ERROR_INVALID_AVA: case SEC_ERROR_INVALID_TIME: case SEC_ERROR_BAD_SIGNATURE: case SEC_ERROR_EXPIRED_CERTIFICATE: case SEC_ERROR_UNKNOWN_ISSUER: case SEC_ERROR_UNTRUSTED_CERT: case SEC_ERROR_CERT_VALID: case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: case SEC_ERROR_CRL_EXPIRED: case SEC_ERROR_CRL_BAD_SIGNATURE: case SEC_ERROR_EXTENSION_VALUE_INVALID: case SEC_ERROR_CA_CERT_INVALID: case SEC_ERROR_CERT_USAGES_INVALID: case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: secStatus = SECSuccess; break; default: secStatus = SECFailure; break; } slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "Bad certificate: %d\n", err); return secStatus; } SECStatus getClientAuthData(void *arg, PRFileDesc *socket, struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey) { CERTCertificate * cert; SECKEYPrivateKey * privKey; char * chosenNickName = (char *)arg; void * proto_win = NULL; SECStatus secStatus = SECFailure; proto_win = SSL_RevealPinArg(socket); if (chosenNickName) { cert = PK11_FindCertFromNickname(chosenNickName, proto_win); if (cert) { privKey = PK11_FindKeyByAnyCert(cert, proto_win); if (privKey) { secStatus = SECSuccess; } else { CERT_DestroyCertificate(cert); } } } else { /* no nickname given, automatically find the right cert */ CERTCertNicknames *names; int i; names = CERT_GetCertNicknames(CERT_GetDefaultCertDB(), SEC_CERT_NICKNAMES_USER, proto_win); if (names != NULL) { for(i = 0; i < names->numnicknames; i++ ) { cert = PK11_FindCertFromNickname(names->nicknames[i], proto_win); if (!cert) { continue; } /* Only check unexpired certs */ if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE) != secCertTimeValid ) { CERT_DestroyCertificate(cert); continue; } secStatus = NSS_CmpCertChainWCANames(cert, caNames); if (secStatus == SECSuccess) { privKey = PK11_FindKeyByAnyCert(cert, proto_win); if (privKey) { break; } secStatus = SECFailure; break; } CERT_FreeNicknames(names); } /* for loop */ } } if (secStatus == SECSuccess) { *pRetCert = cert; *pRetKey = privKey; } return secStatus; } SECStatus handshakeCallback(PRFileDesc *socket, void *arg) { slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "----------> Handshake has completed, ready to send data securely.\n"); return SECSuccess; } /** * PUBLIC FUNCTIONS IMPLEMENTATION */ int http_impl_init(Slapi_ComponentId *plugin_id) { int status = HTTP_IMPL_SUCCESS; slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "-> http_impl_init \n"); httpConfig = NULL; httpConfig = (httpPluginConfig *) slapi_ch_calloc(1, sizeof(httpPluginConfig)); status = readConfigLDAPurl(plugin_id, HTTP_PLUGIN_DN); if (status != 0) { slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "http_impl_start: Unable to get HTTP config information \n"); return HTTP_IMPL_FAILURE; } status = readConfigLDAPurl(plugin_id, CONFIG_DN); if (status != 0) { slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, "http_impl_start: Unable to get config information \n"); return HTTP_IMPL_FAILURE; } slapi_log_error(SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "<- http_impl_init \n"); return status; } int http_impl_get_text(char *url, char **data, int *bytesRead) { int status = HTTP_IMPL_SUCCESS; status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET); return status; } int http_impl_get_binary(char *url, char **data, int *bytesRead) { int status = HTTP_IMPL_SUCCESS; status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_GET); return status; } int http_impl_get_redirected_uri(char *url, char **data, int *bytesRead) { int status = HTTP_IMPL_SUCCESS; status = doRequestRetry(url, NULL, NULL, data, bytesRead, HTTP_REQ_TYPE_REDIRECT); return status; } int http_impl_post(char *url, httpheader **httpheaderArray, char *body, char **data, int *bytesRead) { int status = HTTP_IMPL_SUCCESS; status = doRequestRetry(url, httpheaderArray, body, data, bytesRead, HTTP_REQ_TYPE_POST); return status; } void http_impl_shutdown() { /** * Put cleanup code here */ } static int readConfigLDAPurl(Slapi_ComponentId *plugin_id, char *plugindn) { int rc = LDAP_SUCCESS; Slapi_DN *sdn = NULL; int status = HTTP_IMPL_SUCCESS; Slapi_Entry *entry = NULL; sdn = slapi_sdn_new_dn_byref(plugindn); rc = slapi_search_internal_get_entry(sdn, NULL, &entry, plugin_id); slapi_sdn_free(&sdn); if (rc != LDAP_SUCCESS) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "readConfigLDAPurl: Could not find entry %s (error %d)\n", plugindn, rc); status = HTTP_IMPL_FAILURE; return status; } if (NULL == entry) { slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, "readConfigLDAPurl: No entries found for <%s>\n", plugindn); status = HTTP_IMPL_FAILURE; return status; } if ((PL_strcasecmp(plugindn, HTTP_PLUGIN_DN) == 0)) status = parseHTTPConfigEntry(entry); else status = parseConfigEntry(entry); slapi_entry_free(entry); return status; } /* Retrieves the plugin configuration info */ /* Retrieves security info as well as the path info required for the SSL config dir */ static int parseConfigEntry(Slapi_Entry *e) { char *value = NULL; value = slapi_entry_attr_get_charptr(e, ATTR_DS_SECURITY); if (value) { httpConfig->DS_sslOn = value; } return HTTP_IMPL_SUCCESS; } static int parseHTTPConfigEntry(Slapi_Entry *e) { int value = 0; value = slapi_entry_attr_get_int(e, ATTR_RETRY_COUNT); if (value) { httpConfig->retryCount = value; } value = slapi_entry_attr_get_int(e, ATTR_CONNECTION_TIME_OUT); if (value) { httpConfig->connectionTimeOut = value; } else { LDAPDebug( LDAP_DEBUG_PLUGIN, "parseHTTPConfigEntry: HTTP Connection Time Out cannot be read. Setting to default value of 5000 ms \n", 0,0,0); httpConfig->connectionTimeOut = 5000; } value = slapi_entry_attr_get_int(e, ATTR_READ_TIME_OUT); if (value) { httpConfig->readTimeOut = value; } else { LDAPDebug( LDAP_DEBUG_PLUGIN, "parseHTTPConfigEntry: HTTP Read Time Out cannot be read. Setting to default value of 5000 ms \n", 0,0,0); httpConfig->readTimeOut = 5000; } httpConfig->nssInitialized = 0; return HTTP_IMPL_SUCCESS; } /** * Self Testing */ #ifdef BUILD_STANDALONE int main(int argc, char **argv) { PRStatus status = PR_SUCCESS; char *buf; int bytes; char *host; PRInt32 port; char *path; if (argc < 2) { printf("URL missing\n"); return -1; } PR_Init(PR_USER_THREAD,PR_PRIORITY_NORMAL, 0); doRequest(argv[1], &buf, &bytes, 2); printf( "%s\n", buf ); return -1; } #endif