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