Free memory returned by client functions
[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, 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
169     success = CreateProcess (exe_path,
170                              NULL,
171                              NULL,
172                              NULL,
173                              TRUE,
174                              DETACHED_PROCESS,
175                              NULL,
176                              NULL,
177                              &startup_info, &process_info);
178
179     if (! success) {
180         *error = moonshot_error_new_with_status
181                    (MOONSHOT_ERROR_UNABLE_TO_START_SERVICE,
182                     GetLastError (),
183                     "Unable to spawn the moonshot server at '%s'",
184                     exe_path);
185         return;
186     }
187 }
188
189 static void bind_rpc (MoonshotError **error)
190 {
191     DWORD status;
192     int   i;
193
194     status = rpc_client_bind (&moonshot_binding_handle,
195                               MOONSHOT_ENDPOINT_NAME,
196                               RPC_PER_USER);
197
198     if (status != RPC_S_OK) {
199         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
200                                                  status);
201         return;
202     }
203
204     status = RpcMgmtIsServerListening (moonshot_binding_handle);
205
206     if (status == RPC_S_NOT_LISTENING) {
207         launch_server (error);
208
209         if (*error != NULL)
210             return;
211
212         /* Allow 1 minute for the server to launch before we time out */
213         for (i=0; i<600; i++) {
214             Sleep (100); /* ms */
215
216             status = RpcMgmtIsServerListening (moonshot_binding_handle);
217
218             if (status == RPC_S_OK)
219                 return;
220
221             if (status != RPC_S_NOT_LISTENING)
222                 break;
223         }
224     }
225
226     if (status != RPC_S_OK)
227         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
228                                                  status);
229 }
230
231 static void init_rpc (MoonshotError **error)
232 {
233     static volatile LONG binding_init_flag = 2;
234     int status;
235
236     /* Hack to avoid requiring a moonshot_init() function. Windows does not
237      * provide any synchronisation primitives that can be statically init'ed,
238      * but we can use its atomic variable access functions to achieve the same.
239      * See: http://msdn.microsoft.com/en-us/library/ms684122%28v=vs.85%29.aspx
240      */
241
242     if (binding_init_flag == 0)
243         return;
244
245     if (InterlockedCompareExchange (&binding_init_flag, 1, 2) == 2) {
246         bind_rpc (error);
247
248         /* We'll handle all exceptions locally to avoid interfering with any
249          * other RPC/other exception handling that goes on in the process,
250          * and so we can store the problem in a MooshotError instead of
251          * aborting.
252          */
253         rpc_set_global_exception_handler_enable (FALSE);
254
255         if (InterlockedCompareExchange (&binding_init_flag, 0, 1) != 1) {
256             /* This should never happen */
257             fprintf (stderr, "moonshot: Internal synchronisation error");
258         }
259     } else {
260         while (binding_init_flag != 0)
261             Sleep (100); /* ms */
262     }
263 }
264
265
266 int moonshot_get_identity (const char     *nai,
267                            const char     *password,
268                            const char     *service,
269                            char          **nai_out,
270                            char          **password_out,
271                            char          **server_certificate_hash_out,
272                            char          **ca_certificate_out,
273                            char          **subject_name_constraint_out,
274                            char          **subject_alt_name_constraint_out,
275                            MoonshotError **error)
276 {
277     int success;
278     RpcAsyncCall call;
279
280     init_rpc (error);
281
282     if (*error != NULL)
283         return FALSE;
284
285     rpc_async_call_init (&call);
286
287     *nai_out = NULL;
288     *password_out = NULL;
289     *server_certificate_hash_out = NULL;
290     *ca_certificate_out = NULL;
291     *subject_name_constraint_out = NULL;
292     *subject_alt_name_constraint_out = NULL;
293
294     RPC_TRY_EXCEPT {
295         moonshot_get_identity_rpc (&call,
296                                    nai,
297                                    password,
298                                    service,
299                                    nai_out,
300                                    password_out,
301                                    server_certificate_hash_out,
302                                    ca_certificate_out,
303                                    subject_name_constraint_out,
304                                    subject_alt_name_constraint_out);
305
306         success = rpc_async_call_complete_int (&call);
307     }
308     RPC_EXCEPT {
309         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
310                                                  RPC_GET_EXCEPTION_CODE ());
311     }
312     RPC_END_EXCEPT
313
314     if (*error != NULL)
315         return FALSE;
316
317     if (success == FALSE) {
318         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
319                                      "No identity was returned by the Moonshot "
320                                      "user interface.");
321         return FALSE;
322     }
323
324     return TRUE;
325 }
326
327
328 int moonshot_get_default_identity (char          **nai_out,
329                                    char          **password_out,
330                                    char          **server_certificate_hash_out,
331                                    char          **ca_certificate_out,
332                                    char          **subject_name_constraint_out,
333                                    char          **subject_alt_name_constraint_out,
334                                    MoonshotError **error)
335 {
336     int success;
337     RpcAsyncCall call;
338
339     init_rpc (error);
340
341     if (*error != NULL)
342         return FALSE;
343
344     rpc_async_call_init (&call);
345
346     *nai_out = NULL;
347     *password_out = NULL;
348     *server_certificate_hash_out = NULL;
349     *ca_certificate_out = NULL;
350     *subject_name_constraint_out = NULL;
351     *subject_alt_name_constraint_out = NULL;
352
353     RPC_TRY_EXCEPT {
354         moonshot_get_default_identity_rpc (&call,
355                                            nai_out,
356                                            password_out,
357                                            server_certificate_hash_out,
358                                            ca_certificate_out,
359                                            subject_name_constraint_out,
360                                            subject_alt_name_constraint_out);
361
362         success = rpc_async_call_complete_int (&call);
363     }
364     RPC_EXCEPT {
365         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
366                                                  RPC_GET_EXCEPTION_CODE ());
367     }
368     RPC_END_EXCEPT
369
370     if (*error != NULL)
371         return FALSE;
372
373     if (success == FALSE) {
374         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
375                                      "No identity was returned by the Moonshot "
376                                      "user interface.");
377         return FALSE;
378     }
379
380     return TRUE;
381 };
382
383 BOOL WINAPI DllMain (HINSTANCE  hinst,
384                      DWORD      reason,
385                      void      *reserved)
386 {
387     if (reason == DLL_PROCESS_DETACH) {
388         /* Process exiting/DLL being unloaded. This is a good
389          * opportunity to free the RPC binding.
390          *
391          * FIXME: we can't use the msrpc-mingw routine for this in case
392          * it was already unloaded. I'd love to work out how to link
393          * that library statically into libmoonshot-0.dll.
394          */
395         RpcBindingFree (&moonshot_binding_handle);
396     }
397
398     return TRUE;
399 }