4219d5eaebae26b516043f0cd53e7b12f762d836
[vsc-common.git] / remote / remote.c
1 /*
2  * remote.c: XML-RPC based remote extension for libvsccli
3  *
4  * Copyright (C) 2009 Matthias Bolte <matthias.bolte@googlemail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
19  */
20
21 #include <termios.h>
22
23 #include <xmlrpc-c/base.h>
24 #include <xmlrpc-c/client.h>
25
26 #include <libvscmisc/assert.h>
27 #include <libvscmisc/error.h>
28 #include <libvscmisc/memory.h>
29 #include <libvscmisc/string.h>
30
31 #include <libvsccli/option.h>
32
33 #include "../include/libvscremote/libvscremote.h"
34
35 static int _initialized = FALSE;
36 static VscRemoteMethodNameLookupFunction _method_name_lookup_function = NULL;
37 static VscRemoteArgumentNameLookupFunction _argument_name_lookup_function = NULL;
38 static char *_remote_url = NULL;
39 static char *_username = NULL;
40 static char *_cookie = NULL;
41
42 static void
43 _error_from_xmlrpc (struct VscError *error, const xmlrpc_env *env,
44                     const char *file, const char *function, int line)
45 {
46         vsc_error_report (error, VSC__ERROR_CODE__XMLRPC_ERROR, file, line,
47                           function, "%s (%d)", env->fault_string,
48                           env->fault_code);
49 }
50
51 #define _ERROR_FROM_XMLRPC(error, env) \
52         _error_from_xmlrpc (error, env, __FILE__, __FUNCTION__, __LINE__)
53
54 static char *
55 _request_password (struct VscError *error, const char *username)
56 {
57         struct termios old, new;
58         char *password = NULL;
59         size_t size = 256;
60
61         VSC__ASSERT (error != NULL);
62         VSC__ASSERT (! error->occured);
63
64         password = vsc_alloc (error, size);
65
66         if (error->occured) {
67                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
68                 return NULL;
69         }
70
71         if (tcgetattr (fileno (stdin), &old) != 0) {
72                 VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
73                              "Internal error");
74                 return NULL;
75         }
76
77         new = old;
78         new.c_lflag &= ~ECHO;
79
80         if (tcsetattr (fileno (stdin), TCSAFLUSH, &new) != 0) {
81                 VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
82                              "Internal error");
83                 return NULL;
84         }
85
86         printf ("Enter password for %s: ", username);
87
88         if (fgets (password, size, stdin) == NULL) {
89                 vsc_free (&password);
90         }
91
92         if (password != NULL) {
93                 size = strlen (password);
94
95                 if (size > 0) {
96                         password[size - 1] = '\0';
97                 }
98         }
99
100         (void) tcsetattr (fileno (stdin), TCSAFLUSH, &old);
101
102         printf ("\n");
103
104         return password;
105 }
106
107 static const char *
108 _lookup_method_name (struct VscError *error, const char *command_name)
109 {
110         const char *method_name = NULL;
111
112         VSC__ASSERT (error != NULL);
113         VSC__ASSERT (! error->occured);
114         VSC__ASSERT (_method_name_lookup_function != NULL);
115
116         method_name = _method_name_lookup_function (error, command_name);
117
118         if (error->occured) {
119                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
120                 return NULL;
121         }
122
123         if (method_name == NULL) {
124                 VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
125                              "Internal error");
126                 return NULL;
127         }
128
129         return method_name;
130 }
131
132 static const char *
133 _lookup_argument_name (struct VscError *error, const char *command_name,
134                        const char *option_long_name)
135 {
136         const char *argument_name = NULL;
137
138         VSC__ASSERT (error != NULL);
139         VSC__ASSERT (! error->occured);
140         VSC__ASSERT (_argument_name_lookup_function != NULL);
141
142         argument_name = _argument_name_lookup_function (error, command_name,
143                                                         option_long_name);
144
145         if (error->occured) {
146                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
147                 return NULL;
148         }
149
150         if (argument_name == NULL) {
151                 VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
152                              "Internal error");
153                 return NULL;
154         }
155
156         return argument_name;
157 }
158
159 /*
160  * Login
161  */
162
163 static void
164 _login (struct VscError *error, const struct VscCliCommandInfo *command_info,
165         const struct VscCliOption *option_list, void *user_data);
166
167 static const struct VscCliOptionInfo _login_option_infos[] = {
168         { -1, "username", "username for the remote system",
169           VSC_CLI__OPTION_TYPE__DATA, TRUE },
170         { -1, "password", "password for the remote system",
171           VSC_CLI__OPTION_TYPE__DATA, FALSE },
172         { -1, NULL, NULL, 0, FALSE },
173 };
174
175 const struct VscCliCommandInfo vsc_remote_login_command_info = {
176         "login",
177         "login to the remote system",
178         "",
179         _login,
180         _login_option_infos,
181 };
182
183 static void
184 _login (struct VscError *error,
185         const struct VscCliCommandInfo *command_info VSC__ATTR__UNUSED,
186         const struct VscCliOption *option_list,
187         void *user_data VSC__ATTR__UNUSED)
188 {
189         const char *method_name;
190         const char *username_name;
191         const char *password_name;
192         const char *username;
193         const char *password;
194         char *password_;
195         xmlrpc_env env;
196         xmlrpc_value *result = NULL;
197         int code;
198         char *message = NULL;
199
200         VSC__ASSERT (error != NULL);
201         VSC__ASSERT (! error->occured);
202
203         vsc_remote_check (error, FALSE);
204
205         if (error->occured) {
206                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
207                 return;
208         }
209
210         method_name = _lookup_method_name (error, "login");
211
212         if (error->occured) {
213                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
214                 return;
215         }
216
217         username_name = _lookup_argument_name (error, "login", "username");
218
219         if (error->occured) {
220                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
221                 return;
222         }
223
224         password_name = _lookup_argument_name (error, "login", "password");
225
226         if (error->occured) {
227                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
228                 return;
229         }
230
231         VSC__ASSERT (_username == NULL);
232
233         username = vsc_cli_option_get_string (option_list, "username");
234         password = vsc_cli_option_get_string (option_list, "password");
235
236         VSC__ASSERT (username != NULL);
237
238         if (password == NULL) {
239                 password_ = _request_password (error, username);
240
241                 if (password_ == NULL) {
242                         VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
243                                      "Could not read passowrd");
244                         return;
245                 }
246         } else {
247                 password_ = vsc_strdup (error, password);
248
249                 if (error->occured) {
250                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
251                         return;
252                 }
253         }
254
255         xmlrpc_env_init (&env);
256
257         result = xmlrpc_client_call (&env, _remote_url, method_name,
258                                      "({s:s,s:s})",
259                                      username_name, username,
260                                      password_name, password_);
261
262         if (env.fault_occurred) {
263                 _ERROR_FROM_XMLRPC (error, &env);
264                 result = NULL;
265                 goto cleanup;
266         }
267
268         xmlrpc_decompose_value (&env, result, "{s:i,s:s,s:s,*}",
269                                 "code", &code,
270                                 "message", &message,
271                                 "value", &_cookie);
272
273         if (env.fault_occurred) {
274                 _ERROR_FROM_XMLRPC (error, &env);
275                 message = NULL;
276                 goto cleanup;
277         }
278
279         if (code != 0) {
280                 VSC__ERROR2 (error, VSC__ERROR_CODE__REMOTE_ERROR,
281                              "%s (%d)", message, code);
282                 vsc_free (&_cookie);
283                 goto cleanup;
284         }
285
286         _username = vsc_strdup (error, username);
287
288         if (error->occured) {
289                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
290                 goto cleanup;
291         }
292
293 cleanup:
294         if (result != NULL) {
295                 xmlrpc_DECREF (result);
296         }
297
298         vsc_free (&password_);
299         vsc_free (&message);
300
301         xmlrpc_env_clean (&env);
302 }
303
304 /*
305  * Logout
306  */
307
308 static void
309 _logout (struct VscError *error, const struct VscCliCommandInfo *command_info,
310          const struct VscCliOption *option_list, void *user_data);
311
312 static const struct VscCliOptionInfo _logout_option_infos[] = {
313         { -1, NULL, NULL, 0, FALSE },
314 };
315
316 const struct VscCliCommandInfo vsc_remote_logout_command_info = {
317         "logout",
318         "logout from the remote system",
319         "",
320         _logout,
321         _logout_option_infos,
322 };
323
324 static void
325 _logout (struct VscError *error,
326          const struct VscCliCommandInfo *command_info VSC__ATTR__UNUSED,
327          const struct VscCliOption *option_list VSC__ATTR__UNUSED,
328          void *user_data VSC__ATTR__UNUSED)
329 {
330         const char *method_name = NULL;
331         const char *cookie_name = NULL;
332         xmlrpc_env env;
333         xmlrpc_value *result = NULL;
334         int code;
335         char *message = NULL;
336
337         VSC__ASSERT (error != NULL);
338         VSC__ASSERT (! error->occured);
339
340         vsc_remote_check (error, TRUE);
341
342         if (error->occured) {
343                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
344                 return;
345         }
346
347         method_name = _lookup_method_name (error, "logout");
348
349         if (error->occured) {
350                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
351                 return;
352         }
353
354         cookie_name = _lookup_argument_name (error, "logout", "cookie");
355
356         if (error->occured) {
357                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
358                 return;
359         }
360
361         xmlrpc_env_init (&env);
362
363         result = xmlrpc_client_call (&env, _remote_url, method_name,
364                                      "({s:s})",
365                                      cookie_name, _cookie);
366
367         if (env.fault_occurred) {
368                 _ERROR_FROM_XMLRPC (error, &env);
369                 result = NULL;
370                 goto cleanup;
371         }
372
373         xmlrpc_decompose_value (&env, result, "{s:i,s:s,*}",
374                                 "code", &code,
375                                 "message", &message);
376
377         if (env.fault_occurred) {
378                 _ERROR_FROM_XMLRPC (error, &env);
379                 message = NULL;
380                 goto cleanup;
381         }
382
383         if (code != 0) {
384                 VSC__ERROR2 (error, VSC__ERROR_CODE__REMOTE_ERROR,
385                              "%s (%d)", message, code);
386                 goto cleanup;
387         }
388
389         vsc_free (&_cookie);
390         vsc_free (&_username);
391
392 cleanup:
393         if (result != NULL) {
394                 xmlrpc_DECREF (result);
395         }
396
397         vsc_free (&message);
398
399         xmlrpc_env_clean (&env);
400 }
401
402 /*
403  * Core
404  */
405
406 void
407 vsc_remote_init (struct VscError *error, const char *name, const char *version,
408                  const char *remote_url,
409                  VscRemoteMethodNameLookupFunction method_name_lookup_function,
410                  VscRemoteArgumentNameLookupFunction argument_name_lookup_function)
411 {
412         xmlrpc_env env;
413         char user_agent[256];
414         struct xmlrpc_clientparms clientParms;
415         struct xmlrpc_curl_xportparms curlParms;
416
417         VSC__ASSERT (error != NULL);
418         VSC__ASSERT (! error->occured);
419         VSC__ASSERT (method_name_lookup_function != NULL);
420         VSC__ASSERT (argument_name_lookup_function != NULL);
421
422         if (_initialized) {
423                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
424                              "Already initialized");
425                 return;
426         }
427
428         _method_name_lookup_function = method_name_lookup_function;
429         _argument_name_lookup_function = argument_name_lookup_function;
430
431         if (remote_url != NULL) {
432                 _remote_url = vsc_strdup (error, remote_url);
433
434                 if (error->occured) {
435                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
436                         return;
437                 }
438         } else {
439                 _remote_url = NULL;
440         }
441
442         /*
443          * Configure the XML-RPC-C Curl backend to send a User-Agent.
444          */
445         xmlrpc_env_init (&env);
446
447         memset (&clientParms, 0, sizeof (struct xmlrpc_clientparms));
448         memset (&curlParms, 0, sizeof (struct xmlrpc_curl_xportparms));
449
450         snprintf (user_agent, 256, "%s/%s", name, version);
451
452         curlParms.user_agent = user_agent;
453
454         clientParms.transport = "curl";
455         clientParms.transportparmsP = &curlParms;
456         clientParms.transportparm_size = XMLRPC_CXPSIZE (user_agent);
457
458         xmlrpc_client_init2 (&env, XMLRPC_CLIENT_NO_FLAGS, name, version,
459                              &clientParms, XMLRPC_CPSIZE (transportparm_size));
460
461         if (env.fault_occurred) {
462                 _ERROR_FROM_XMLRPC (error, &env);
463                 goto cleanup;
464         }
465
466         _initialized = TRUE;
467
468 cleanup:
469         xmlrpc_env_clean (&env);
470 }
471
472 void
473 vsc_remote_cleanup (void)
474 {
475         struct VscError error;
476
477         VSC__ASSERT (_initialized);
478
479         if (! _initialized) {
480                 return;
481         }
482
483         /*
484          * FIXME: If an RPC error occured this call segfaults. This only happens if
485          *        the XML-RPC-C library is compiled with -O3. If compiled with -O0
486          *        everything is fine.
487          */
488         xmlrpc_client_cleanup();
489
490         if (_cookie != NULL) {
491                 vsc_error_init (&error);
492
493                 _logout (&error, NULL, NULL, NULL);
494
495                 vsc_error_cleanup (&error);
496         }
497
498         vsc_free (&_remote_url);
499         vsc_free (&_cookie);
500         vsc_free (&_username);
501
502         _method_name_lookup_function = NULL;
503         _argument_name_lookup_function = NULL;
504
505         _initialized = FALSE;
506 }
507
508 void
509 vsc_remote_check (struct VscError *error, int expected_login_state)
510 {
511         VSC__ASSERT (error != NULL);
512         VSC__ASSERT (! error->occured);
513
514         if (! _initialized) {
515                 VSC__ERROR1 (error, VSC__ERROR_CODE__NOT_INITIALIZED,
516                              "Not initialized");
517                 return;
518         }
519
520         if (_remote_url == NULL) {
521                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
522                              "No remote URL specified");
523                 return;
524         }
525
526         if (expected_login_state == TRUE && _cookie == NULL) {
527                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL, "Not logged in");
528                 return;
529         }
530
531         if (expected_login_state == FALSE && _cookie != NULL) {
532                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
533                              "Already logged in");
534                 return;
535         }
536 }
537
538 const char *
539 vsc_remote_get_cookie (struct VscError *error)
540 {
541         VSC__ASSERT (error != NULL);
542         VSC__ASSERT (! error->occured);
543
544         if (! _initialized) {
545                 VSC__ERROR1 (error, VSC__ERROR_CODE__NOT_INITIALIZED,
546                              "Not initialized");
547                 return NULL;
548         }
549
550         if (_cookie == NULL) {
551                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
552                              "No cookie avaiable");
553                 return NULL;
554         }
555
556         return _cookie;
557 }
558
559 const char *
560 vsc_remote_get_username (struct VscError *error)
561 {
562         VSC__ASSERT (error != NULL);
563         VSC__ASSERT (! error->occured);
564
565         if (! _initialized) {
566                 VSC__ERROR1 (error, VSC__ERROR_CODE__NOT_INITIALIZED,
567                              "Not initialized");
568                 return NULL;
569         }
570
571         return _username;
572 }