]> asedeno.scripts.mit.edu Git - PuTTY_svn.git/blob - unix/uxshare.c
Implement connection sharing between instances of PuTTY.
[PuTTY_svn.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
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <sys/file.h>
14
15 #define DEFINE_PLUG_METHOD_MACROS
16 #include "tree234.h"
17 #include "putty.h"
18 #include "network.h"
19 #include "proxy.h"
20 #include "ssh.h"
21
22 #define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
23
24 /*
25  * Functions provided by uxnet.c to help connection sharing.
26  */
27 SockAddr unix_sock_addr(const char *path);
28 Socket new_unix_listener(SockAddr listenaddr, Plug plug);
29
30 static char *make_dirname(const char *name, char **parent_out)
31 {
32     char *username, *dirname, *parent;
33
34     username = get_username();
35     parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
36     sfree(username);
37     assert(*parent == '/');
38
39     dirname = dupprintf("%s/%s", parent, name);
40
41     if (parent_out)
42         *parent_out = parent;
43     else
44         sfree(parent);
45
46     return dirname;
47 }
48
49 static char *make_dir_and_check_ours(const char *dirname)
50 {
51     struct stat st;
52
53     /*
54      * Create the directory. We might have created it before, so
55      * EEXIST is an OK error; but anything else is doom.
56      */
57     if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
58         return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
59
60     /*
61      * Now check that that directory is _owned by us_ and not writable
62      * by anybody else. This protects us against somebody else
63      * previously having created the directory in a way that's
64      * writable to us, and thus manipulating us into creating the
65      * actual socket in a directory they can see so that they can
66      * connect to it and use our authenticated SSH sessions.
67      */
68     if (stat(dirname, &st) < 0)
69         return dupprintf("%s: stat: %s", dirname, strerror(errno));
70     if (st.st_uid != getuid())
71         return dupprintf("%s: directory owned by uid %d, not by us",
72                          dirname, st.st_uid);
73     if ((st.st_mode & 077) != 0)
74         return dupprintf("%s: directory has overgenerous permissions %03o"
75                          " (expected 700)", dirname, st.st_mode & 0777);
76
77     return NULL;
78 }
79
80 int platform_ssh_share(const char *pi_name, Conf *conf,
81                        Plug downplug, Plug upplug, Socket *sock,
82                        char **logtext, char **ds_err, char **us_err,
83                        int can_upstream, int can_downstream)
84 {
85     char *name, *parentdirname, *dirname, *lockname, *sockname, *err;
86     int lockfd;
87     Socket retsock;
88
89     /*
90      * Transform the platform-independent version of the connection
91      * identifier into something valid for a Unix socket, by escaping
92      * slashes (and, while we're here, any control characters).
93      */
94     {
95         const char *p;
96         char *q;
97
98         name = snewn(1+3*strlen(pi_name), char);
99
100         for (p = pi_name, q = name; *p; p++) {
101             if (*p == '/' || *p == '%' ||
102                 (unsigned char)*p < 0x20 || *p == 0x7f) {
103                 q += sprintf(q, "%%%02x", (unsigned char)*p);
104             } else {
105                 *q++ = *p;
106             }
107         }
108         *q = '\0';
109     }
110
111     /*
112      * First, make sure our subdirectory exists. We must create two
113      * levels of directory - the one for this particular connection,
114      * and the containing one for our username.
115      */
116     dirname = make_dirname(name, &parentdirname);
117     if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
118         *logtext = err;
119         sfree(dirname);
120         sfree(parentdirname);
121         sfree(name);
122         return SHARE_NONE;
123     }
124     sfree(parentdirname);
125     if ((err = make_dir_and_check_ours(dirname)) != NULL) {
126         *logtext = err;
127         sfree(dirname);
128         sfree(name);
129         return SHARE_NONE;
130     }
131
132     /*
133      * Acquire a lock on a file in that directory.
134      */
135     lockname = dupcat(dirname, "/lock", (char *)NULL);
136     lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
137     if (lockfd < 0) {
138         *logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
139         sfree(dirname);
140         sfree(lockname);
141         sfree(name);
142         return SHARE_NONE;
143     }
144     if (flock(lockfd, LOCK_EX) < 0) {
145         *logtext = dupprintf("%s: flock(LOCK_EX): %s",
146                              lockname, strerror(errno));
147         sfree(dirname);
148         sfree(lockname);
149         close(lockfd);
150         sfree(name);
151         return SHARE_NONE;
152     }
153
154     sockname = dupprintf("%s/socket", dirname);
155
156     *logtext = NULL;
157
158     if (can_downstream) {
159         retsock = new_connection(unix_sock_addr(sockname),
160                                  "", 0, 0, 1, 0, 0, downplug, conf);
161         if (sk_socket_error(retsock) == NULL) {
162             sfree(*logtext);
163             *logtext = sockname;
164             *sock = retsock;
165             sfree(dirname);
166             sfree(lockname);
167             close(lockfd);
168             sfree(name);
169             return SHARE_DOWNSTREAM;
170         }
171         sfree(*ds_err);
172         *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
173         sk_close(retsock);
174     }
175
176     if (can_upstream) {
177         retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
178         if (sk_socket_error(retsock) == NULL) {
179             sfree(*logtext);
180             *logtext = sockname;
181             *sock = retsock;
182             sfree(dirname);
183             sfree(lockname);
184             close(lockfd);
185             sfree(name);
186             return SHARE_UPSTREAM;
187         }
188         sfree(*us_err);
189         *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
190         sk_close(retsock);
191     }
192
193     /* One of the above clauses ought to have happened. */
194     assert(*logtext || *ds_err || *us_err);
195
196     sfree(dirname);
197     sfree(lockname);
198     sfree(sockname);
199     close(lockfd);
200     sfree(name);
201     return SHARE_NONE;
202 }
203
204 void platform_ssh_share_cleanup(const char *name)
205 {
206     char *dirname, *filename;
207
208     dirname = make_dirname(name, NULL);
209
210     filename = dupcat(dirname, "/socket", (char *)NULL);
211     remove(filename);
212     sfree(filename);
213
214     filename = dupcat(dirname, "/lock", (char *)NULL);
215     remove(filename);
216     sfree(filename);
217
218     rmdir(dirname);
219
220     /*
221      * We deliberately _don't_ clean up the parent directory
222      * /tmp/putty-connshare.<username>, because if we leave it around
223      * then it reduces the ability for other users to be a nuisance by
224      * putting their own directory in the way of it.
225      */
226
227     sfree(dirname);
228 }