client lib: don't link to GLib on Windows
[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         free (data);
56 }
57
58 static MoonshotError *moonshot_error_new_from_status (MoonshotErrorCode code,
59                                                       DWORD             status)
60 {
61     MoonshotError *error = malloc (sizeof (MoonshotError));
62     error->code = code;
63     error->message = malloc (256);
64
65     FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, (LPSTR)error->message, 255, NULL);
66     return error;
67 }
68
69 static MoonshotError *moonshot_error_new_with_status (MoonshotErrorCode code,
70                                                       DWORD             status,
71                                                       const char        *format, ...)
72 {
73     MoonshotError *error = malloc (sizeof (MoonshotError));
74     char *buffer;
75     va_list args;
76     int length;
77
78     va_start (args, format);
79
80     error->code = code;
81
82     buffer = malloc (strlen (format) + 256 + 3);
83     strcpy (buffer, format);
84     strcat (buffer, ": ");
85
86     FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
87                    NULL,
88                    status,
89                    0,
90                    (LPSTR)buffer + strlen (format) + 3,
91                    255,
92                    NULL);
93
94     length = _vscprintf (buffer, args);
95     error->message = malloc (length + 1);
96     _vsnprintf (error->message, length, buffer, args);
97     free (buffer);
98
99     va_end (args);
100
101     return error;
102 }
103
104 static void launch_server (MoonshotError **error) {
105     HKEY key = NULL;
106     STARTUPINFO startup_info = { 0 };
107     PROCESS_INFORMATION process_info = { 0 };
108     LONG status;
109     BOOL success;
110     DWORD value_type;
111     DWORD length;
112     char exe_path[1024];
113
114     status = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
115                            MOONSHOT_INSTALL_PATH_KEY,
116                            0,
117                            KEY_READ,
118                            &key);
119
120     if (status == ERROR_FILE_NOT_FOUND) {
121         *error = moonshot_error_new
122                    (MOONSHOT_ERROR_INSTALLATION_ERROR,
123                     "Moonshot is not installed correctly on this system. "
124                     "(Registry key HKLM\\%s was not found).",
125                     MOONSHOT_INSTALL_PATH_KEY);
126         return;
127     } else if (status != 0) {
128         *error = moonshot_error_new_with_status
129                    (MOONSHOT_ERROR_OS_ERROR,
130                     status,
131                     "Unable to read registry key HKLM\\%s",
132                     MOONSHOT_INSTALL_PATH_KEY);
133         return;
134     }
135
136     length = 1023;
137     status = RegQueryValueEx (key, NULL, NULL, &value_type, exe_path, &length);
138
139     if (value_type != REG_SZ) {
140         *error = moonshot_error_new_with_status
141                    (MOONSHOT_ERROR_INSTALLATION_ERROR,
142                     status,
143                     "Value of registry key HKLM\\%s is invalid. Please set it "
144                     "to point to the location of moonshot.exe",
145                     MOONSHOT_INSTALL_PATH_KEY);
146         return;
147     }
148
149
150     if (status != 0) {
151         *error = moonshot_error_new_with_status
152                    (MOONSHOT_ERROR_OS_ERROR,
153                     status,
154                     "Unable to read value of registry key HKLM\\%s",
155                     MOONSHOT_INSTALL_PATH_KEY);
156         return;
157     }
158
159     startup_info.cb = sizeof (startup_info);
160
161     success = CreateProcess (exe_path,
162                              NULL,
163                              NULL,
164                              NULL,
165                              TRUE,
166                              DETACHED_PROCESS,
167                              NULL,
168                              NULL,
169                              &startup_info, &process_info);
170
171     if (! success) {
172         *error = moonshot_error_new_with_status
173                    (MOONSHOT_ERROR_UNABLE_TO_START_SERVICE,
174                     GetLastError (),
175                     "Unable to spawn the moonshot server at '%s'",
176                     exe_path);
177         return;
178     }
179 }
180
181 static void bind_rpc (MoonshotError **error)
182 {
183     DWORD status;
184     int   i;
185
186     status = rpc_client_bind (&moonshot_binding_handle,
187                               MOONSHOT_ENDPOINT_NAME,
188                               RPC_PER_USER);
189
190     if (status != RPC_S_OK) {
191         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
192                                                  status);
193         return;
194     }
195
196     status = RpcMgmtIsServerListening (moonshot_binding_handle);
197
198     if (status == RPC_S_NOT_LISTENING) {
199         launch_server (error);
200
201         if (*error != NULL)
202             return;
203
204         /* Allow 1 minute for the server to launch before we time out */
205         for (i=0; i<600; i++) {
206             Sleep (100); /* ms */
207
208             status = RpcMgmtIsServerListening (moonshot_binding_handle);
209
210             if (status == RPC_S_OK)
211                 return;
212
213             if (status != RPC_S_NOT_LISTENING)
214                 break;
215         }
216     }
217
218     if (status != RPC_S_OK)
219         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
220                                                  status);
221 }
222
223 static void init_rpc (MoonshotError **error)
224 {
225     static volatile LONG binding_init_flag = 2;
226     int status;
227
228     /* Hack to avoid requiring a moonshot_init() function. Windows does not
229      * provide any synchronisation primitives that can be statically init'ed,
230      * but we can use its atomic variable access functions to achieve the same.
231      * See: http://msdn.microsoft.com/en-us/library/ms684122%28v=vs.85%29.aspx
232      */
233
234     if (binding_init_flag == 0)
235         return;
236
237     if (InterlockedCompareExchange (&binding_init_flag, 1, 2) == 2) {
238         bind_rpc (error);
239
240         /* We'll handle all exceptions locally to avoid interfering with any
241          * other RPC/other exception handling that goes on in the process,
242          * and so we can store the problem in a MooshotError instead of
243          * aborting.
244          */
245         rpc_set_global_exception_handler_enable (FALSE);
246
247         if (InterlockedCompareExchange (&binding_init_flag, 0, 1) != 1) {
248             /* This should never happen */
249             fprintf (stderr, "moonshot: Internal synchronisation error");
250         }
251     } else {
252         while (binding_init_flag != 0)
253             Sleep (100); /* ms */
254     }
255 }
256
257
258 int moonshot_get_identity (const char     *nai,
259                            const char     *password,
260                            const char     *service,
261                            char          **nai_out,
262                            char          **password_out,
263                            char          **server_certificate_hash_out,
264                            char          **ca_certificate_out,
265                            char          **subject_name_constraint_out,
266                            char          **subject_alt_name_constraint_out,
267                            MoonshotError **error)
268 {
269     int success;
270     RpcAsyncCall call;
271
272     init_rpc (error);
273
274     if (*error != NULL)
275         return FALSE;
276
277     rpc_async_call_init (&call);
278
279     *nai_out = NULL;
280     *password_out = NULL;
281     *server_certificate_hash_out = NULL;
282     *ca_certificate_out = NULL;
283     *subject_name_constraint_out = NULL;
284     *subject_alt_name_constraint_out = NULL;
285
286     RPC_TRY_EXCEPT {
287         moonshot_get_identity_rpc (&call,
288                                    nai,
289                                    password,
290                                    service,
291                                    nai_out,
292                                    password_out,
293                                    server_certificate_hash_out,
294                                    ca_certificate_out,
295                                    subject_name_constraint_out,
296                                    subject_alt_name_constraint_out);
297
298         success = rpc_async_call_complete_int (&call);
299     }
300     RPC_EXCEPT {
301         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
302                                                  RPC_GET_EXCEPTION_CODE ());
303     }
304     RPC_END_EXCEPT
305
306     if (*error != NULL)
307         return FALSE;
308
309     if (success == FALSE) {
310         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
311                                      "No identity was returned by the Moonshot "
312                                      "user interface.");
313         return FALSE;
314     }
315
316     return TRUE;
317 }
318
319
320 int moonshot_get_default_identity (char          **nai_out,
321                                    char          **password_out,
322                                    char          **server_certificate_hash_out,
323                                    char          **ca_certificate_out,
324                                    char          **subject_name_constraint_out,
325                                    char          **subject_alt_name_constraint_out,
326                                    MoonshotError **error)
327 {
328     int success;
329     RpcAsyncCall call;
330
331     init_rpc (error);
332
333     if (*error != NULL)
334         return FALSE;
335
336     rpc_async_call_init (&call);
337
338     *nai_out = NULL;
339     *password_out = NULL;
340     *server_certificate_hash_out = NULL;
341     *ca_certificate_out = NULL;
342     *subject_name_constraint_out = NULL;
343     *subject_alt_name_constraint_out = NULL;
344
345     RPC_TRY_EXCEPT {
346         moonshot_get_default_identity_rpc (&call,
347                                            nai_out,
348                                            password_out,
349                                            server_certificate_hash_out,
350                                            ca_certificate_out,
351                                            subject_name_constraint_out,
352                                            subject_alt_name_constraint_out);
353
354         success = rpc_async_call_complete_int (&call);
355     }
356     RPC_EXCEPT {
357         *error = moonshot_error_new_from_status (MOONSHOT_ERROR_IPC_ERROR,
358                                                  RPC_GET_EXCEPTION_CODE ());
359     }
360     RPC_END_EXCEPT
361
362     if (*error != NULL)
363         return FALSE;
364
365     if (success == FALSE) {
366         *error = moonshot_error_new (MOONSHOT_ERROR_NO_IDENTITY_SELECTED,
367                                      "No identity was returned by the Moonshot "
368                                      "user interface.");
369         return FALSE;
370     }
371
372     return TRUE;
373 };