Windows: improve error reporting
[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 //#include <rpc.h>
37 #include <msrpc-mingw.h>
38
39 #include "libmoonshot.h"
40 #include "libmoonshot-common.h"
41 #include "moonshot-msrpc.h"
42
43 #include <stdarg.h>
44 #include <stdio.h>
45 #include <string.h>
46
47 #define MOONSHOT_ENDPOINT_NAME "/org/janet/Moonshot"
48 #define MOONSHOT_INSTALL_PATH_KEY "Software\\Moonshot"
49
50 void *__RPC_USER MIDL_user_allocate (size_t size) {
51     return malloc (size);
52 }
53
54 void __RPC_USER MIDL_user_free (void *data) {
55     if (data == NULL)
56         return;
57
58     free (data);
59 }
60
61 void moonshot_free (void *data)
62 {
63     free (data);
64 }
65
66 static MoonshotError *moonshot_error_new_from_status (MoonshotErrorCode code,
67                                                       DWORD             status)
68 {
69     MoonshotError *error = malloc (sizeof (MoonshotError));
70     error->code = code;
71     error->message = malloc (256);
72
73     FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, (LPSTR)error->message, 255, NULL);
74     return error;
75 }
76
77 static MoonshotError *moonshot_error_new_with_status (MoonshotErrorCode code,
78                                                       DWORD             status,
79                                                       const char        *format, ...)
80 {
81     MoonshotError *error = malloc (sizeof (MoonshotError));
82     char *buffer;
83     va_list args;
84     int length;
85
86     va_start (args, format);
87
88     error->code = code;
89
90     buffer = malloc (strlen (format) + 256 + 3);
91     strcpy (buffer, format);
92     strcat (buffer, ": ");
93
94     FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
95                    NULL,
96                    status,
97                    0,
98                    (LPSTR)buffer + strlen (format) + 3,
99                    255,
100                    NULL);
101
102     length = _vscprintf (buffer, args);
103     error->message = malloc (length + 1);
104     _vsnprintf (error->message, length, buffer, args);
105     free (buffer);
106
107     va_end (args);
108
109     return error;
110 }
111
112 static void launch_server (MoonshotError **error) {
113     HKEY key = NULL;
114     STARTUPINFO startup_info = { 0 };
115     PROCESS_INFORMATION process_info = { 0 };
116     LONG status;
117     BOOL success;
118     DWORD value_type;
119     DWORD length;
120     char exe_path[1024];
121
122     status = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
123                            MOONSHOT_INSTALL_PATH_KEY,
124                            0,
125                            KEY_READ,
126                            &key);
127
128     if (status == ERROR_FILE_NOT_FOUND) {
129         *error = moonshot_error_new
130                    (MOONSHOT_ERROR_INSTALLATION_ERROR,
131                     "Moonshot is not installed correctly on this system. "
132                     "(Registry key HKLM\\%s was not found).",
133                     MOONSHOT_INSTALL_PATH_KEY);
134         return;
135     } else if (status != 0) {
136         *error = moonshot_error_new_with_status
137                    (MOONSHOT_ERROR_OS_ERROR,
138                     status,
139                     "Unable to read registry key HKLM\\%s",
140                     MOONSHOT_INSTALL_PATH_KEY);
141         return;
142     }
143
144     length = 1023;
145     status = RegQueryValueEx (key, NULL, NULL, &value_type, (LPBYTE )exe_path, &length);
146
147     if (value_type != REG_SZ) {
148         *error = moonshot_error_new_with_status
149                    (MOONSHOT_ERROR_INSTALLATION_ERROR,
150                     status,
151                     "Value of registry key HKLM\\%s is invalid. Please set it "
152                     "to point to the location of moonshot.exe",
153                     MOONSHOT_INSTALL_PATH_KEY);
154         return;
155     }
156
157
158     if (status != 0) {
159         *error = moonshot_error_new_with_status
160                    (MOONSHOT_ERROR_OS_ERROR,
161                     status,
162                     "Unable to read value of registry key HKLM\\%s",
163                     MOONSHOT_INSTALL_PATH_KEY);
164         return;
165     }
166
167     startup_info.cb = sizeof (startup_info);
168     success = CreateProcess (exe_path,
169                              NULL,
170                              NULL,
171                              NULL,
172                              TRUE,
173                              DETACHED_PROCESS,
174                              NULL,
175                              NULL,
176                              &startup_info, &process_info);
177
178     if (! success) {
179         *error = moonshot_error_new_with_status
180                    (MOONSHOT_ERROR_UNABLE_TO_START_SERVICE,
181                     GetLastError (),
182                     "Unable to spawn the moonshot server at '%s'",
183                     exe_path);
184         return;
185     }
186 }
187
188 static void bind_rpc (MoonshotError **error)
189 {
190     DWORD status;
191     int   i;
192
193     status = rpc_client_bind (&moonshot_binding_handle,
194                               MOONSHOT_ENDPOINT_NAME,
195                               RPC_PER_USER);
196
197     if (status != RPC_S_OK) {
198         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
199                                                  status);
200         return;
201     }
202
203     status = RpcMgmtIsServerListening (moonshot_binding_handle);
204
205     if (status == RPC_S_NOT_LISTENING) {
206         launch_server (error);
207
208         if (*error != NULL)
209             return;
210
211         /* Allow 1 minute for the server to launch before we time out */
212         for (i=0; i<600; i++) {
213             Sleep (100); /* ms */
214
215             status = RpcMgmtIsServerListening (moonshot_binding_handle);
216
217             if (status == RPC_S_OK)
218                 return;
219
220             if (status != RPC_S_NOT_LISTENING)
221                 break;
222         }
223     }
224
225     if (status != RPC_S_OK)
226         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
227                                                  status);
228 }
229
230 static void init_rpc (MoonshotError **error)
231 {
232     static volatile LONG binding_init_flag = 2;
233
234     /* Hack to avoid requiring a moonshot_init() function. Windows does not
235      * provide any synchronisation primitives that can be statically init'ed,
236      * but we can use its atomic variable access functions to achieve the same.
237      * See: http://msdn.microsoft.com/en-us/library/ms684122%28v=vs.85%29.aspx
238      */
239
240     if (binding_init_flag == 0)
241         return;
242
243     if (InterlockedCompareExchange (&binding_init_flag, 1, 2) == 2) {
244         bind_rpc (error);
245
246         /* We'll handle all exceptions locally to avoid interfering with any
247          * other RPC/other exception handling that goes on in the process,
248          * and so we can store the problem in a MooshotError instead of
249          * aborting.
250          */
251         rpc_set_global_exception_handler_enable (FALSE);
252
253         if (InterlockedCompareExchange (&binding_init_flag, 0, 1) != 1) {
254             /* This should never happen */
255             fprintf (stderr, "moonshot: Internal synchronisation error");
256         }
257     } else {
258         while (binding_init_flag != 0)
259             Sleep (100); /* ms */
260     }
261 }
262
263
264 int moonshot_get_identity (const char     *nai,
265                            const char     *password,
266                            const char     *service,
267                            char          **nai_out,
268                            char          **password_out,
269                            char          **server_certificate_hash_out,
270                            char          **ca_certificate_out,
271                            char          **subject_name_constraint_out,
272                            char          **subject_alt_name_constraint_out,
273                            MoonshotError **error)
274 {
275     int success = FALSE;
276     RpcAsyncCall call;
277
278     init_rpc (error);
279
280     if (*error != NULL)
281         return FALSE;
282
283     rpc_async_call_init (&call);
284
285     if (nai == NULL) nai = "";
286     if (password == NULL) password = "";
287     if (service == NULL) service = "";
288
289     *nai_out = NULL;
290     *password_out = NULL;
291     *server_certificate_hash_out = NULL;
292     *ca_certificate_out = NULL;
293     *subject_name_constraint_out = NULL;
294     *subject_alt_name_constraint_out = NULL;
295
296     RPC_TRY_EXCEPT {
297         moonshot_get_identity_rpc (&call,
298                                    nai,
299                                    password,
300                                    service,
301                                    nai_out,
302                                    password_out,
303                                    server_certificate_hash_out,
304                                    ca_certificate_out,
305                                    subject_name_constraint_out,
306                                    subject_alt_name_constraint_out);
307
308         success = rpc_async_call_complete_int (&call);
309     }
310     RPC_EXCEPT {
311         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
312                                                  RPC_GET_EXCEPTION_CODE ());
313     }
314     RPC_END_EXCEPT
315
316     if (*error != NULL)
317         return FALSE;
318
319     if (success == FALSE) {
320         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
321                                      "No identity was returned by the Moonshot "
322                                      "user interface.");
323         return FALSE;
324     }
325
326     return TRUE;
327 }
328
329
330 int moonshot_get_default_identity (char          **nai_out,
331                                    char          **password_out,
332                                    char          **server_certificate_hash_out,
333                                    char          **ca_certificate_out,
334                                    char          **subject_name_constraint_out,
335                                    char          **subject_alt_name_constraint_out,
336                                    MoonshotError **error)
337 {
338     int success = FALSE;
339     RpcAsyncCall call;
340
341     init_rpc (error);
342
343     if (*error != NULL)
344         return FALSE;
345
346     rpc_async_call_init (&call);
347
348     *nai_out = NULL;
349     *password_out = NULL;
350     *server_certificate_hash_out = NULL;
351     *ca_certificate_out = NULL;
352     *subject_name_constraint_out = NULL;
353     *subject_alt_name_constraint_out = NULL;
354
355     RPC_TRY_EXCEPT {
356         moonshot_get_default_identity_rpc (&call,
357                                            nai_out,
358                                            password_out,
359                                            server_certificate_hash_out,
360                                            ca_certificate_out,
361                                            subject_name_constraint_out,
362                                            subject_alt_name_constraint_out);
363
364         success = rpc_async_call_complete_int (&call);
365     }
366     RPC_EXCEPT {
367         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
368                                                  RPC_GET_EXCEPTION_CODE ());
369     }
370     RPC_END_EXCEPT
371
372     if (*error != NULL)
373         return FALSE;
374
375     if (success == FALSE) {
376         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
377                                      "No identity was returned by the Moonshot "
378                                      "user interface.");
379         return FALSE;
380     }
381
382     return TRUE;
383 };
384
385 int moonshot_install_id_card (const char     *display_name,
386                               const char     *user_name,
387                               const char     *password,
388                               const char     *realm,
389                               char           *rules_patterns[],
390                               int             rules_patterns_length,
391                               char           *rules_always_confirm[],
392                               int             rules_always_confirm_length,
393                               char           *services[],
394                               int             services_length,
395                               const char     *ca_cert,
396                               const char     *subject,
397                               const char     *subject_alt,
398                               const char     *server_cert,
399                               int             force_flat_file_store,
400                               MoonshotError **error)
401 {
402     int success = FALSE;
403
404     init_rpc (error);
405     if (*error != NULL)
406         return FALSE;
407
408     if (user_name == NULL) user_name = "";
409     if (password == NULL) password = "";
410     if (realm == NULL) realm = "";
411     if (ca_cert == NULL) ca_cert = "";
412     if (subject == NULL) subject = "";
413     if (subject_alt == NULL) subject_alt = "";
414     if (server_cert == NULL) server_cert = "";
415
416     RPC_TRY_EXCEPT {
417         success = moonshot_install_id_card_rpc (display_name,
418                                                 user_name,
419                                                 password,
420                                                 realm,
421                                                 rules_patterns,
422                                                 rules_patterns_length,
423                                                 rules_always_confirm,
424                                                 rules_always_confirm_length,
425                                                 services,
426                                                 services_length,
427                                                 ca_cert,
428                                                 subject,
429                                                 subject_alt,
430                                                 server_cert,
431                                                 force_flat_file_store);
432     }
433     RPC_EXCEPT {
434         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
435                                                  RPC_GET_EXCEPTION_CODE ());
436     }
437     RPC_END_EXCEPT
438     return success;
439 }
440
441 BOOL WINAPI DllMain (HINSTANCE  hinst,
442                      DWORD      reason,
443                      void      *reserved)
444 {
445     if (reason == DLL_PROCESS_DETACH) {
446         /* Process exiting/DLL being unloaded. This is a good
447          * opportunity to free the RPC binding.
448          *
449          * FIXME: we can't use the msrpc-mingw routine for this in case
450          * it was already unloaded. I'd love to work out how to link
451          * that library statically into libmoonshot-0.dll.
452          */
453         RpcBindingFree (&moonshot_binding_handle);
454     }
455
456     return TRUE;
457 }