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