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