]> asedeno.scripts.mit.edu Git - PuTTY_svn.git/blob - windows/winshare.c
Implement connection sharing between instances of PuTTY.
[PuTTY_svn.git] / windows / winshare.c
1 /*
2  * Windows implementation of SSH connection-sharing IPC setup.
3  */
4
5 #include <stdio.h>
6 #include <assert.h>
7
8 #define DEFINE_PLUG_METHOD_MACROS
9 #include "tree234.h"
10 #include "putty.h"
11 #include "network.h"
12 #include "proxy.h"
13 #include "ssh.h"
14
15 #if !defined NO_SECURITY
16
17 #include "winsecur.h"
18
19 #define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare"
20 #define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex"
21
22 static char *obfuscate_name(const char *realname)
23 {
24     /*
25      * Windows's named pipes all live in the same namespace, so one
26      * user can see what pipes another user has open. This is an
27      * undesirable privacy leak and in particular permits one user to
28      * know what username@host another user is SSHing to, so we
29      * protect that information by using CryptProtectMemory (which
30      * uses a key built in to each user's account).
31      */
32     char *cryptdata;
33     int cryptlen;
34     SHA256_State sha;
35     unsigned char lenbuf[4];
36     unsigned char digest[32];
37     char retbuf[65];
38     int i;
39
40     cryptlen = strlen(realname) + 1;
41     cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
42     cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
43     cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
44
45     cryptdata = snewn(cryptlen, char);
46     memset(cryptdata, 0, cryptlen);
47     strcpy(cryptdata, realname);
48
49     /*
50      * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
51      * use the same key in all processes with this user id, meaning
52      * that the next PuTTY process calling this function with the same
53      * input will get the same data.
54      *
55      * (Contrast with CryptProtectData, which invents a new session
56      * key every time since its API permits returning more data than
57      * was input, so calling _that_ and hashing the output would not
58      * be stable.)
59      */
60     if (!p_CryptProtectMemory(cryptdata, cryptlen,
61                               CRYPTPROTECTMEMORY_CROSS_PROCESS)) {
62         return NULL;
63     }
64
65     /*
66      * We don't want to give away the length of the hostname either,
67      * so having got it back out of CryptProtectMemory we now hash it.
68      */
69     SHA256_Init(&sha);
70     PUT_32BIT_MSB_FIRST(lenbuf, cryptlen);
71     SHA256_Bytes(&sha, lenbuf, 4);
72     SHA256_Bytes(&sha, cryptdata, cryptlen);
73     SHA256_Final(&sha, digest);
74
75     sfree(cryptdata);
76
77     /*
78      * Finally, make printable.
79      */
80     for (i = 0; i < 32; i++) {
81         sprintf(retbuf + 2*i, "%02x", digest[i]);
82         /* the last of those will also write the trailing NUL */
83     }
84
85     return dupstr(retbuf);
86 }
87
88 static char *make_name(const char *prefix, const char *name)
89 {
90     char *username, *retname;
91
92     username = get_username();
93     retname = dupprintf("%s.%s.%s", prefix, username, name);
94     sfree(username);
95
96     return retname;
97 }
98
99 Socket new_named_pipe_client(const char *pipename, Plug plug);
100 Socket new_named_pipe_listener(const char *pipename, Plug plug);
101
102 int platform_ssh_share(const char *pi_name, Conf *conf,
103                        Plug downplug, Plug upplug, Socket *sock,
104                        char **logtext, char **ds_err, char **us_err,
105                        int can_upstream, int can_downstream)
106 {
107     char *name, *mutexname, *pipename;
108     HANDLE mutex;
109     Socket retsock;
110     PSECURITY_DESCRIPTOR psd;
111     PACL acl;
112     PSID networksid;
113
114     if (!got_crypt()) {
115         *logtext = dupprintf("Unable to load crypt32.dll");
116         return SHARE_NONE;
117     }
118
119     /*
120      * Transform the platform-independent version of the connection
121      * identifier into the obfuscated version we'll use for our
122      * Windows named pipe and mutex. A side effect of doing this is
123      * that it also eliminates any characters illegal in Windows pipe
124      * names.
125      */
126     name = obfuscate_name(pi_name);
127     if (!name) {
128         *logtext = dupprintf("Unable to call CryptProtectMemory: %s",
129                              win_strerror(GetLastError()));
130         return SHARE_NONE;
131     }
132
133     /*
134      * Make a mutex name out of the connection identifier, and lock it
135      * while we decide whether to be upstream or downstream.
136      */
137     {
138         SECURITY_ATTRIBUTES sa;
139
140         mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name);
141         if (!make_private_security_descriptor(MUTEX_ALL_ACCESS,
142                                               &psd, &networksid,
143                                               &acl, logtext)) {
144             sfree(mutexname);
145             return SHARE_NONE;
146         }
147
148         memset(&sa, 0, sizeof(sa));
149         sa.nLength = sizeof(sa);
150         sa.lpSecurityDescriptor = psd;
151         sa.bInheritHandle = FALSE;
152
153         mutex = CreateMutex(&sa, FALSE, mutexname);
154
155         if (!mutex) {
156             *logtext = dupprintf("CreateMutex(\"%s\") failed: %s",
157                                  mutexname, win_strerror(GetLastError()));
158             sfree(mutexname);
159             LocalFree(psd);
160             LocalFree(networksid);
161             LocalFree(acl);
162             return SHARE_NONE;
163         }
164
165         sfree(mutexname);
166         LocalFree(psd);
167         LocalFree(networksid);
168         LocalFree(acl);
169
170         WaitForSingleObject(mutex, INFINITE);
171     }
172
173     pipename = make_name(CONNSHARE_PIPE_PREFIX, name);
174
175     *logtext = NULL;
176
177     if (can_downstream) {
178         retsock = new_named_pipe_client(pipename, downplug);
179         if (sk_socket_error(retsock) == NULL) {
180             sfree(*logtext);
181             *logtext = pipename;
182             *sock = retsock;
183             sfree(name);
184             ReleaseMutex(mutex);
185             CloseHandle(mutex);
186             return SHARE_DOWNSTREAM;
187         }
188         sfree(*ds_err);
189         *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
190         sk_close(retsock);
191     }
192
193     if (can_upstream) {
194         retsock = new_named_pipe_listener(pipename, upplug);
195         if (sk_socket_error(retsock) == NULL) {
196             sfree(*logtext);
197             *logtext = pipename;
198             *sock = retsock;
199             sfree(name);
200             ReleaseMutex(mutex);
201             CloseHandle(mutex);
202             return SHARE_UPSTREAM;
203         }
204         sfree(*us_err);
205         *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
206         sk_close(retsock);
207     }
208
209     /* One of the above clauses ought to have happened. */
210     assert(*logtext || *ds_err || *us_err);
211
212     sfree(pipename);
213     sfree(name);
214     ReleaseMutex(mutex);
215     CloseHandle(mutex);
216     return SHARE_NONE;
217 }
218
219 void platform_ssh_share_cleanup(const char *name)
220 {
221 }
222
223 #else /* !defined NO_SECURITY */
224
225 #include "noshare.c"
226
227 #endif /* !defined NO_SECURITY */