1 #include <petscwebclient.h> 2 PETSC_PRAGMA_DIAGNOSTIC_IGNORED_BEGIN("-Wdeprecated-declarations") 3 4 static BIO *bio_err = NULL; 5 6 #define PASSWORD "password" 7 8 #if defined(PETSC_USE_SSL_CERTIFICATE) 9 static int password_cb(char *buf, int num, int rwflag, void *userdata) 10 { 11 if (num < strlen(PASSWORD) + 1) return 0; 12 strcpy(buf, PASSWORD); 13 return strlen(PASSWORD); 14 } 15 #endif 16 17 static void sigpipe_handle(int x) { } 18 19 /*@C 20 PetscSSLInitializeContext - Set up an SSL context suitable for initiating HTTPS requests. 21 22 Output Parameter: 23 . octx - the SSL_CTX to be passed to `PetscHTTPSConnect90` 24 25 Level: advanced 26 27 If PETSc was ./configure -with-ssl-certificate requires the user have created a self-signed certificate with 28 .vb 29 saws/CA.pl -newcert (using the passphrase of password) 30 cat newkey.pem newcert.pem > sslclient.pem 31 .ve 32 33 and put the resulting file in either the current directory (with the application) or in the home directory. This seems kind of 34 silly but it was all I could figure out. 35 36 .seealso: `PetscSSLDestroyContext()`, `PetscHTTPSConnect()`, `PetscHTTPSRequest()` 37 @*/ 38 PetscErrorCode PetscSSLInitializeContext(SSL_CTX **octx) 39 { 40 SSL_CTX *ctx; 41 #if defined(PETSC_USE_SSL_CERTIFICATE) 42 char keyfile[PETSC_MAX_PATH_LEN]; 43 PetscBool exists; 44 #endif 45 46 PetscFunctionBegin; 47 if (!bio_err) { 48 SSL_library_init(); 49 SSL_load_error_strings(); 50 bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); 51 } 52 53 /* Set up a SIGPIPE handler */ 54 signal(SIGPIPE, sigpipe_handle); 55 56 /* suggested at https://mta.openssl.org/pipermail/openssl-dev/2015-May/001449.html */ 57 #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) 58 ctx = SSL_CTX_new(TLS_client_method()); 59 #else 60 ctx = SSL_CTX_new(SSLv23_client_method()); 61 #endif 62 SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); 63 64 #if defined(PETSC_USE_SSL_CERTIFICATE) 65 /* Locate keyfile */ 66 PetscCall(PetscStrncpy(keyfile, "sslclient.pem", sizeof(keyfile))); 67 PetscCall(PetscTestFile(keyfile, 'r', &exists)); 68 if (!exists) { 69 PetscCall(PetscGetHomeDirectory(keyfile, PETSC_MAX_PATH_LEN)); 70 PetscCall(PetscStrlcat(keyfile, "/", sizeof(keyfile))); 71 PetscCall(PetscStrlcat(keyfile, "sslclient.pem", sizeof(keyfile))); 72 PetscCall(PetscTestFile(keyfile, 'r', &exists)); 73 PetscCheck(exists, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate sslclient.pem file in current directory or home directory"); 74 } 75 76 /* Load our keys and certificates*/ 77 PetscCheck(SSL_CTX_use_certificate_chain_file(ctx, keyfile), PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot read certificate file"); 78 79 SSL_CTX_set_default_passwd_cb(ctx, password_cb); 80 PetscCheck(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM), PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot read key file"); 81 #endif 82 83 *octx = ctx; 84 PetscFunctionReturn(PETSC_SUCCESS); 85 } 86 87 /*@C 88 PetscSSLDestroyContext - frees a `SSL_CTX` obtained with `PetscSSLInitializeContext()` 89 90 Input Parameter: 91 . ctx - the `SSL_CTX` 92 93 Level: advanced 94 95 .seealso: `PetscSSLInitializeContext()`, `PetscHTTPSConnect()` 96 @*/ 97 PetscErrorCode PetscSSLDestroyContext(SSL_CTX *ctx) 98 { 99 PetscFunctionBegin; 100 SSL_CTX_free(ctx); 101 PetscFunctionReturn(PETSC_SUCCESS); 102 } 103 104 static PetscErrorCode PetscHTTPBuildRequest(const char type[], const char url[], const char header[], const char ctype[], const char body[], char **outrequest) 105 { 106 char *request = 0; 107 char contentlength[40], contenttype[80], *path, *host; 108 size_t request_len, headlen, bodylen, contentlen, pathlen, hostlen, typelen, contenttypelen = 0; 109 PetscBool flg; 110 111 PetscFunctionBegin; 112 PetscCall(PetscStrallocpy(url, &host)); 113 PetscCall(PetscStrchr(host, '/', &path)); 114 PetscCheck(path, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "URL must contain /, it is %s", url); 115 *path = 0; 116 PetscCall(PetscStrlen(host, &hostlen)); 117 118 PetscCall(PetscStrchr(url, '/', &path)); 119 PetscCall(PetscStrlen(path, &pathlen)); 120 121 if (header) { 122 PetscCall(PetscStrendswith(header, "\r\n", &flg)); 123 PetscCheck(flg, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Header must end with \\r\\n, it is %s", header); 124 } 125 126 PetscCall(PetscStrlen(type, &typelen)); 127 if (ctype) { 128 PetscCall(PetscSNPrintf(contenttype, 80, "Content-Type: %s\r\n", ctype)); 129 PetscCall(PetscStrlen(contenttype, &contenttypelen)); 130 } 131 PetscCall(PetscStrlen(header, &headlen)); 132 PetscCall(PetscStrlen(body, &bodylen)); 133 PetscCall(PetscSNPrintf(contentlength, 40, "Content-Length: %d\r\n\r\n", (int)bodylen)); 134 PetscCall(PetscStrlen(contentlength, &contentlen)); 135 136 /* Now construct our HTTP request */ 137 request_len = typelen + 1 + pathlen + hostlen + 100 + headlen + contenttypelen + contentlen + bodylen + 1; 138 PetscCall(PetscMalloc1(request_len, &request)); 139 PetscCall(PetscStrncpy(request, type, request_len)); 140 PetscCall(PetscStrlcat(request, " ", request_len)); 141 PetscCall(PetscStrlcat(request, path, request_len)); 142 PetscCall(PetscStrlcat(request, " HTTP/1.1\r\nHost: ", request_len)); 143 PetscCall(PetscStrlcat(request, host, request_len)); 144 PetscCall(PetscFree(host)); 145 PetscCall(PetscStrlcat(request, "\r\nUser-Agent:PETScClient\r\n", request_len)); 146 PetscCall(PetscStrlcat(request, header, request_len)); 147 if (ctype) PetscCall(PetscStrlcat(request, contenttype, request_len)); 148 PetscCall(PetscStrlcat(request, contentlength, request_len)); 149 PetscCall(PetscStrlcat(request, body, request_len)); 150 PetscCall(PetscStrlen(request, &request_len)); 151 PetscCall(PetscInfo(NULL, "HTTPS request follows: \n%s\n", request)); 152 153 *outrequest = request; 154 PetscFunctionReturn(PETSC_SUCCESS); 155 } 156 157 /*@C 158 PetscHTTPSRequest - Send a request to an HTTPS server 159 160 Input Parameters: 161 + type - either "POST" or "GET" 162 . url - URL of request host/path 163 . header - additional header information, may be `NULL` 164 . ctype - data type of body, for example application/json 165 . body - data to send to server 166 . ssl - obtained with `PetscHTTPSConnect()` 167 - buffsize - size of buffer 168 169 Output Parameter: 170 . buff - everything returned from server 171 172 Level: advanced 173 174 .seealso: `PetscHTTPRequest()`, `PetscHTTPSConnect()`, `PetscSSLInitializeContext()`, `PetscSSLDestroyContext()`, `PetscPullJSONValue()` 175 @*/ 176 PetscErrorCode PetscHTTPSRequest(const char type[], const char url[], const char header[], const char ctype[], const char body[], SSL *ssl, char buff[], size_t buffsize) 177 { 178 char *request; 179 int r; 180 size_t request_len, len; 181 PetscBool foundbody = PETSC_FALSE; 182 183 PetscFunctionBegin; 184 PetscCall(PetscHTTPBuildRequest(type, url, header, ctype, body, &request)); 185 PetscCall(PetscStrlen(request, &request_len)); 186 187 r = SSL_write(ssl, request, (int)request_len); 188 switch (SSL_get_error(ssl, r)) { 189 case SSL_ERROR_NONE: 190 PetscCheck(request_len == (size_t)r, PETSC_COMM_SELF, PETSC_ERR_LIB, "Incomplete write to SSL socket"); 191 break; 192 default: 193 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, "SSL socket write problem"); 194 } 195 196 /* Now read the server's response, globus sends it in two chunks hence must read a second time if needed */ 197 PetscCall(PetscArrayzero(buff, buffsize)); 198 len = 0; 199 foundbody = PETSC_FALSE; 200 do { 201 char *clen; 202 int cl; 203 size_t nlen; 204 205 r = SSL_read(ssl, buff + len, (int)buffsize); 206 len += r; 207 switch (SSL_get_error(ssl, r)) { 208 case SSL_ERROR_NONE: 209 break; 210 case SSL_ERROR_ZERO_RETURN: 211 foundbody = PETSC_TRUE; 212 SSL_shutdown(ssl); 213 break; 214 case SSL_ERROR_SYSCALL: 215 foundbody = PETSC_TRUE; 216 break; 217 default: 218 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, "SSL read problem"); 219 } 220 221 PetscCall(PetscStrstr(buff, "Content-Length: ", &clen)); 222 if (clen) { 223 clen += 15; 224 sscanf(clen, "%d", &cl); 225 if (!cl) foundbody = PETSC_TRUE; 226 else { 227 PetscCall(PetscStrstr(buff, "\r\n\r\n", &clen)); 228 if (clen) { 229 PetscCall(PetscStrlen(clen, &nlen)); 230 if (nlen - 4 == (size_t)cl) foundbody = PETSC_TRUE; 231 } 232 } 233 } else { 234 /* if no content length than must leave because you don't know if you can read again */ 235 foundbody = PETSC_TRUE; 236 } 237 } while (!foundbody); 238 PetscCall(PetscInfo(NULL, "HTTPS result follows: \n%s\n", buff)); 239 240 SSL_free(ssl); 241 PetscCall(PetscFree(request)); 242 PetscFunctionReturn(PETSC_SUCCESS); 243 } 244 245 /*@C 246 PetscHTTPRequest - Send a request to an HTTP server 247 248 Input Parameters: 249 + type - either "POST" or "GET" 250 . url - URL of request host/path 251 . header - additional header information, may be `NULL` 252 . ctype - data type of body, for example application/json 253 . body - data to send to server 254 . sock - obtained with `PetscOpenSocket()` 255 - buffsize - size of buffer 256 257 Output Parameter: 258 . buff - everything returned from server 259 260 Level: advanced 261 262 .seealso: `PetscHTTPSRequest()`, `PetscOpenSocket()`, `PetscHTTPSConnect()`, `PetscPullJSONValue()` 263 @*/ 264 PetscErrorCode PetscHTTPRequest(const char type[], const char url[], const char header[], const char ctype[], const char body[], int sock, char buff[], size_t buffsize) 265 { 266 char *request; 267 size_t request_len; 268 269 PetscFunctionBegin; 270 PetscCall(PetscHTTPBuildRequest(type, url, header, ctype, body, &request)); 271 PetscCall(PetscStrlen(request, &request_len)); 272 273 PetscCall(PetscBinaryWrite(sock, request, request_len, PETSC_CHAR)); 274 PetscCall(PetscFree(request)); 275 PetscCall(PetscBinaryRead(sock, buff, buffsize, NULL, PETSC_CHAR)); 276 buff[buffsize - 1] = 0; 277 PetscCall(PetscInfo(NULL, "HTTP result follows: \n%s\n", buff)); 278 PetscFunctionReturn(PETSC_SUCCESS); 279 } 280 281 /*@C 282 PetscHTTPSConnect - connect to a HTTPS server 283 284 Input Parameters: 285 + host - the name of the machine hosting the HTTPS server 286 . port - the port number where the server is hosting, usually 443 287 - ctx - value obtained with `PetscSSLInitializeContext()` 288 289 Output Parameters: 290 + sock - socket to connect 291 - ssl - the argument passed to `PetscHTTPSRequest()` 292 293 Level: advanced 294 295 .seealso: `PetscOpenSocket()`, `PetscHTTPSRequest()`, `PetscSSLInitializeContext()` 296 @*/ 297 PetscErrorCode PetscHTTPSConnect(const char host[], int port, SSL_CTX *ctx, int *sock, SSL **ssl) 298 { 299 BIO *sbio; 300 301 PetscFunctionBegin; 302 /* Connect the TCP socket*/ 303 PetscCall(PetscOpenSocket(host, port, sock)); 304 305 /* Connect the SSL socket */ 306 *ssl = SSL_new(ctx); 307 sbio = BIO_new_socket(*sock, BIO_NOCLOSE); 308 SSL_set_bio(*ssl, sbio, sbio); 309 PetscCheck(SSL_connect(*ssl) > 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "SSL connect error"); 310 PetscFunctionReturn(PETSC_SUCCESS); 311 } 312 313 /*@C 314 PetscPullJSONValue - Given a JSON response containing the substring with "key" : "value" where there may or not be spaces around the : returns the value. 315 316 Input Parameters: 317 + buff - the char array containing the possible values 318 . key - the key of the requested value 319 - valuelen - the length of the array to contain the value associated with the key 320 321 Output Parameters: 322 + value - the value obtained 323 - found - flag indicating if the value was found in the buff 324 325 Level: advanced 326 327 .seealso: `PetscOpenSocket()`, `PetscHTTPSRequest()`, `PetscSSLInitializeContext()`, `PetscPushJSONValue()` 328 @*/ 329 PetscErrorCode PetscPullJSONValue(const char buff[], const char key[], char value[], size_t valuelen, PetscBool *found) 330 { 331 char *v, *w; 332 char work[256]; 333 size_t len; 334 335 PetscFunctionBegin; 336 PetscCall(PetscStrncpy(work, "\"", sizeof(work))); 337 PetscCall(PetscStrlcat(work, key, sizeof(work))); 338 PetscCall(PetscStrlcat(work, "\":", sizeof(work))); 339 PetscCall(PetscStrstr(buff, work, &v)); 340 PetscCall(PetscStrlen(work, &len)); 341 if (v) { 342 v += len; 343 } else { 344 work[len++ - 1] = 0; 345 PetscCall(PetscStrlcat(work, " :", sizeof(work))); 346 PetscCall(PetscStrstr(buff, work, &v)); 347 if (!v) { 348 *found = PETSC_FALSE; 349 PetscFunctionReturn(PETSC_SUCCESS); 350 } 351 v += len; 352 } 353 PetscCall(PetscStrchr(v, '\"', &v)); 354 if (!v) { 355 *found = PETSC_FALSE; 356 PetscFunctionReturn(PETSC_SUCCESS); 357 } 358 PetscCall(PetscStrchr(v + 1, '\"', &w)); 359 if (!w) { 360 *found = PETSC_FALSE; 361 PetscFunctionReturn(PETSC_SUCCESS); 362 } 363 *found = PETSC_TRUE; 364 PetscCall(PetscStrncpy(value, v + 1, PetscMin((size_t)(w - v), valuelen))); 365 PetscFunctionReturn(PETSC_SUCCESS); 366 } 367 368 #include <ctype.h> 369 370 /*@C 371 PetscPushJSONValue - Puts a "key" : "value" pair onto a string 372 373 Input Parameters: 374 + buff - the char array where the value will be put 375 . key - the key value to be set 376 . value - the value associated with the key 377 - bufflen - the size of the buffer (currently ignored) 378 379 Level: advanced 380 381 Note: 382 Ignores lengths so can cause buffer overflow 383 384 .seealso: `PetscOpenSocket()`, `PetscHTTPSRequest()`, `PetscSSLInitializeContext()`, `PetscPullJSONValue()` 385 @*/ 386 PetscErrorCode PetscPushJSONValue(char buff[], const char key[], const char value[], size_t bufflen) 387 { 388 size_t len; 389 PetscBool special; 390 391 PetscFunctionBegin; 392 PetscCall(PetscStrcmp(value, "null", &special)); 393 if (!special) PetscCall(PetscStrcmp(value, "true", &special)); 394 if (!special) PetscCall(PetscStrcmp(value, "false", &special)); 395 if (!special) { 396 PetscInt i; 397 398 PetscCall(PetscStrlen(value, &len)); 399 special = PETSC_TRUE; 400 for (i = 0; i < (int)len; i++) { 401 if (!isdigit(value[i])) { 402 special = PETSC_FALSE; 403 break; 404 } 405 } 406 } 407 408 PetscCall(PetscStrlcat(buff, "\"", bufflen)); 409 PetscCall(PetscStrlcat(buff, key, bufflen)); 410 PetscCall(PetscStrlcat(buff, "\":", bufflen)); 411 if (!special) PetscCall(PetscStrlcat(buff, "\"", bufflen)); 412 PetscCall(PetscStrlcat(buff, value, bufflen)); 413 if (!special) PetscCall(PetscStrlcat(buff, "\"", bufflen)); 414 PetscFunctionReturn(PETSC_SUCCESS); 415 } 416