]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxshare.c
Change the naming policy for connection-sharing Unix sockets.
[PuTTY.git] / unix / uxshare.c
1 /*
2  * Unix implementation of SSH connection-sharing IPC setup.
3  */
4
5 #include <stdio.h>
6 #include <assert.h>
7 #include <errno.h>
8 #include <limits.h>
9
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <sys/file.h>
15
16 #define DEFINE_PLUG_METHOD_MACROS
17 #include "tree234.h"
18 #include "putty.h"
19 #include "network.h"
20 #include "proxy.h"
21 #include "ssh.h"
22
23 #define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
24 #define SALT_FILENAME "salt"
25 #define SALT_SIZE 64
26
27 /*
28  * Functions provided by uxnet.c to help connection sharing.
29  */
30 SockAddr unix_sock_addr(const char *path);
31 Socket new_unix_listener(SockAddr listenaddr, Plug plug);
32
33 static char *make_parentdir_name(void)
34 {
35     char *username, *parent;
36
37     username = get_username();
38     parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
39     sfree(username);
40     assert(*parent == '/');
41
42     return parent;
43 }
44
45 static char *make_dir_and_check_ours(const char *dirname)
46 {
47     struct stat st;
48
49     /*
50      * Create the directory. We might have created it before, so
51      * EEXIST is an OK error; but anything else is doom.
52      */
53     if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
54         return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
55
56     /*
57      * Now check that that directory is _owned by us_ and not writable
58      * by anybody else. This protects us against somebody else
59      * previously having created the directory in a way that's
60      * writable to us, and thus manipulating us into creating the
61      * actual socket in a directory they can see so that they can
62      * connect to it and use our authenticated SSH sessions.
63      */
64     if (stat(dirname, &st) < 0)
65         return dupprintf("%s: stat: %s", dirname, strerror(errno));
66     if (st.st_uid != getuid())
67         return dupprintf("%s: directory owned by uid %d, not by us",
68                          dirname, st.st_uid);
69     if ((st.st_mode & 077) != 0)
70         return dupprintf("%s: directory has overgenerous permissions %03o"
71                          " (expected 700)", dirname, st.st_mode & 0777);
72
73     return NULL;
74 }
75
76 static char *make_dirname(const char *pi_name, char **logtext)
77 {
78     char *name, *parentdirname, *dirname, *err;
79
80     /*
81      * First, create the top-level directory for all shared PuTTY
82      * connections owned by this user.
83      */
84     parentdirname = make_parentdir_name();
85     if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
86         *logtext = err;
87         sfree(parentdirname);
88         return NULL;
89     }
90
91     /*
92      * Transform the platform-independent version of the connection
93      * identifier into the name we'll actually use for the directory
94      * containing the Unix socket.
95      *
96      * We do this by hashing the identifier with some user-specific
97      * secret information, to avoid the privacy leak of having
98      * "user@host" strings show up in 'netstat -x'. (Irritatingly, the
99      * full pathname of a Unix-domain socket _does_ show up in the
100      * 'netstat -x' output, at least on Linux, even if that socket is
101      * in a directory not readable to the user running netstat. You'd
102      * think putting things inside an 0700 directory would hide their
103      * names from other users, but no.)
104      *
105      * The secret information we use to salt the hash lives in a file
106      * inside the top-level directory we just created, so we must
107      * first create that file (with some fresh random data in it) if
108      * it's not already been done by a previous PuTTY.
109      */
110     {
111         unsigned char saltbuf[SALT_SIZE];
112         char *saltname;
113         int saltfd, i, ret;
114
115         saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME);
116         saltfd = open(saltname, O_RDONLY);
117         if (saltfd < 0) {
118             char *tmpname;
119             int pid;
120
121             if (errno != ENOENT) {
122                 *logtext = dupprintf("%s: open: %s", saltname,
123                                      strerror(errno));
124                 sfree(saltname);
125                 sfree(parentdirname);
126                 return NULL;
127             }
128
129             /*
130              * The salt file doesn't already exist, so try to create
131              * it. Another process may be attempting the same thing
132              * simultaneously, so we must do this carefully: we write
133              * a salt file under a different name, then hard-link it
134              * into place, which guarantees that we won't change the
135              * contents of an existing salt file.
136              */
137             pid = getpid();
138             for (i = 0;; i++) {
139                 tmpname = dupprintf("%s/%s.tmp.%d.%d",
140                                     parentdirname, SALT_FILENAME, pid, i);
141                 saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400);
142                 if (saltfd >= 0)
143                     break;
144                 if (errno != EEXIST) {
145                     *logtext = dupprintf("%s: open: %s", tmpname,
146                                          strerror(errno));
147                     sfree(tmpname);
148                     sfree(saltname);
149                     sfree(parentdirname);
150                     return NULL;
151                 }
152                 sfree(tmpname);        /* go round and try again with i+1 */
153             }
154             /*
155              * Invent some random data.
156              */
157             for (i = 0; i < SALT_SIZE; i++) {
158                 saltbuf[i] = random_byte();
159             }
160             ret = write(saltfd, saltbuf, SALT_SIZE);
161             /* POSIX atomicity guarantee: because we wrote less than
162              * PIPE_BUF bytes, the write either completed in full or
163              * failed. */
164             assert(SALT_SIZE < PIPE_BUF);
165             assert(ret < 0 || ret == SALT_SIZE);
166             if (ret < 0) {
167                 close(saltfd);
168                 *logtext = dupprintf("%s: write: %s", tmpname,
169                                      strerror(errno));
170                 sfree(tmpname);
171                 sfree(saltname);
172                 sfree(parentdirname);
173                 return NULL;
174             }
175             if (close(saltfd) < 0) {
176                 *logtext = dupprintf("%s: close: %s", tmpname,
177                                      strerror(errno));
178                 sfree(tmpname);
179                 sfree(saltname);
180                 sfree(parentdirname);
181                 return NULL;
182             }
183
184             /*
185              * Now attempt to hard-link our temp file into place. We
186              * tolerate EEXIST as an outcome, because that just means
187              * another PuTTY got their attempt in before we did (and
188              * we only care that there is a valid salt file we can
189              * agree on, no matter who created it).
190              */
191             if (link(tmpname, saltname) < 0 && errno != EEXIST) {
192                 *logtext = dupprintf("%s: link: %s", saltname,
193                                      strerror(errno));
194                 sfree(tmpname);
195                 sfree(saltname);
196                 sfree(parentdirname);
197                 return NULL;
198             }
199
200             /*
201              * Whether that succeeded or not, get rid of our temp file.
202              */
203             if (unlink(tmpname) < 0) {
204                 *logtext = dupprintf("%s: unlink: %s", tmpname,
205                                      strerror(errno));
206                 sfree(tmpname);
207                 sfree(saltname);
208                 sfree(parentdirname);
209                 return NULL;
210             }
211
212             /*
213              * And now we've arranged for there to be a salt file, so
214              * we can try to open it for reading again and this time
215              * expect it to work.
216              */
217             sfree(tmpname);
218
219             saltfd = open(saltname, O_RDONLY);
220             if (saltfd < 0) {
221                 *logtext = dupprintf("%s: open: %s", saltname,
222                                      strerror(errno));
223                 sfree(saltname);
224                 sfree(parentdirname);
225                 return NULL;
226             }
227         }
228
229         for (i = 0; i < SALT_SIZE; i++) {
230             ret = read(saltfd, saltbuf, SALT_SIZE);
231             if (ret <= 0) {
232                 close(saltfd);
233                 *logtext = dupprintf("%s: read: %s", saltname,
234                                      ret == 0 ? "unexpected EOF" :
235                                      strerror(errno));
236                 sfree(saltname);
237                 sfree(parentdirname);
238                 return NULL;
239             }
240             assert(0 < ret && ret <= SALT_SIZE - i);
241             i += ret;
242         }
243
244         close(saltfd);
245         sfree(saltname);
246
247         /*
248          * Now we've got our salt, hash it with the connection
249          * identifier to produce our actual socket name.
250          */
251         {
252             SHA256_State sha;
253             unsigned len;
254             unsigned char lenbuf[4];
255             unsigned char digest[32];
256             char retbuf[65];
257
258             SHA256_Init(&sha);
259             PUT_32BIT(lenbuf, SALT_SIZE);
260             SHA256_Bytes(&sha, lenbuf, 4);
261             SHA256_Bytes(&sha, saltbuf, SALT_SIZE);
262             len = strlen(pi_name);
263             PUT_32BIT(lenbuf, len);
264             SHA256_Bytes(&sha, lenbuf, 4);
265             SHA256_Bytes(&sha, pi_name, len);
266             SHA256_Final(&sha, digest);
267
268             /*
269              * And make it printable.
270              */
271             for (i = 0; i < 32; i++) {
272                 sprintf(retbuf + 2*i, "%02x", digest[i]);
273                 /* the last of those will also write the trailing NUL */
274             }
275
276             name = dupstr(retbuf);
277         }
278
279         smemclr(saltbuf, sizeof(saltbuf));
280     }
281
282     dirname = dupprintf("%s/%s", parentdirname, name);
283     sfree(parentdirname);
284     sfree(name);
285
286     return dirname;
287 }
288
289 int platform_ssh_share(const char *pi_name, Conf *conf,
290                        Plug downplug, Plug upplug, Socket *sock,
291                        char **logtext, char **ds_err, char **us_err,
292                        int can_upstream, int can_downstream)
293 {
294     char *dirname, *lockname, *sockname, *err;
295     int lockfd;
296     Socket retsock;
297
298     /*
299      * Sort out what we're going to call the directory in which we
300      * keep the socket. This has the side effect of potentially
301      * creating its top-level containing dir and/or the salt file
302      * within that, if they don't already exist.
303      */
304     dirname = make_dirname(pi_name, logtext);
305     if (!dirname) {
306         return SHARE_NONE;
307     }
308
309     /*
310      * Now make sure the subdirectory exists.
311      */
312     if ((err = make_dir_and_check_ours(dirname)) != NULL) {
313         *logtext = err;
314         sfree(dirname);
315         return SHARE_NONE;
316     }
317
318     /*
319      * Acquire a lock on a file in that directory.
320      */
321     lockname = dupcat(dirname, "/lock", (char *)NULL);
322     lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
323     if (lockfd < 0) {
324         *logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
325         sfree(dirname);
326         sfree(lockname);
327         return SHARE_NONE;
328     }
329     if (flock(lockfd, LOCK_EX) < 0) {
330         *logtext = dupprintf("%s: flock(LOCK_EX): %s",
331                              lockname, strerror(errno));
332         sfree(dirname);
333         sfree(lockname);
334         close(lockfd);
335         return SHARE_NONE;
336     }
337
338     sockname = dupprintf("%s/socket", dirname);
339
340     *logtext = NULL;
341
342     if (can_downstream) {
343         retsock = new_connection(unix_sock_addr(sockname),
344                                  "", 0, 0, 1, 0, 0, downplug, conf);
345         if (sk_socket_error(retsock) == NULL) {
346             sfree(*logtext);
347             *logtext = sockname;
348             *sock = retsock;
349             sfree(dirname);
350             sfree(lockname);
351             close(lockfd);
352             return SHARE_DOWNSTREAM;
353         }
354         sfree(*ds_err);
355         *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
356         sk_close(retsock);
357     }
358
359     if (can_upstream) {
360         retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
361         if (sk_socket_error(retsock) == NULL) {
362             sfree(*logtext);
363             *logtext = sockname;
364             *sock = retsock;
365             sfree(dirname);
366             sfree(lockname);
367             close(lockfd);
368             return SHARE_UPSTREAM;
369         }
370         sfree(*us_err);
371         *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
372         sk_close(retsock);
373     }
374
375     /* One of the above clauses ought to have happened. */
376     assert(*logtext || *ds_err || *us_err);
377
378     sfree(dirname);
379     sfree(lockname);
380     sfree(sockname);
381     close(lockfd);
382     return SHARE_NONE;
383 }
384
385 void platform_ssh_share_cleanup(const char *name)
386 {
387     char *dirname, *filename, *logtext;
388
389     dirname = make_dirname(name, &logtext);
390     if (!dirname) {
391         sfree(logtext);                /* we can't do much with this */
392         return;
393     }
394
395     filename = dupcat(dirname, "/socket", (char *)NULL);
396     remove(filename);
397     sfree(filename);
398
399     filename = dupcat(dirname, "/lock", (char *)NULL);
400     remove(filename);
401     sfree(filename);
402
403     rmdir(dirname);
404
405     /*
406      * We deliberately _don't_ clean up the parent directory
407      * /tmp/putty-connshare.<username>, because if we leave it around
408      * then it reduces the ability for other users to be a nuisance by
409      * putting their own directory in the way of it. Also, the salt
410      * file in it can be reused.
411      */
412
413     sfree(dirname);
414 }