Add launch-hidden functionality to moonshot msrpc implementation
[moonshot-ui.git] / libmoonshot / libmoonshot-msrpc.c
1 /* libmoonshot - Moonshot client library
2  * Copyright (c) 2011, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * Author: Sam Thursfield <samthursfield@codethink.co.uk>
33  */
34
35 #include <windows.h>
36 #ifdef _MSC_VER
37 #include <rpc.h>
38 #else
39 #include <msrpc-mingw.h>
40 #endif
41
42 #include "libmoonshot.h"
43 #include "libmoonshot-common.h"
44 #include "moonshot-msrpc.h"
45
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <string.h>
49
50 #define MOONSHOT_ENDPOINT_NAME "/org/janet/Moonshot"
51 #define MOONSHOT_INSTALL_PATH_KEY "Software\\Moonshot"
52 #define MOONSHOT_STARTUP_ARGS "--dbus-launched"
53
54 void *__RPC_USER MIDL_user_allocate (size_t size) {
55     return malloc (size);
56 }
57
58 void __RPC_USER MIDL_user_free (void *data) {
59     if (data == NULL)
60         return;
61
62     free (data);
63 }
64
65 void moonshot_free (void *data)
66 {
67     free (data);
68 }
69
70 static MoonshotError *moonshot_error_new_from_status (MoonshotErrorCode code,
71                                                       DWORD             status)
72 {
73     MoonshotError *error = malloc (sizeof (MoonshotError));
74     error->code = code;
75     error->message = malloc (256);
76
77     FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, (LPSTR)error->message, 255, NULL);
78     return error;
79 }
80
81 static MoonshotError *moonshot_error_new_with_status (MoonshotErrorCode code,
82                                                       DWORD             status,
83                                                       const char        *format, ...)
84 {
85     MoonshotError *error = malloc (sizeof (MoonshotError));
86     char *buffer;
87     va_list args;
88     int length;
89
90     va_start (args, format);
91
92     error->code = code;
93
94     buffer = malloc (strlen (format) + 256 + 3);
95     strcpy (buffer, format);
96     strcat (buffer, ": ");
97
98     FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
99                    NULL,
100                    status,
101                    0,
102                    (LPSTR)buffer + strlen (format) + 3,
103                    255,
104                    NULL);
105
106     length = _vscprintf (buffer, args);
107     error->message = malloc (length + 1);
108     _vsnprintf (error->message, length, buffer, args);
109     free (buffer);
110
111     va_end (args);
112
113     return error;
114 }
115
116 static void launch_server (MoonshotError **error) {
117     HKEY key = NULL;
118     STARTUPINFO startup_info = { 0 };
119     PROCESS_INFORMATION process_info = { 0 };
120     LONG status;
121     BOOL success;
122     DWORD value_type;
123     DWORD commandline_length;
124     DWORD length;
125     char* commandline=NULL;
126     char* exe_path=NULL;
127
128     status = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
129                            MOONSHOT_INSTALL_PATH_KEY,
130                            0,
131                            KEY_READ,
132                            &key);
133
134     if (status == ERROR_FILE_NOT_FOUND) {
135         *error = moonshot_error_new
136                    (MOONSHOT_ERROR_INSTALLATION_ERROR,
137                     "Moonshot is not installed correctly on this system. "
138                     "(Registry key HKLM\\%s was not found).",
139                     MOONSHOT_INSTALL_PATH_KEY);
140         return;
141     } else if (status != 0) {
142         *error = moonshot_error_new_with_status
143                    (MOONSHOT_ERROR_OS_ERROR,
144                     status,
145                     "Unable to read registry key HKLM\\%s",
146                     MOONSHOT_INSTALL_PATH_KEY);
147         return;
148     }
149     length = 0;
150     status = RegQueryValueEx (key, NULL, NULL, &value_type, (PVOID )exe_path, &length);
151     if (status == ERROR_SUCCESS) {
152         exe_path = (char *)malloc(length + 1);
153         if (exe_path != NULL) {
154             status = RegQueryValueEx (key, NULL, NULL, &value_type, (PVOID )exe_path, &length);
155         } else {
156             *error = moonshot_error_new
157                         (MOONSHOT_ERROR_OS_ERROR,
158                          "Out of memory allocating %d bytes for registry value",
159                          length);
160             return;
161         }
162     }
163
164     if (status != ERROR_SUCCESS) {
165         *error = moonshot_error_new_with_status
166                    (MOONSHOT_ERROR_OS_ERROR,
167                     status,
168                     "Unable to read value of registry key HKLM\\%s",
169                     MOONSHOT_INSTALL_PATH_KEY);
170         goto cleanup;
171     }
172
173     if (value_type != REG_SZ) {
174         *error = moonshot_error_new_with_status
175                    (MOONSHOT_ERROR_INSTALLATION_ERROR,
176                     status,
177                     "Value of registry key HKLM\\%s is invalid. Please set it "
178                     "to point to the location of moonshot.exe",
179                     MOONSHOT_INSTALL_PATH_KEY);
180         goto cleanup;
181     }
182
183     exe_path[length] = 0;
184     startup_info.cb = sizeof (startup_info);
185     commandline_length = length + 1 + sizeof(MOONSHOT_STARTUP_ARGS);
186     commandline = malloc(commandline_length + 1);
187     if (commandline == NULL) {
188         *error = moonshot_error_new
189                     (MOONSHOT_ERROR_OS_ERROR,
190                         "Out of memory allocating %d bytes for moonshot commandline.",
191                         length);
192         goto cleanup;
193     }
194     snprintf(commandline, commandline_length, "%s %s", exe_path, MOONSHOT_STARTUP_ARGS);
195     commandline[commandline_length] = 0;
196     success = CreateProcess (exe_path,
197                              commandline,
198                              NULL,
199                              NULL,
200                              TRUE,
201                              DETACHED_PROCESS,
202                              NULL,
203                              NULL,
204                              &startup_info, &process_info);
205
206     if (! success) {
207         *error = moonshot_error_new_with_status
208                    (MOONSHOT_ERROR_UNABLE_TO_START_SERVICE,
209                     GetLastError (),
210                     "Unable to spawn the moonshot server at '%s'",
211                     exe_path);
212         goto cleanup;
213     }
214 cleanup:
215     if (exe_path)
216         free(exe_path);
217     if (commandline)
218         free(commandline);
219 }
220
221 static void bind_rpc (MoonshotError **error)
222 {
223     DWORD status;
224     int   i;
225
226     status = rpc_client_bind (&moonshot_binding_handle,
227                               MOONSHOT_ENDPOINT_NAME,
228                               RPC_PER_USER);
229
230     if (status != RPC_S_OK) {
231         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
232                                                  status);
233         return;
234     }
235
236     status = RpcMgmtIsServerListening (moonshot_binding_handle);
237
238     if (status == RPC_S_NOT_LISTENING) {
239         launch_server (error);
240
241         if (*error != NULL)
242             return;
243
244         /* Allow 1 minute for the server to launch before we time out */
245         for (i=0; i<600; i++) {
246             Sleep (100); /* ms */
247
248             status = RpcMgmtIsServerListening (moonshot_binding_handle);
249
250             if (status == RPC_S_OK)
251                 return;
252
253             if (status != RPC_S_NOT_LISTENING)
254                 break;
255         }
256     }
257
258     if (status != RPC_S_OK)
259         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
260                                                  status);
261 }
262
263 static void init_rpc (MoonshotError **error)
264 {
265     static volatile LONG binding_init_flag = 2;
266
267     /* Hack to avoid requiring a moonshot_init() function. Windows does not
268      * provide any synchronisation primitives that can be statically init'ed,
269      * but we can use its atomic variable access functions to achieve the same.
270      * See: http://msdn.microsoft.com/en-us/library/ms684122%28v=vs.85%29.aspx
271      */
272
273     if (binding_init_flag == 0)
274         return;
275
276     if (InterlockedCompareExchange (&binding_init_flag, 1, 2) == 2) {
277         bind_rpc (error);
278
279         /* We'll handle all exceptions locally to avoid interfering with any
280          * other RPC/other exception handling that goes on in the process,
281          * and so we can store the problem in a MooshotError instead of
282          * aborting.
283          */
284         rpc_set_global_exception_handler_enable (FALSE);
285
286         if (InterlockedCompareExchange (&binding_init_flag, 0, 1) != 1) {
287             /* This should never happen */
288             fprintf (stderr, "moonshot: Internal synchronisation error");
289         }
290     } else {
291         while (binding_init_flag != 0)
292             Sleep (100); /* ms */
293     }
294 }
295
296
297 int moonshot_get_identity (const char     *nai,
298                            const char     *password,
299                            const char     *service,
300                            char          **nai_out,
301                            char          **password_out,
302                            char          **server_certificate_hash_out,
303                            char          **ca_certificate_out,
304                            char          **subject_name_constraint_out,
305                            char          **subject_alt_name_constraint_out,
306                            MoonshotError **error)
307 {
308     int success = FALSE;
309     RpcAsyncCall call;
310
311     init_rpc (error);
312
313     if (*error != NULL)
314         return FALSE;
315
316     rpc_async_call_init (&call);
317
318     if (nai == NULL) nai = "";
319     if (password == NULL) password = "";
320     if (service == NULL) service = "";
321
322     *nai_out = NULL;
323     *password_out = NULL;
324     *server_certificate_hash_out = NULL;
325     *ca_certificate_out = NULL;
326     *subject_name_constraint_out = NULL;
327     *subject_alt_name_constraint_out = NULL;
328
329     RPC_TRY_EXCEPT {
330         c_moonshot_get_identity_rpc (&call,
331                                    nai,
332                                    password,
333                                    service,
334                                    nai_out,
335                                    password_out,
336                                    server_certificate_hash_out,
337                                    ca_certificate_out,
338                                    subject_name_constraint_out,
339                                    subject_alt_name_constraint_out);
340
341         success = rpc_async_call_complete_int (&call);
342     }
343     RPC_EXCEPT {
344         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
345                                                  RPC_GET_EXCEPTION_CODE ());
346     }
347     RPC_END_EXCEPT
348
349     if (*error != NULL)
350         return FALSE;
351
352     if (success == FALSE) {
353         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
354                                      "No identity was returned by the Moonshot "
355                                      "user interface.");
356         return FALSE;
357     }
358
359     return TRUE;
360 }
361
362
363 int moonshot_get_default_identity (char          **nai_out,
364                                    char          **password_out,
365                                    char          **server_certificate_hash_out,
366                                    char          **ca_certificate_out,
367                                    char          **subject_name_constraint_out,
368                                    char          **subject_alt_name_constraint_out,
369                                    MoonshotError **error)
370 {
371     int success = FALSE;
372     RpcAsyncCall call;
373
374     init_rpc (error);
375
376     if (*error != NULL)
377         return FALSE;
378
379     rpc_async_call_init (&call);
380
381     *nai_out = NULL;
382     *password_out = NULL;
383     *server_certificate_hash_out = NULL;
384     *ca_certificate_out = NULL;
385     *subject_name_constraint_out = NULL;
386     *subject_alt_name_constraint_out = NULL;
387
388     RPC_TRY_EXCEPT {
389         c_moonshot_get_default_identity_rpc (&call,
390                                            nai_out,
391                                            password_out,
392                                            server_certificate_hash_out,
393                                            ca_certificate_out,
394                                            subject_name_constraint_out,
395                                            subject_alt_name_constraint_out);
396
397         success = rpc_async_call_complete_int (&call);
398     }
399     RPC_EXCEPT {
400         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
401                                                  RPC_GET_EXCEPTION_CODE ());
402     }
403     RPC_END_EXCEPT
404
405     if (*error != NULL)
406         return FALSE;
407
408     if (success == FALSE) {
409         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
410                                      "No identity was returned by the Moonshot "
411                                      "user interface.");
412         return FALSE;
413     }
414
415     return TRUE;
416 };
417
418 int moonshot_install_id_card (const char     *display_name,
419                               const char     *user_name,
420                               const char     *password,
421                               const char     *realm,
422                               char           *rules_patterns[],
423                               int             rules_patterns_length,
424                               char           *rules_always_confirm[],
425                               int             rules_always_confirm_length,
426                               char           *services[],
427                               int             services_length,
428                               const char     *ca_cert,
429                               const char     *subject,
430                               const char     *subject_alt,
431                               const char     *server_cert,
432                               int             force_flat_file_store,
433                               MoonshotError **error)
434 {
435     int success = FALSE;
436
437     init_rpc (error);
438     if (*error != NULL)
439         return FALSE;
440
441     if (user_name == NULL) user_name = "";
442     if (password == NULL) password = "";
443     if (realm == NULL) realm = "";
444     if (ca_cert == NULL) ca_cert = "";
445     if (subject == NULL) subject = "";
446     if (subject_alt == NULL) subject_alt = "";
447     if (server_cert == NULL) server_cert = "";
448
449     RPC_TRY_EXCEPT {
450         success = c_moonshot_install_id_card_rpc (display_name,
451                                                 user_name,
452                                                 password,
453                                                 realm,
454                                                 rules_patterns,
455                                                 rules_patterns_length,
456                                                 rules_always_confirm,
457                                                 rules_always_confirm_length,
458                                                 services,
459                                                 services_length,
460                                                 ca_cert,
461                                                 subject,
462                                                 subject_alt,
463                                                 server_cert,
464                                                 force_flat_file_store);
465     }
466     RPC_EXCEPT {
467         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
468                                                  RPC_GET_EXCEPTION_CODE ());
469     }
470     RPC_END_EXCEPT
471     return success;
472 }
473
474 BOOL WINAPI DllMain (HINSTANCE  hinst,
475                      DWORD      reason,
476                      void      *reserved)
477 {
478     if (reason == DLL_PROCESS_DETACH) {
479         /* Process exiting/DLL being unloaded. This is a good
480          * opportunity to free the RPC binding.
481          *
482          * FIXME: we can't use the msrpc-mingw routine for this in case
483          * it was already unloaded. I'd love to work out how to link
484          * that library statically into libmoonshot-0.dll.
485          */
486         RpcBindingFree (&moonshot_binding_handle);
487     }
488
489     return TRUE;
490 }