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