source: filezilla/trunk/fuentes/src/putty/unix/uxshare.c @ 130

Last change on this file since 130 was 130, checked in by jrpelegrina, 4 years ago

First release to xenial

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