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