xref: /petsc/src/sys/webclient/client.c (revision b967cddfa3204c66e2c99be2f6a740815d5662bb)
1 
2 #include <petscsys.h>
3 #include <errno.h>
4 #include <sys/types.h>
5 #include <sys/socket.h>
6 #include <netinet/in.h>
7 #include <netinet/tcp.h>
8 #include <netdb.h>
9 #include <fcntl.h>
10 #include <signal.h>
11 #include <unistd.h>
12 #include <string.h>
13 
14 #include <openssl/ssl.h>
15 #include <openssl/err.h>
16 
17 static BIO *bio_err = NULL;
18 
19 #define PASSWORD "password"
20 
21 #if defined(PETSC_USE_CERTIFICATE)
22 static int password_cb(char *buf,int num, int rwflag,void *userdata)
23 {
24   if (num < strlen(PASSWORD)+1) return(0);
25   strcpy(buf,PASSWORD);
26   return(strlen(PASSWORD));
27 }
28 #endif
29 
30 static void sigpipe_handle(int x)
31 {
32 }
33 
34 #undef __FUNCT__
35 #define __FUNCT__ "PetscSSLInitializeContext"
36 /*
37     PetscSSLInitializeContext - Set up an SSL context suitable for initiating HTTPS requests.
38 
39     If built with PETSC_USE_CERTIFICATE requires the user have created a self-signed certificate with
40 
41 $    ./CA.pl  -newcert  (using the passphrase of password)
42 $    cat newkey.pem newcert.pem > sslclient.pem
43 
44     and put the resulting file in either the current directory (with the application) or in the home directory. This seems kind of
45     silly but it was all I could figure out.
46 
47 */
48 PetscErrorCode PetscSSLInitializeContext(SSL_CTX **octx)
49 {
50     SSL_METHOD     *meth;
51     SSL_CTX        *ctx;
52 #if defined(PETSC_USE_CERTIFICATE)
53     char           keyfile[PETSC_MAX_PATH_LEN];
54     PetscBool      exists;
55     PetscErrorCode ierr;
56 #endif
57 
58     PetscFunctionBegin;
59     if (!bio_err){
60       SSL_library_init();
61       SSL_load_error_strings();
62       bio_err = BIO_new_fp(stderr,BIO_NOCLOSE);
63     }
64 
65     /* Set up a SIGPIPE handler */
66     signal(SIGPIPE,sigpipe_handle);
67 
68     meth = SSLv23_method();
69     ctx  = SSL_CTX_new(meth);
70 
71 #if defined(PETSC_USE_CERTIFICATE)
72     /* Locate keyfile */
73     ierr = PetscStrcpy(keyfile,"sslclient.pem");CHKERRQ(ierr);
74     ierr = PetscTestFile(keyfile,'r',&exists);CHKERRQ(ierr);
75     if (!exists) {
76       ierr = PetscGetHomeDirectory(keyfile,PETSC_MAX_PATH_LEN);CHKERRQ(ierr);
77       ierr = PetscStrcat(keyfile,"/");CHKERRQ(ierr);
78       ierr = PetscStrcat(keyfile,"sslclient.pem");CHKERRQ(ierr);
79       ierr = PetscTestFile(keyfile,'r',&exists);CHKERRQ(ierr);
80       if (!exists) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_FILE_OPEN,"Unable to locate sslclient.pem file in current directory or home directory");
81     }
82 
83     /* Load our keys and certificates*/
84     if (!(SSL_CTX_use_certificate_chain_file(ctx,keyfile))) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_FILE_OPEN,"Cannot read certificate file");
85 
86     SSL_CTX_set_default_passwd_cb(ctx,password_cb);
87     if (!(SSL_CTX_use_PrivateKey_file(ctx,keyfile,SSL_FILETYPE_PEM))) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_FILE_OPEN,"Cannot read key file");
88 #endif
89 
90     *octx = ctx;
91     PetscFunctionReturn(0);
92 }
93 
94 #undef __FUNCT__
95 #define __FUNCT__ "PetscSSLDestroyContext"
96 PetscErrorCode PetscSSLDestroyContext(SSL_CTX *ctx)
97 {
98   PetscFunctionBegin;
99   SSL_CTX_free(ctx);
100   PetscFunctionReturn(0);
101 }
102 
103 #undef __FUNCT__
104 #define __FUNCT__ "PetscHTTPSRequest"
105 /*
106      PetscHTTPSRequest - Send a request to an HTTPS server
107 
108    Input Parameters:
109 +   type - either "POST" or "GET"
110 .   url - complete URL of request including https://
111 .   header - additional header information, may be NULL
112 .   ctype - data type of body, for example application/json
113 .   body - data to send to server
114 .   ssl - obtained with PetscHTTPSConnect()
115 -   buffsize - size of buffer
116 
117    Output Parameter:
118 .   buff - everything returned from server
119  */
120 static PetscErrorCode PetscHTTPSRequest(const char type[],const char url[],const char header[],const char ctype[],const char body[],SSL *ssl,char buff[],size_t buffsize)
121 {
122   char           *request=0;
123   char           contentlength[40],contenttype[80];
124   int            r;
125   size_t         request_len,len,headlen,bodylen,contentlen,urllen,typelen,contenttypelen = 0;
126   PetscErrorCode ierr;
127   PetscBool      flg;
128 
129   PetscFunctionBegin;
130   ierr = PetscStrbeginswith(url,"https://",&flg);CHKERRQ(ierr);
131   if (!flg) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_WRONG,"URL must begin with https://");
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   ierr = PetscStrlen(url,&urllen);CHKERRQ(ierr);
139   if (ctype) {
140     ierr = PetscSNPrintf(contenttype,80,"Content-Type: %s\r\n",ctype);CHKERRQ(ierr);
141     ierr = PetscStrlen(contenttype,&contenttypelen);CHKERRQ(ierr);
142   }
143   ierr = PetscStrlen(header,&headlen);CHKERRQ(ierr);
144   ierr = PetscStrlen(body,&bodylen);CHKERRQ(ierr);
145   ierr = PetscSNPrintf(contentlength,40,"Content-Length: %d\r\n\r\n",(int)bodylen);CHKERRQ(ierr);
146   ierr = PetscStrlen(contentlength,&contentlen);CHKERRQ(ierr);
147 
148   /* Now construct our HTTP request */
149   request_len = typelen + 1 + urllen + 35 + headlen + contenttypelen + contentlen + bodylen + 1;
150   ierr = PetscMalloc(request_len*sizeof(char),&request);CHKERRQ(ierr);
151   ierr = PetscStrcpy(request,type);CHKERRQ(ierr);
152   ierr = PetscStrcat(request," ");CHKERRQ(ierr);
153   ierr = PetscStrcat(request,url);CHKERRQ(ierr);
154   ierr = PetscStrcat(request," HTTP/1.1\r\nUser-Agent:PETScClient\r\n");CHKERRQ(ierr);
155   ierr = PetscStrcat(request,header);CHKERRQ(ierr);
156   if (ctype) {
157     ierr = PetscStrcat(request,contenttype);CHKERRQ(ierr);
158   }
159   ierr = PetscStrcat(request,contentlength);CHKERRQ(ierr);
160   ierr = PetscStrcat(request,body);CHKERRQ(ierr);
161   ierr = PetscStrlen(request,&request_len);CHKERRQ(ierr);
162   ierr = PetscInfo1(NULL,"HTTPS request follows: \n%s\n",request);CHKERRQ(ierr);
163 
164   r = SSL_write(ssl,request,request_len);
165   switch (SSL_get_error(ssl,r)){
166     case SSL_ERROR_NONE:
167       if (request_len != r) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Incomplete write to SSL socket");
168       break;
169       default:
170         SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"SSL socket write problem");
171   }
172 
173   /* Now read the server's response, assuming  that it's terminated by a close */
174   r = SSL_read(ssl,buff,(int)buffsize);
175   len = r;
176   switch (SSL_get_error(ssl,r)){
177   case SSL_ERROR_NONE:
178     break;
179   case SSL_ERROR_ZERO_RETURN:
180     SSL_shutdown(ssl);  /* ignore shutdown error message */
181     break;
182   case SSL_ERROR_SYSCALL:
183     break;
184   default:
185     SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"SSL read problem");
186   }
187   buff[len] = 0; /* null terminate string */
188   ierr = PetscInfo1(NULL,"HTTPS result follows: \n%s\n",buff);CHKERRQ(ierr);
189 
190   SSL_free(ssl);
191   ierr = PetscFree(request);CHKERRQ(ierr);
192   PetscFunctionReturn(0);
193 }
194 
195 #undef __FUNCT__
196 #define __FUNCT__ "PetscHTTPSConnect"
197 PetscErrorCode PetscHTTPSConnect(const char host[],int port,SSL_CTX *ctx,int *sock,SSL **ssl)
198 {
199   BIO            *sbio;
200   PetscErrorCode ierr;
201 
202   PetscFunctionBegin;
203   /* Connect the TCP socket*/
204   ierr = PetscOpenSocket(host,port,sock);CHKERRQ(ierr);
205 
206   /* Connect the SSL socket */
207   *ssl = SSL_new(ctx);
208   sbio = BIO_new_socket(*sock,BIO_NOCLOSE);
209   SSL_set_bio(*ssl,sbio,sbio);
210   if (SSL_connect(*ssl) <= 0) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"SSL connect error");
211   PetscFunctionReturn(0);
212 }
213 
214 /*
215     This file is not included in the respository since it contains authorization secrets
216 */
217 #include <../src/sys/webclient/authorization.h>
218 
219 #undef __FUNCT__
220 #define __FUNCT__ "PetscGoogleDriveRefresh"
221 /*C
222      PetscGoogleDriveRefresh - Get a new authorization token for accessing Google drive from PETSc from a refresh token
223 
224 
225 */
226 PetscErrorCode PetscGoogleDriveRefresh(MPI_Comm comm,const char refresh_token[],char access_token[],size_t tokensize)
227 {
228   SSL_CTX        *ctx;
229   SSL            *ssl;
230   int            sock;
231   PetscErrorCode ierr;
232   char           buff[8*1024],body[1024],*access,*ctmp;
233   PetscMPIInt    rank;
234 
235   PetscFunctionBegin;
236   ierr = MPI_Comm_rank(comm,&rank);CHKERRQ(ierr);
237   if (!rank) {
238     ierr = PetscSSLInitializeContext(&ctx);CHKERRQ(ierr);
239     ierr = PetscHTTPSConnect("accounts.google.com",443,ctx,&sock,&ssl);CHKERRQ(ierr);
240     ierr = PetscStrcpy(body,"&client_id=");CHKERRQ(ierr);
241     ierr = PetscStrcat(body,PETSC_CLIENT_ID);CHKERRQ(ierr);
242     ierr = PetscStrcat(body,"&client_secret=");CHKERRQ(ierr);
243     ierr = PetscStrcat(body,PETSC_CLIENT_SECRET);CHKERRQ(ierr);
244     ierr = PetscStrcat(body,"&refresh_token=");CHKERRQ(ierr);
245     ierr = PetscStrcat(body,refresh_token);CHKERRQ(ierr);
246     ierr = PetscStrcat(body,"&grant_type=refresh_token");CHKERRQ(ierr);
247 
248     ierr = PetscHTTPSRequest("POST","https://accounts.google.com/o/oauth2/token",NULL,"application/x-www-form-urlencoded",body,ssl,buff,sizeof(buff));CHKERRQ(ierr);
249     ierr = PetscSSLDestroyContext(ctx);CHKERRQ(ierr);
250     close(sock);
251 
252     ierr   = PetscStrstr(buff,"\"access_token\" : \"",&access);CHKERRQ(ierr);
253     if (!access) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Did not receive access token from Google");
254     access += 18;
255     ierr   = PetscStrchr(access,'\"',&ctmp);CHKERRQ(ierr);
256     if (!ctmp) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Access token from Google is misformed");
257     *ctmp  = 0;
258     ierr   = PetscStrncpy(access_token,access,tokensize);CHKERRQ(ierr);
259     *ctmp  = '\"';
260   }
261   PetscFunctionReturn(0);
262 }
263 
264 #include <sys/stat.h>
265 
266 #undef __FUNCT__
267 #define __FUNCT__ "PetscGoogleDriveUpload"
268 /*@C
269      PetscGoogleDriveUpload - Loads a file to the google drive
270 
271      Not collective, only the first process in the MPI_Comm uploads the file
272 
273   Input Parameters:
274 +   comm - MPI communicator
275 .   access_token - obtained with PetscGoogleDriveRefresh()
276 -   filename - file to upload; if you upload multiple times it will have different names each time on Google Drive
277 
278 @*/
279 PetscErrorCode PetscGoogleDriveUpload(MPI_Comm comm,const char access_token[],const char filename[])
280 {
281   SSL_CTX        *ctx;
282   SSL            *ssl;
283   int            sock;
284   PetscErrorCode ierr;
285   char           head[1024],buff[8*1024],*body,*title;
286   PetscMPIInt    rank;
287   struct stat    sb;
288   size_t         len,blen,rd;
289   FILE           *fd;
290 
291   PetscFunctionBegin;
292   ierr = MPI_Comm_rank(comm,&rank);CHKERRQ(ierr);
293   if (!rank) {
294     ierr = PetscStrcpy(head,"Authorization: Bearer ");CHKERRQ(ierr);
295     ierr = PetscStrcat(head,access_token);CHKERRQ(ierr);
296     ierr = PetscStrcat(head,"\r\n");CHKERRQ(ierr);
297     ierr = PetscStrcat(head,"uploadType: multipart\r\n");CHKERRQ(ierr);
298 
299     ierr = stat(filename,&sb);
300     if (ierr) SETERRQ1(PETSC_COMM_SELF,PETSC_ERR_FILE_OPEN,"Unable to stat file: %s",filename);
301     len = 1024 + sb.st_size;
302     ierr = PetscMalloc(len*sizeof(char),&body);CHKERRQ(ierr);
303     ierr = PetscStrcpy(body,"--foo_bar_baz\r\n"
304                          "Content-Type: application/json\r\n\r\n"
305                          "{"
306                          "\"title\": \"");
307     ierr = PetscStrcat(body,filename);
308     ierr = PetscStrcat(body,"\","
309                          "\"mimeType\": \"text.html\","
310                          "\"description\": \" a file\""
311                          "}\r\n\r\n"
312                          "--foo_bar_baz\r\n"
313                          "Content-Type: text/html\r\n\r\n");
314     ierr = PetscStrlen(body,&blen);CHKERRQ(ierr);
315     fd = fopen (filename, "r");
316     if (!fd) SETERRQ1(PETSC_COMM_SELF,PETSC_ERR_FILE_OPEN,"Unable to open file: %s",filename);
317     rd = fread (body+blen, sizeof (unsigned char), sb.st_size, fd);
318     if (rd != sb.st_size) SETERRQ3(PETSC_COMM_SELF,PETSC_ERR_FILE_OPEN,"Unable to read entire file: %s %d %d",filename,(int)rd,sb.st_size);
319     fclose(fd);
320     body[blen + rd] = 0;
321     ierr = PetscStrcat(body,"\r\n\r\n"
322                             "--foo_bar_baz\r\n");
323     ierr = PetscSSLInitializeContext(&ctx);CHKERRQ(ierr);
324     ierr = PetscHTTPSConnect("www.googleapis.com",443,ctx,&sock,&ssl);CHKERRQ(ierr);
325     ierr = PetscHTTPSRequest("POST","https://www.googleapis.com/upload/drive/v2/files/",head,"multipart/related; boundary=\"foo_bar_baz\"",body,ssl,buff,sizeof(buff));CHKERRQ(ierr);
326     ierr = PetscSSLDestroyContext(ctx);CHKERRQ(ierr);
327     close(sock);
328     ierr   = PetscStrstr(buff,"\"title\"",&title);CHKERRQ(ierr);
329     if (!title) SETERRQ1(PETSC_COMM_SELF,PETSC_ERR_LIB,"Upload of file %s failed",filename);
330   }
331   PetscFunctionReturn(0);
332 }
333 
334 
335 #undef __FUNCT__
336 #define __FUNCT__ "PetscGoogleDriveAuthorize"
337 /*C
338      PetscGoogleDriveAuthorize - Get authorization and refresh token for accessing Google drive from PETSc
339 
340 
341 */
342 PetscErrorCode PetscGoogleDriveAuthorize(MPI_Comm comm,char access_token[],char refresh_token[],size_t tokensize)
343 {
344   SSL_CTX        *ctx;
345   SSL            *ssl;
346   int            sock;
347   PetscErrorCode ierr;
348   char           buff[8*1024],*ptr,body[1024],*access,*refresh,*ctmp;
349   PetscMPIInt    rank;
350   size_t         len;
351 
352   PetscFunctionBegin;
353   ierr = PetscPrintf(comm,"Cut and paste the following into your browser:\n"
354                           "https://accounts.google.com/o/oauth2/auth?"
355                           "scope=https%%3A%%2F%%2Fwww.googleapis.com%%2Fauth%%2Fdrive.file&"
356                           "redirect_uri=urn:ietf:wg:oauth:2.0:oob&"
357                           "response_type=code&"
358                           "client_id="
359                           PETSC_CLIENT_ID
360                           "\n\n");CHKERRQ(ierr);
361   ierr = PetscPrintf(comm,"Paste the result here:");CHKERRQ(ierr);
362   ierr = MPI_Comm_rank(comm,&rank);CHKERRQ(ierr);
363   if (!rank) {
364     ptr  = fgets(buff, 1024, stdin);
365     if (!ptr) SETERRQ1(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Error reading from stdin: %d", errno);
366     ierr = PetscStrlen(buff,&len);CHKERRQ(ierr);
367     buff[len-1] = 0; /* remove carriage return at end of line */
368 
369     ierr = PetscSSLInitializeContext(&ctx);CHKERRQ(ierr);
370     ierr = PetscHTTPSConnect("accounts.google.com",443,ctx,&sock,&ssl);CHKERRQ(ierr);
371     ierr = PetscStrcpy(body,"code=");CHKERRQ(ierr);
372     ierr = PetscStrcat(body,buff);CHKERRQ(ierr);
373     ierr = PetscStrcat(body,"&client_id=");CHKERRQ(ierr);
374     ierr = PetscStrcat(body,PETSC_CLIENT_ID);CHKERRQ(ierr);
375     ierr = PetscStrcat(body,"&client_secret=");CHKERRQ(ierr);
376     ierr = PetscStrcat(body,PETSC_CLIENT_SECRET);CHKERRQ(ierr);
377     ierr = PetscStrcat(body,"&redirect_uri=urn:ietf:wg:oauth:2.0:oob&");CHKERRQ(ierr);
378     ierr = PetscStrcat(body,"grant_type=authorization_code");CHKERRQ(ierr);
379 
380     ierr = PetscHTTPSRequest("POST","https://accounts.google.com/o/oauth2/token",NULL,"application/x-www-form-urlencoded",body,ssl,buff,sizeof(buff));CHKERRQ(ierr);
381     ierr = PetscSSLDestroyContext(ctx);CHKERRQ(ierr);
382     close(sock);
383 
384     ierr   = PetscStrstr(buff,"\"access_token\" : \"",&access);CHKERRQ(ierr);
385     if (!access) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Did not receive access token from Google");
386     access += 18;
387     ierr   = PetscStrchr(access,'\"',&ctmp);CHKERRQ(ierr);
388     if (!ctmp) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Access token from Google is misformed");
389     *ctmp  = 0;
390     ierr   = PetscStrncpy(access_token,access,tokensize);CHKERRQ(ierr);
391     *ctmp  = '\"';
392 
393     ierr   = PetscStrstr(buff,"\"refresh_token\" : \"",&refresh);CHKERRQ(ierr);
394     if (!refresh) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Did not receive refresh token from Google");
395     refresh += 19;
396     ierr   = PetscStrchr(refresh,'\"',&ctmp);CHKERRQ(ierr);
397     if (!ctmp) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Refresh token from Google is misformed");
398     *ctmp  = 0;
399     ierr = PetscStrncpy(refresh_token,refresh,tokensize);CHKERRQ(ierr);
400   }
401   PetscFunctionReturn(0);
402 }
403 
404 
405 #undef __FUNCT__
406 #define __FUNCT__ "PetscURLShorten"
407 /*@C
408      PetscURLShorten - Uses Google's service to get a short url for a long url
409 
410     Input Parameters:
411 +    url - long URL you want shortened
412 -    lenshorturl - length of buffer to contain short URL
413 
414     Output Parameter:
415 .    shorturl - the shortened URL
416 
417 @*/
418 PetscErrorCode PetscURLShorten(const char url[],char shorturl[],size_t lenshorturl)
419 {
420   SSL_CTX        *ctx;
421   SSL            *ssl;
422   int            sock;
423   PetscErrorCode ierr;
424   char           buff[1024],body[512],*sub1,*sub2;
425 
426   PetscFunctionBegin;
427   ierr = PetscSSLInitializeContext(&ctx);CHKERRQ(ierr);
428   ierr = PetscHTTPSConnect("www.googleapis.com",443,ctx,&sock,&ssl);CHKERRQ(ierr);
429   ierr = PetscSNPrintf(body,512,"{\"longUrl\": \"%s\"}",url);CHKERRQ(ierr);
430   ierr = PetscHTTPSRequest("POST","https://www.googleapis.com/urlshortener/v1/url",NULL,"application/json",body,ssl,buff,sizeof(buff));CHKERRQ(ierr);
431   ierr = PetscSSLDestroyContext(ctx);CHKERRQ(ierr);
432   close(sock);
433   ierr = PetscStrstr(buff,"\"id\": \"",&sub1);CHKERRQ(ierr);
434   if (sub1) {
435     sub1 += 7;
436     ierr = PetscStrstr(sub1,"\"",&sub2);CHKERRQ(ierr);
437     if (!sub2) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Google did not shorten URL");
438     sub2[0] = 0;
439     ierr = PetscStrncpy(shorturl,sub1,lenshorturl);CHKERRQ(ierr);
440   } else SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Google did not shorten URL");
441   PetscFunctionReturn(0);
442 }
443 
444