diff options
Diffstat (limited to 'ldap/servers/plugins/http/http_impl.c')
-rw-r--r-- | ldap/servers/plugins/http/http_impl.c | 1479 |
1 files changed, 1479 insertions, 0 deletions
diff --git a/ldap/servers/plugins/http/http_impl.c b/ldap/servers/plugins/http/http_impl.c new file mode 100644 index 00000000..bad8315c --- /dev/null +++ b/ldap/servers/plugins/http/http_impl.c @@ -0,0 +1,1479 @@ +/** + * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to + * license terms. Copyright 2001 Sun Microsystems, Inc. + * Some preexisting portions Copyright 2001 Netscape Communications Corp. + * All rights reserved. + */ +/** + * Implementation of a Simple HTTP Client + */ +#include <stdio.h> +#include <string.h> + +#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 "slapi-private.h" +#include "slapi-plugin-compat4.h" +/* get file mode flags for unix */ +#ifndef _WIN32 +#include <sys/stat.h> +#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); + +static int nssReinitializationRequired(); + +/*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; + char *val = NULL; + char *defaultprefix = NULL; + PRFileDesc *fd = NULL; + PRNetAddr addr; + PRInt32 port; + PRInt32 errcode = 0; + PRInt32 http_connection_time_out = 0; + PRInt32 sslOn; + PRInt32 nssStatus; + PRUint32 nssFlags = 0; + char certDir[1024]; + char certPref[1024]; + char keyPref[1024]; + + 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) { + + /* Have to reinitialize NSS is the DS security is set to off. + This is because the HTTPS required the cert dbs to be created. + The default prefixes are used as per DS norm */ + + if (PL_strcasecmp(httpConfig->DS_sslOn, "off") == 0) { + if (!httpConfig->nssInitialized) { + if (nssReinitializationRequired()) + { + NSS_Shutdown(); + nssFlags &= (~NSS_INIT_READONLY); + val = config_get_instancedir(); + strcpy(certDir, val); + defaultprefix = strrchr(certDir, '/'); + if (!defaultprefix) + defaultprefix = strrchr(certDir, '\\'); + if (!defaultprefix) /* still could not find it . . . */ + goto bail; /* . . . can't do anything */ + defaultprefix++; + sprintf(certPref, "%s-",defaultprefix); + strcpy(keyPref, certPref); + *defaultprefix= '\0'; + sprintf(certDir, "%salias", certDir); + nssStatus = NSS_Initialize(certDir, certPref, keyPref, "secmod.db", nssFlags); + slapi_ch_free((void **)&val); + + if (nssStatus != 0) { + slapi_log_error(SLAPI_LOG_FATAL, HTTP_PLUGIN_SUBSYSTEM, + "doRequest: Unable to initialize NSS Cert/Key Database\n"); + status = HTTP_CLIENT_ERROR_NSS_INITIALIZE; + goto bail; + } + } + httpConfig->nssInitialized = 1; + } + } + + NSS_SetDomesticPolicy(); + + 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 int nssReinitializationRequired() +{ + int nssReinitializationRequired = 0; + int err = 0; + int str_len = 0; + float version = 0; + const float DSVERSION = 6.1; + char *str = NULL; + char *value = NULL; + char *ver_value = NULL; + Slapi_Entry **entry = NULL; + Slapi_PBlock *resultpb= NULL; + + resultpb= slapi_search_internal( "", LDAP_SCOPE_BASE, "objectclass=*", NULL, NULL, 0); + slapi_pblock_get( resultpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entry ); + slapi_pblock_get( resultpb, SLAPI_PLUGIN_INTOP_RESULT, &err); + if ( err == LDAP_SUCCESS && entry!=NULL && entry[0]!=NULL) + { + value = slapi_entry_attr_get_charptr(entry[0], "vendorVersion"); + if (value == NULL || strncmp(value, "Netscape", strlen("Netscape"))) + { + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "nssReinitializationRequired: vendor is not Netscape \n"); + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "or version is earlier than 6.0\n", value); + nssReinitializationRequired = 1; + slapi_free_search_results_internal(resultpb); + slapi_pblock_destroy(resultpb); + slapi_ch_free((void **)&value); + return nssReinitializationRequired; + } + + if ( (str = strstr(value,"/")) != NULL ) + { + str++; + version = atof(str); + slapi_log_error( SLAPI_LOG_PLUGIN, HTTP_PLUGIN_SUBSYSTEM, + "nssReinitializationRequired: version is %f. \n", version); + } + + + if (str == NULL || version < DSVERSION) + { + nssReinitializationRequired = 1; + } + slapi_ch_free((void **)&value); + + } + slapi_free_search_results_internal(resultpb); + slapi_pblock_destroy(resultpb); + return nssReinitializationRequired; + +} + +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); + +bail: + 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; + + body_len = strlen(body); + 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"); + 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); + +bail: + 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) + 1024)); + + /* Just write the path and check for a starting / */ + if ('/' != *url) { + PR_sscanf(*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() +{ + int status = HTTP_IMPL_SUCCESS; + /** + * 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 + |