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