xref: /petsc/src/sys/webclient/globus.c (revision 0ea77eda237b1068ff0d8bfa28c3463dc2087695)
1 #include <petscwebclient.h>
2 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
3 #pragma gcc diagnostic   ignored "-Wdeprecated-declarations"
4 
5 /*
6     Encodes and decodes from MIME Base64
7 */
8 static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
9                                 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
10 
11 static PetscErrorCode base64_encode(const unsigned char *data, unsigned char *encoded_data, size_t len) {
12   static size_t mod_table[] = {0, 2, 1};
13   size_t        i, j;
14   size_t        input_length, output_length;
15 
16   PetscFunctionBegin;
17   PetscCall(PetscStrlen((const char *)data, &input_length));
18   output_length = 4 * ((input_length + 2) / 3);
19   PetscCheck(output_length <= len, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Output length not large enough");
20 
21   for (i = 0, j = 0; i < input_length;) {
22     uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
23     uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
24     uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
25     uint32_t triple  = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
26 
27     encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
28     encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
29     encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
30     encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
31   }
32   encoded_data[j] = 0;
33   for (i = 0; i < mod_table[input_length % 3]; i++) encoded_data[output_length - 1 - i] = '=';
34   PetscFunctionReturn(0);
35 }
36 
37 PETSC_UNUSED static PetscErrorCode base64_decode(const unsigned char *data, unsigned char *decoded_data, size_t length) {
38   static char decoding_table[257];
39   static int  decode_table_built = 0;
40   size_t      i, j;
41   size_t      input_length, output_length;
42 
43   PetscFunctionBegin;
44   if (!decode_table_built) {
45     for (i = 0; i < 64; i++) decoding_table[(unsigned char)encoding_table[i]] = i;
46     decode_table_built = 1;
47   }
48 
49   PetscCall(PetscStrlen((const char *)data, &input_length));
50   PetscCheck(input_length % 4 == 0, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Input length must be divisible by 4");
51 
52   output_length = input_length / 4 * 3;
53   if (data[input_length - 1] == '=') (output_length)--;
54   if (data[input_length - 2] == '=') (output_length)--;
55   PetscCheck(output_length <= length, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Output length too shore");
56 
57   for (i = 0, j = 0; i < input_length;) {
58     uint32_t sextet_a = data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]];
59     uint32_t sextet_b = data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]];
60     uint32_t sextet_c = data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]];
61     uint32_t sextet_d = data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]];
62     uint32_t triple   = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6);
63 
64     if (j < output_length) decoded_data[j++] = (triple >> 2 * 8) & 0xFF;
65     if (j < output_length) decoded_data[j++] = (triple >> 1 * 8) & 0xFF;
66     if (j < output_length) decoded_data[j++] = (triple >> 0 * 8) & 0xFF;
67   }
68   decoded_data[j] = 0;
69   PetscFunctionReturn(0);
70 }
71 
72 #if defined(PETSC_HAVE_UNISTD_H)
73 #include <unistd.h>
74 #endif
75 
76 /*@C
77      PetscGlobusAuthorize - Get an access token allowing PETSc applications to make Globus file transfer requests
78 
79    Not collective, only the first process in `MPI_Comm` does anything
80 
81    Input Parameters:
82 +  comm - the MPI communicator
83 -  tokensize - size of the token array
84 
85    Output Parameters:
86 .  access_token - can be used with `PetscGlobusUpLoad()` for 30 days
87 
88    Notes:
89     This call requires stdout and stdin access from process 0 on the MPI communicator
90 
91    You can run src/sys/webclient/tutorials/globusobtainaccesstoken to get an access token
92 
93    Level: intermediate
94 
95 .seealso: `PetscGoogleDriveRefresh()`, `PetscGoogleDriveUpload()`, `PetscURLShorten()`, `PetscGlobusUpload()`
96 @*/
97 PetscErrorCode PetscGlobusAuthorize(MPI_Comm comm, char access_token[], size_t tokensize) {
98   SSL_CTX    *ctx;
99   SSL        *ssl;
100   int         sock;
101   char        buff[8 * 1024], *ptr, head[1024];
102   PetscMPIInt rank;
103   size_t      len;
104   PetscBool   found;
105 
106   PetscFunctionBegin;
107   PetscCallMPI(MPI_Comm_rank(comm, &rank));
108   if (rank == 0) {
109     PetscCheck(isatty(fileno(PETSC_STDOUT)), PETSC_COMM_SELF, PETSC_ERR_USER, "Requires users input/output");
110     PetscCall(PetscPrintf(comm, "Enter globus username:"));
111     ptr = fgets(buff, 1024, stdin);
112     PetscCheck(ptr, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Error reading from stdin: %d", errno);
113     PetscCall(PetscStrlen(buff, &len));
114     buff[len - 1] = ':'; /* remove carriage return at end of line */
115 
116     PetscCall(PetscPrintf(comm, "Enter globus password:"));
117     ptr = fgets(buff + len, 1024 - len, stdin);
118     PetscCheck(ptr, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Error reading from stdin: %d", errno);
119     PetscCall(PetscStrlen(buff, &len));
120     buff[len - 1] = '\0'; /* remove carriage return at end of line */
121     PetscCall(PetscStrcpy(head, "Authorization: Basic "));
122     PetscCall(base64_encode((const unsigned char *)buff, (unsigned char *)(head + 21), sizeof(head) - 21));
123     PetscCall(PetscStrcat(head, "\r\n"));
124 
125     PetscCall(PetscSSLInitializeContext(&ctx));
126     PetscCall(PetscHTTPSConnect("nexus.api.globusonline.org", 443, ctx, &sock, &ssl));
127     PetscCall(PetscHTTPSRequest("GET", "nexus.api.globusonline.org/goauth/token?grant_type=client_credentials", head, "application/x-www-form-urlencoded", NULL, ssl, buff, sizeof(buff)));
128     PetscCall(PetscSSLDestroyContext(ctx));
129     close(sock);
130 
131     PetscCall(PetscPullJSONValue(buff, "access_token", access_token, tokensize, &found));
132     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Globus did not return access token");
133 
134     PetscCall(PetscPrintf(comm, "Here is your Globus access token, save it in a save place, in the future you can run PETSc\n"));
135     PetscCall(PetscPrintf(comm, "programs with the option -globus_access_token %s\n", access_token));
136     PetscCall(PetscPrintf(comm, "to access Globus automatically\n"));
137   }
138   PetscFunctionReturn(0);
139 }
140 
141 /*@C
142      PetscGlobusGetTransfers - Get a record of current transfers requested from Globus
143 
144    Not collective, only the first process in `MPI_Comm` does anything
145 
146    Input Parameters:
147 +  comm - the MPI communicator
148 .  access_token - Globus access token, if NULL will check in options database for -globus_access_token XXX otherwise
149                   will call `PetscGlobusAuthorize()`.
150 -  buffsize - size of the buffer
151 
152    Output Parameters:
153 .  buff - location to put Globus information
154 
155    Level: intermediate
156 
157 .seealso: `PetscGoogleDriveRefresh()`, `PetscGoogleDriveUpload()`, `PetscURLShorten()`, `PetscGlobusUpload()`, `PetscGlobusAuthorize()`
158 @*/
159 PetscErrorCode PetscGlobusGetTransfers(MPI_Comm comm, const char access_token[], char buff[], size_t buffsize) {
160   SSL_CTX    *ctx;
161   SSL        *ssl;
162   int         sock;
163   char        head[4096];
164   PetscMPIInt rank;
165 
166   PetscFunctionBegin;
167   PetscCallMPI(MPI_Comm_rank(comm, &rank));
168   if (rank == 0) {
169     PetscCall(PetscStrcpy(head, "Authorization : Globus-Goauthtoken "));
170     if (access_token) {
171       PetscCall(PetscStrcat(head, access_token));
172     } else {
173       PetscBool set;
174       char      accesstoken[4096];
175       PetscCall(PetscOptionsGetString(NULL, NULL, "-globus_access_token", accesstoken, sizeof(accesstoken), &set));
176       PetscCheck(set, PETSC_COMM_SELF, PETSC_ERR_USER, "Pass in Globus accesstoken or use -globus_access_token XXX");
177       PetscCall(PetscStrcat(head, accesstoken));
178     }
179     PetscCall(PetscStrcat(head, "\r\n"));
180 
181     PetscCall(PetscSSLInitializeContext(&ctx));
182     PetscCall(PetscHTTPSConnect("transfer.api.globusonline.org", 443, ctx, &sock, &ssl));
183     PetscCall(PetscHTTPSRequest("GET", "transfer.api.globusonline.org/v0.10/tasksummary", head, "application/json", NULL, ssl, buff, buffsize));
184     PetscCall(PetscSSLDestroyContext(ctx));
185     close(sock);
186   }
187   PetscFunctionReturn(0);
188 }
189 
190 /*@C
191      PetscGlobusUpload - Loads a file to Globus
192 
193      Not collective, only the first process in the `MPI_Comm` uploads the file
194 
195   Input Parameters:
196 +   comm - MPI communicator
197 .   access_token - obtained with `PetscGlobusAuthorize()`, pass NULL to use -globus_access_token XXX from the PETSc database
198 -   filename - file to upload
199 
200   Options Database Key:
201 .  -globus_access_token XXX - the Globus token
202 
203    Level: intermediate
204 
205 .seealso: `PetscURLShorten()`, `PetscGoogleDriveAuthorize()`, `PetscGoogleDriveRefresh()`, `PetscGlobusAuthorize()`
206 @*/
207 PetscErrorCode PetscGlobusUpload(MPI_Comm comm, const char access_token[], const char filename[]) {
208   SSL_CTX    *ctx;
209   SSL        *ssl;
210   int         sock;
211   char        head[4096], buff[8 * 1024], body[4096], submission_id[4096];
212   PetscMPIInt rank;
213   PetscBool   flg, found;
214 
215   PetscFunctionBegin;
216   PetscCallMPI(MPI_Comm_rank(comm, &rank));
217   if (rank == 0) {
218     PetscCall(PetscTestFile(filename, 'r', &flg));
219     PetscCheck(flg, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to find file: %s", filename);
220 
221     PetscCall(PetscStrcpy(head, "Authorization : Globus-Goauthtoken "));
222     if (access_token) {
223       PetscCall(PetscStrcat(head, access_token));
224     } else {
225       PetscBool set;
226       char      accesstoken[4096];
227       PetscCall(PetscOptionsGetString(NULL, NULL, "-globus_access_token", accesstoken, sizeof(accesstoken), &set));
228       PetscCheck(set, PETSC_COMM_SELF, PETSC_ERR_USER, "Pass in Globus accesstoken or use -globus_access_token XXX");
229       PetscCall(PetscStrcat(head, accesstoken));
230     }
231     PetscCall(PetscStrcat(head, "\r\n"));
232 
233     /* Get Globus submission id */
234     PetscCall(PetscSSLInitializeContext(&ctx));
235     PetscCall(PetscHTTPSConnect("transfer.api.globusonline.org", 443, ctx, &sock, &ssl));
236     PetscCall(PetscHTTPSRequest("GET", "transfer.api.globusonline.org/v0.10/submission_id", head, "application/json", NULL, ssl, buff, sizeof(buff)));
237     PetscCall(PetscSSLDestroyContext(ctx));
238     close(sock);
239     PetscCall(PetscPullJSONValue(buff, "value", submission_id, sizeof(submission_id), &found));
240     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Globus did not return submission id");
241 
242     /* build JSON body of transfer request */
243     PetscCall(PetscStrcpy(body, "{"));
244     PetscCall(PetscPushJSONValue(body, "submission_id", submission_id, sizeof(body)));
245     PetscCall(PetscStrcat(body, ","));
246     PetscCall(PetscPushJSONValue(body, "DATA_TYPE", "transfer", sizeof(body)));
247     PetscCall(PetscStrcat(body, ","));
248     PetscCall(PetscPushJSONValue(body, "sync_level", "null", sizeof(body)));
249     PetscCall(PetscStrcat(body, ","));
250     PetscCall(PetscPushJSONValue(body, "source_endpoint", "barryfsmith#MacBookPro", sizeof(body)));
251     PetscCall(PetscStrcat(body, ","));
252     PetscCall(PetscPushJSONValue(body, "label", "PETSc transfer label", sizeof(body)));
253     PetscCall(PetscStrcat(body, ","));
254     PetscCall(PetscPushJSONValue(body, "length", "1", sizeof(body)));
255     PetscCall(PetscStrcat(body, ","));
256     PetscCall(PetscPushJSONValue(body, "destination_endpoint", "mcs#home", sizeof(body)));
257     PetscCall(PetscStrcat(body, ","));
258 
259     PetscCall(PetscStrcat(body, "\"DATA\": [ {"));
260     PetscCall(PetscPushJSONValue(body, "source_path", "/~/FEM_GPU.pdf", sizeof(body)));
261     PetscCall(PetscStrcat(body, ","));
262     PetscCall(PetscPushJSONValue(body, "destination_path", "/~/FEM_GPU.pdf", sizeof(body)));
263     PetscCall(PetscStrcat(body, ","));
264     PetscCall(PetscPushJSONValue(body, "verify_size", "null", sizeof(body)));
265     PetscCall(PetscStrcat(body, ","));
266     PetscCall(PetscPushJSONValue(body, "recursive", "false", sizeof(body)));
267     PetscCall(PetscStrcat(body, ","));
268     PetscCall(PetscPushJSONValue(body, "DATA_TYPE", "transfer_item", sizeof(body)));
269     PetscCall(PetscStrcat(body, "} ] }"));
270 
271     PetscCall(PetscSSLInitializeContext(&ctx));
272     PetscCall(PetscHTTPSConnect("transfer.api.globusonline.org", 443, ctx, &sock, &ssl));
273     PetscCall(PetscHTTPSRequest("POST", "transfer.api.globusonline.org/v0.10/transfer", head, "application/json", body, ssl, buff, sizeof(buff)));
274     PetscCall(PetscSSLDestroyContext(ctx));
275     close(sock);
276     PetscCall(PetscPullJSONValue(buff, "code", submission_id, sizeof(submission_id), &found));
277     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Globus did not return code on transfer");
278     PetscCall(PetscStrcmp(submission_id, "Accepted", &found));
279     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Globus did not accept transfer");
280   }
281   PetscFunctionReturn(0);
282 }
283