1 /******************************************************************************
2   Copyright (c) 1992, 1995, 1996 Xerox Corporation.  All rights reserved.
3   Portions of this code were written by Stephen White, aka ghond.
4   Use and copying of this software and preparation of derivative works based
5   upon this software are permitted.  Any distribution of this software or
6   derivative works must comply with all applicable United States export
7   control laws.  This software is made available AS IS, and Xerox Corporation
8   makes no warranty about the software, its performance or its conformity to
9   any specification.  Any person obtaining a copy of this software is requested
10   to send their name and post office or electronic mail address to:
11     Pavel Curtis
12     Xerox PARC
13     3333 Coyote Hill Rd.
14     Palo Alto, CA 94304
15     Pavel@Xerox.Com
16  *****************************************************************************/
17 
18 /* Multi-user networking protocol implementation for local clients on SysV UNIX
19 
20  * The protocol for connection establishment works like this:
21  *
22  *      CLIENT                          SERVER
23  *                                      Create SERVER_FIFO (mode 622).
24  *                                      Open SERVER_FIFO for reading (O_NDELAY)
25  *                                      Wait for input on SERVER_FIFO
26  *                                      ...
27  *      Create C2S_FIFO (mode 644)
28  *        and S2C_FIFO (mode 622) in
29  *        some personal directory
30  *        (like $HOME).
31  *      Open S2C_FIFO for reading
32  *        (O_NDELAY).
33  *      Open SERVER_FIFO for writing
34  *        (no O_NDELAY), write
35  *        '\n' C2S_FIFO ' ' S2C_FIFO '\n'
36  *        in a single write() call, and
37  *        then close SERVER_FIFO.
38  *      Open C2S_FIFO for writing       Read C2S_FIFO and S2C_FIFO from
39  *        (*no* O_NDELAY).                SERVER_FIFO.
40  *                                      Open S2C_FIFO for writing (O_NDELAY)
41  *                                        and abort connection on error.
42  *                                      Open C2S_FIFO for reading (O_NDELAY)
43  *                                        and abort connection on error.
44  *      Unlink C2S_FIFO and S2C_FIFO
45  *        for privacy; their names
46  *        are no longer needed.
47  *                                      ...
48  *                                      Close SERVER_FIFO and unlink it.
49  *
50  * It is important that the client do the above actions in the given order;
51  * no other sequence (with the sole exception of the timing of the opening
52  * and closing of SERVER_FIFO) will accomplish a successful connection.
53  *
54  * The unlinking of the client's two FIFOs makes it impossible for someone
55  * else thereafter to either spoof input or divert output in the name of the
56  * client.  There is, of course, a brief window during which the names exist
57  * and a spoofer could intervene, but this appears to be unavoidable.
58  *
59  * The extra newline at the beginning of the client's connection request on
60  * SERVER_FIFO is there to make the request parsable even in the presence of
61  * random extra garbage already in the FIFO, thus thwarting a denial-of-service
62  * attack on the server.
63  *
64  * The server makes use of information about the client's FIFOs (i.e., their
65  * owner) to identify the connection for humans.  For example, the user name
66  * appears in the server's log and is available to wizards via the MOO
67  * function connection_name(player).  Caveat Emptor.
68  */
69 
70 #include <errno.h>		/* EMFILE */
71 #include "my-fcntl.h"		/* open(), O_RDONLY, O_WRONLY, NONBLOCK_FLAG */
72 #include <pwd.h>		/* struct passwd, getpwuid() */
73 #include "my-stat.h"		/* S_IFIFO, fstat(), mkfifo() */
74 #include "my-stdio.h"		/* remove() */
75 #include "my-stdlib.h"		/* exit() */
76 #include "my-unistd.h"		/* chmod(), close(), pipe(), read(), write() */
77 
78 #include "config.h"
79 #include "exceptions.h"
80 #include "list.h"
81 #include "log.h"
82 #include "net_multi.h"
83 #include "net_proto.h"
84 #include "storage.h"
85 #include "streams.h"
86 #include "utils.h"
87 
88 enum state {
89     RejectLine, GetC2S, GetS2C, Accepting
90 };
91 
92 typedef struct listener {
93     struct listener *next;
94     int fifo, pseudo_client;
95     const char *filename;
96     enum state state;
97     char c2s[1001], s2c[1001];
98     int ptr;			/* current index in c2s or s2c */
99 } listener;
100 
101 static listener *all_listeners = 0;
102 
103 const char *
proto_name(void)104 proto_name(void)
105 {
106     return "FIFO";
107 }
108 
109 const char *
proto_usage_string(void)110 proto_usage_string(void)
111 {
112     return "[server-connect-file]";
113 }
114 
115 int
proto_initialize(struct proto * proto,Var * desc,int argc,char ** argv)116 proto_initialize(struct proto *proto, Var * desc, int argc, char **argv)
117 {
118     const char *connect_file = DEFAULT_CONNECT_FILE;
119 
120     proto->pocket_size = 2;
121 #if POSIX_NONBLOCKING_WORKS
122     /* With POSIX-style nonblocking, we'll win */
123     proto->believe_eof = 1;
124 #else
125     proto->believe_eof = 0;
126 #endif
127     proto->eol_out_string = "\n";
128 
129     if (argc > 1)
130 	return 0;
131     else if (argc == 1) {
132 	connect_file = argv[0];
133     }
134     desc->type = TYPE_STR;
135     desc->v.str = str_dup(connect_file);
136     return 1;
137 }
138 
139 enum error
proto_make_listener(Var desc,int * fd,Var * canon,const char ** name)140 proto_make_listener(Var desc, int *fd, Var * canon, const char **name)
141 {
142     char buffer[1024];
143     const char *connect_file;
144     int fifo, pseudo_client;
145     listener *l;
146 
147     if (desc.type != TYPE_STR)
148 	return E_TYPE;
149 
150     connect_file = desc.v.str;
151     if (mkfifo(connect_file, 0600) < 0) {
152 	sprintf(buffer, "Creating listening FIFO `%s'", connect_file);
153 	log_perror(buffer);
154 	return E_QUOTA;
155     } else if ((fifo = open(connect_file, O_RDONLY | NONBLOCK_FLAG)) < 0) {
156 	log_perror("Opening listening FIFO");
157 	return E_QUOTA;
158     } else if ((pseudo_client = open(connect_file, O_WRONLY)) < 0) {
159 	log_perror("Opening pseudo-client");
160 	return E_QUOTA;
161     } else if (!network_set_nonblocking(fifo)) {
162 	log_perror("Setting listening FIFO non-blocking");
163 	return E_QUOTA;
164     }
165     l = mymalloc(sizeof(listener), M_NETWORK);
166     l->next = all_listeners;
167     all_listeners = l;
168     l->filename = str_dup(connect_file);
169     l->fifo = fifo;
170     l->pseudo_client = pseudo_client;
171     l->state = GetC2S;
172     l->ptr = 0;
173 
174     *fd = fifo;
175     *canon = var_ref(desc);
176     *name = l->filename;
177     return E_NONE;
178 }
179 
180 static listener *
find_listener(int fd)181 find_listener(int fd)
182 {
183     listener *l;
184 
185     for (l = all_listeners; l; l = l->next)
186 	if (l->fifo == fd)
187 	    return l;
188 
189     return 0;
190 }
191 
192 int
proto_listen(int fd)193 proto_listen(int fd)
194 {
195     listener *l = find_listener(fd);
196 
197     if (l) {
198 	if (chmod(l->filename, 0622) < 0) {
199 	    log_perror("Making listening FIFO writable");
200 	    return 0;
201 	}
202 	return 1;
203     }
204     errlog("Can't find FIFO in PROTO_LISTEN!");
205     return 0;
206 }
207 
208 enum proto_accept_error
proto_accept_connection(int listener_fd,int * read_fd,int * write_fd,const char ** name)209 proto_accept_connection(int listener_fd, int *read_fd, int *write_fd,
210 			const char **name)
211 {
212     /* There is input available on listener_fd; read up to 1K of it and try
213      * to parse a line like this from it:
214      *          <c2s-path-name> <space> <s2c-path-name> <newline>
215      * Because it's impossible in System V and can be difficult in POSIX to do
216      * otherwise, we assume that the maximum length of a path-name is 1000
217      * bytes.  In fact, because System V and POSIX only guarantee that 512
218      * bytes can be written atomically on a pipe or FIFO, and since the client
219      * has to get two path-names, two newlines, and a space atomically into the
220      * FIFO, it really ought not use names longer than about 250 bytes each.
221      * Of course, in practice, the names will likely never exceed 100 bytes...
222      */
223     listener *l = find_listener(listener_fd);
224     struct stat st1, st2;
225     struct passwd *pw;
226 
227     if (l->state != Accepting) {
228 	int got_one = 0;
229 
230 	while (!got_one) {
231 	    char c;
232 
233 	    if (read(l->fifo, &c, 1) != 1)
234 		break;
235 
236 	    switch (l->state) {
237 	    case RejectLine:
238 		if (c == '\n') {
239 		    l->state = GetC2S;
240 		    l->ptr = 0;
241 		}
242 		break;
243 
244 	    case GetC2S:
245 		if (c == ' ') {
246 		    l->c2s[l->ptr] = '\0';
247 		    l->state = GetS2C;
248 		    l->ptr = 0;
249 		} else if (c == '\n') {
250 		    if (l->ptr != 0)
251 			errlog("Missing FIFO name(s) on listening FIFO\n");
252 		    l->ptr = 0;
253 		} else if (l->ptr == 1000) {
254 		    errlog("Overlong line on server FIFO\n");
255 		    l->state = RejectLine;
256 		} else
257 		    l->c2s[l->ptr++] = c;
258 		break;
259 
260 	    case GetS2C:
261 		if (c == '\n') {
262 		    l->s2c[l->ptr] = '\0';
263 		    l->state = Accepting;
264 		    l->ptr = 0;
265 		    got_one = 1;
266 		} else if (c == ' ' || l->ptr == 1000) {
267 		    errlog("Overlong or malformed line on listening FIFO\n");
268 		    l->state = RejectLine;
269 		} else
270 		    l->s2c[l->ptr++] = c;
271 		break;
272 
273 	    default:
274 		panic("Can't happen in proto_accept_connection()");
275 	    }
276 	}
277 
278 	if (!got_one) {
279 	    errlog("Unproductive call to proto_accept_connection()\n");
280 	    return PA_OTHER;
281 	}
282     }
283     if ((*write_fd = open(l->s2c, O_WRONLY | NONBLOCK_FLAG)) < 0) {
284 	log_perror("Failed to open server->client FIFO");
285 	if (errno == EMFILE)
286 	    return PA_FULL;
287 	else {
288 	    l->state = GetC2S;
289 	    return PA_OTHER;
290 	}
291     }
292     if ((*read_fd = open(l->c2s, O_RDONLY | NONBLOCK_FLAG)) < 0) {
293 	log_perror("Failed to open server->client FIFO");
294 	close(*write_fd);
295 	if (errno == EMFILE)
296 	    return PA_FULL;
297 	else {
298 	    l->state = GetC2S;
299 	    return PA_OTHER;
300 	}
301     }
302     l->state = GetC2S;
303 
304     if (fstat(*read_fd, &st1) < 0 || fstat(*write_fd, &st2) < 0) {
305 	log_perror("Statting client FIFOs");
306 	return PA_OTHER;
307     }
308     if (st1.st_mode & S_IFMT != S_IFIFO
309 	|| st2.st_mode & S_IFMT != S_IFIFO
310 	|| st1.st_uid != st2.st_uid) {
311 	close(*read_fd);
312 	close(*write_fd);
313 	errlog("Bogus FIFO names: \"%s\" and \"%s\"\n", l->c2s, l->s2c);
314 	return PA_OTHER;
315     }
316     pw = getpwuid(st1.st_uid);
317     if (pw)
318 	*name = pw->pw_name;
319     else {
320 	static char buffer[20];
321 
322 	sprintf(buffer, "User #%d", (int) st1.st_uid);
323 	*name = buffer;
324     }
325 
326     return PA_OKAY;
327 }
328 
329 void
proto_close_connection(int read_fd,int write_fd)330 proto_close_connection(int read_fd, int write_fd)
331 {
332     write(write_fd, "\0", 1);	/* Tell client we're shutting down. */
333     close(read_fd);
334     close(write_fd);
335 }
336 
337 void
proto_close_listener(int fd)338 proto_close_listener(int fd)
339 {
340     listener *l, **ll;
341 
342     for (l = all_listeners, ll = &all_listeners; l; ll = &(l->next),
343 	 l = l->next)
344 	if (l->fifo == fd) {
345 	    remove(l->filename);
346 	    close(l->fifo);
347 	    close(l->pseudo_client);
348 
349 	    *ll = l->next;
350 	    free_str(l->filename);
351 	    myfree(l, M_NETWORK);
352 	    return;
353 	}
354     errlog("Can't find fd in PROTO_CLOSE_LISTENER!\n");
355 }
356 
357 char rcsid_net_sysv_lcl[] = "$Id: net_sysv_lcl.c,v 1.2 1997/03/03 04:19:08 nop Exp $";
358 
359 /* $Log: net_sysv_lcl.c,v $
360 /* Revision 1.2  1997/03/03 04:19:08  nop
361 /* GNU Indent normalization
362 /*
363  * Revision 1.1.1.1  1997/03/03 03:45:02  nop
364  * LambdaMOO 1.8.0p5
365  *
366  * Revision 2.4  1996/03/10  01:13:11  pavel
367  * Moved definition of DEFAULT_CONNECT_FILE to options.h.  Release 1.8.0.
368  *
369  * Revision 2.3  1996/02/08  06:34:28  pavel
370  * Renamed err/logf() to errlog/oklog().  Updated copyright notice for 1996.
371  * Release 1.8.0beta1.
372  *
373  * Revision 2.2  1995/12/30  23:59:02  pavel
374  * Added support for the new multiple-listening-points interface.
375  * Release 1.8.0alpha4.
376  *
377  * Revision 2.1  1995/12/28  00:34:29  pavel
378  * Removed old support for protocol-specific input EOL conventions.
379  * Release 1.8.0alpha3.
380  *
381  * Revision 2.0  1995/11/30  04:47:19  pavel
382  * New baseline version, corresponding to release 1.8.0alpha1.
383  *
384  * Revision 1.10  1993/12/13  18:08:41  pavel
385  * -- Updated to provide new proto_listen() return value.
386  * -- Added the file name to the `creating server FIFO' message.
387  *
388  * Revision 1.9  1992/10/23  23:03:47  pavel
389  * Added copyright notice.
390  *
391  * Revision 1.8  1992/10/23  22:01:46  pavel
392  * -- #includes "my-stat.h" instead of <sys/stat.h>.
393  * -- Moved macro definition of mkfifo() to my-stat.h.
394  * -- Changed to conditionalize on POSIX_NONBLOCKING_WORKS instead of
395  *    defined(O_NONBLOCK).
396  * -- Now opens its own `write'-side port on the server FIFO, so that the
397  *    server FIFO won't always be input-ready (a read() would always
398  *    immediately return EOF if no writers existed).
399  * -- No longer assumes that fstat() shows how much data is in the FIFO.
400  *
401  * Revision 1.7  1992/10/21  03:02:35  pavel
402  * Converted to use new automatic configuration system.
403  *
404  * Revision 1.6  1992/10/17  20:46:04  pavel
405  * Changed to use NONBLOCK_FLAG instead of O_NDELAY to allow of using
406  * POSIX-style non-blocking on systems where it is available.
407  *
408  * Revision 1.5  1992/10/06  01:35:35  pavel
409  * Moved non-blocking code to net_multi.c, replacing it with code to set
410  * proto->believe_eof appropriately.
411  *
412  * Revision 1.4  1992/10/01  16:46:56  pavel
413  * Fixed state machine in proto_accept_connection so that a PA_FULL condition
414  * leaves things in such a state that the next call will try that same
415  * connection again.  Also added more logging of unsuccessful connection
416  * attempts.
417  *
418  * Revision 1.3  1992/09/27  19:34:14  pavel
419  * Added RCS Id and Log fields.
420  */
421