xref: /petsc/src/sys/webclient/client.c (revision d5b43468fb8780a8feea140ccd6fa3e6a50411cc)
1 
2 #include <petscwebclient.h>
3 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
4 #pragma gcc diagnostic   ignored "-Wdeprecated-declarations"
5 
6 static BIO *bio_err = NULL;
7 
8 #define PASSWORD "password"
9 
10 #if defined(PETSC_USE_SSL_CERTIFICATE)
11 static int password_cb(char *buf, int num, int rwflag, void *userdata)
12 {
13   if (num < strlen(PASSWORD) + 1) return (0);
14   strcpy(buf, PASSWORD);
15   return (strlen(PASSWORD));
16 }
17 #endif
18 
19 static void sigpipe_handle(int x) { }
20 
21 /*@C
22     PetscSSLInitializeContext - Set up an SSL context suitable for initiating HTTPS requests.
23 
24     Output Parameter:
25 .   octx - the SSL_CTX to be passed to `PetscHTTPSConnect90`
26 
27     Level: advanced
28 
29     If PETSc was ./configure -with-ssl-certificate requires the user have created a self-signed certificate with
30 $    saws/CA.pl  -newcert  (using the passphrase of password)
31 $    cat newkey.pem newcert.pem > sslclient.pem
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(PetscStrcpy(keyfile, "sslclient.pem"));
67   PetscCall(PetscTestFile(keyfile, 'r', &exists));
68   if (!exists) {
69     PetscCall(PetscGetHomeDirectory(keyfile, PETSC_MAX_PATH_LEN));
70     PetscCall(PetscStrcat(keyfile, "/"));
71     PetscCall(PetscStrcat(keyfile, "sslclient.pem"));
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(0);
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(0);
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");
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(PetscStrcpy(request, type));
140   PetscCall(PetscStrcat(request, " "));
141   PetscCall(PetscStrcat(request, path));
142   PetscCall(PetscStrcat(request, " HTTP/1.1\r\nHost: "));
143   PetscCall(PetscStrcat(request, host));
144   PetscCall(PetscFree(host));
145   PetscCall(PetscStrcat(request, "\r\nUser-Agent:PETScClient\r\n"));
146   PetscCall(PetscStrcat(request, header));
147   if (ctype) PetscCall(PetscStrcat(request, contenttype));
148   PetscCall(PetscStrcat(request, contentlength));
149   PetscCall(PetscStrcat(request, body));
150   PetscCall(PetscStrlen(request, &request_len));
151   PetscCall(PetscInfo(NULL, "HTTPS request follows: \n%s\n", request));
152 
153   *outrequest = request;
154   PetscFunctionReturn(0);
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(0);
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   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(0);
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(0);
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(PetscStrcpy(work, "\""));
337   PetscCall(PetscStrlcat(work, key, sizeof(work)));
338   PetscCall(PetscStrcat(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(PetscStrcat(work, " :"));
346     PetscCall(PetscStrstr(buff, work, &v));
347     if (!v) {
348       *found = PETSC_FALSE;
349       PetscFunctionReturn(0);
350     }
351     v += len;
352   }
353   PetscCall(PetscStrchr(v, '\"', &v));
354   if (!v) {
355     *found = PETSC_FALSE;
356     PetscFunctionReturn(0);
357   }
358   PetscCall(PetscStrchr(v + 1, '\"', &w));
359   if (!w) {
360     *found = PETSC_FALSE;
361     PetscFunctionReturn(0);
362   }
363   *found = PETSC_TRUE;
364   PetscCall(PetscStrncpy(value, v + 1, PetscMin((size_t)(w - v), valuelen)));
365   PetscFunctionReturn(0);
366 }
367 
368 #include <ctype.h>
369 
370 /*@C
371     PetscPushJSONValue -  Puts a "key" : "value" pair onto a string
372 
373     Input Parameters:
374 +   buffer - 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(PetscStrcat(buff, "\""));
409   PetscCall(PetscStrcat(buff, key));
410   PetscCall(PetscStrcat(buff, "\":"));
411   if (!special) PetscCall(PetscStrcat(buff, "\""));
412   PetscCall(PetscStrcat(buff, value));
413   if (!special) PetscCall(PetscStrcat(buff, "\""));
414   PetscFunctionReturn(0);
415 }
416