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