1156230Smux/*-
2156230Smux * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3156230Smux * All rights reserved.
4156230Smux *
5156230Smux * Redistribution and use in source and binary forms, with or without
6156230Smux * modification, are permitted provided that the following conditions
7156230Smux * are met:
8156230Smux * 1. Redistributions of source code must retain the above copyright
9156230Smux *    notice, this list of conditions and the following disclaimer.
10156230Smux * 2. Redistributions in binary form must reproduce the above copyright
11156230Smux *    notice, this list of conditions and the following disclaimer in the
12156230Smux *    documentation and/or other materials provided with the distribution.
13156230Smux *
14156230Smux * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15156230Smux * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16156230Smux * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17156230Smux * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18156230Smux * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19156230Smux * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20156230Smux * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21156230Smux * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22156230Smux * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23156230Smux * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24156230Smux * SUCH DAMAGE.
25156230Smux *
26156230Smux * $FreeBSD$
27156230Smux */
28156230Smux
29156230Smux#include <sys/param.h>
30156230Smux#include <sys/select.h>
31156230Smux#include <sys/socket.h>
32156230Smux#include <sys/types.h>
33156230Smux#include <sys/stat.h>
34156230Smux
35156230Smux#include <assert.h>
36156230Smux#include <err.h>
37156230Smux#include <errno.h>
38229184Sdim#include <inttypes.h>
39156230Smux#include <netdb.h>
40156230Smux#include <pthread.h>
41156230Smux#include <signal.h>
42156230Smux#include <stdarg.h>
43156230Smux#include <stddef.h>
44156230Smux#include <stdio.h>
45156230Smux#include <stdlib.h>
46156230Smux#include <string.h>
47156230Smux#include <unistd.h>
48156230Smux
49203368Slulf#include "auth.h"
50156230Smux#include "config.h"
51156230Smux#include "detailer.h"
52156230Smux#include "fattr.h"
53156230Smux#include "fixups.h"
54156230Smux#include "globtree.h"
55156230Smux#include "keyword.h"
56156230Smux#include "lister.h"
57156230Smux#include "misc.h"
58156230Smux#include "mux.h"
59156230Smux#include "proto.h"
60156230Smux#include "queue.h"
61156230Smux#include "stream.h"
62156230Smux#include "threads.h"
63156230Smux#include "updater.h"
64156230Smux
65156230Smuxstruct killer {
66156230Smux	pthread_t thread;
67156230Smux	sigset_t sigset;
68156230Smux	struct mux *mux;
69156230Smux	int killedby;
70156230Smux};
71156230Smux
72156230Smuxstatic void		 killer_start(struct killer *, struct mux *);
73156230Smuxstatic void		*killer_run(void *);
74156230Smuxstatic void		 killer_stop(struct killer *);
75156230Smux
76156230Smuxstatic int		 proto_waitconnect(int);
77156230Smuxstatic int		 proto_greet(struct config *);
78156230Smuxstatic int		 proto_negproto(struct config *);
79156230Smuxstatic int		 proto_fileattr(struct config *);
80156230Smuxstatic int		 proto_xchgcoll(struct config *);
81156230Smuxstatic struct mux	*proto_mux(struct config *);
82156230Smux
83156230Smuxstatic int		 proto_escape(struct stream *, const char *);
84156230Smuxstatic void		 proto_unescape(char *);
85156230Smux
86156230Smuxstatic int
87156230Smuxproto_waitconnect(int s)
88156230Smux{
89156230Smux	fd_set readfd;
90156230Smux	socklen_t len;
91156230Smux	int error, rv, soerror;
92156230Smux
93156230Smux	FD_ZERO(&readfd);
94156230Smux	FD_SET(s, &readfd);
95156230Smux
96156230Smux	do {
97156230Smux		rv = select(s + 1, &readfd, NULL, NULL, NULL);
98156230Smux	} while (rv == -1 && errno == EINTR);
99156230Smux	if (rv == -1)
100156230Smux		return (-1);
101156230Smux	/* Check that the connection was really successful. */
102156230Smux	len = sizeof(soerror);
103156230Smux	error = getsockopt(s, SOL_SOCKET, SO_ERROR, &soerror, &len);
104156230Smux	if (error) {
105156230Smux		/* We have no choice but faking an error here. */
106156230Smux		errno = ECONNREFUSED;
107156230Smux		return (-1);
108156230Smux	}
109156230Smux	if (soerror) {
110156230Smux		errno = soerror;
111156230Smux		return (-1);
112156230Smux	}
113156230Smux	return (0);
114156230Smux}
115156230Smux
116156230Smux/* Connect to the CVSup server. */
117156230Smuxint
118156230Smuxproto_connect(struct config *config, int family, uint16_t port)
119156230Smux{
120156230Smux	char addrbuf[NI_MAXHOST];
121156230Smux	/* Enough to hold sizeof("cvsup") or any port number. */
122156230Smux	char servname[8];
123156230Smux	struct addrinfo *res, *ai, hints;
124156230Smux	int error, opt, s;
125156230Smux
126156230Smux	s = -1;
127156230Smux	if (port != 0)
128156230Smux		snprintf(servname, sizeof(servname), "%d", port);
129156230Smux	else {
130156230Smux		strncpy(servname, "cvsup", sizeof(servname) - 1);
131156230Smux		servname[sizeof(servname) - 1] = '\0';
132156230Smux	}
133156230Smux	memset(&hints, 0, sizeof(hints));
134156230Smux	hints.ai_family = family;
135156230Smux	hints.ai_socktype = SOCK_STREAM;
136156230Smux	error = getaddrinfo(config->host, servname, &hints, &res);
137156230Smux	/*
138156230Smux	 * Try with the hardcoded port number for OSes that don't
139156230Smux	 * have cvsup defined in the /etc/services file.
140156230Smux	 */
141156230Smux	if (error == EAI_SERVICE) {
142156230Smux		strncpy(servname, "5999", sizeof(servname) - 1);
143156230Smux		servname[sizeof(servname) - 1] = '\0';
144156230Smux		error = getaddrinfo(config->host, servname, &hints, &res);
145156230Smux	}
146156230Smux	if (error) {
147156230Smux		lprintf(0, "Name lookup failure for \"%s\": %s\n", config->host,
148156230Smux		    gai_strerror(error));
149156230Smux		return (STATUS_TRANSIENTFAILURE);
150156230Smux	}
151156230Smux	for (ai = res; ai != NULL; ai = ai->ai_next) {
152156230Smux		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
153156230Smux		if (s != -1) {
154156230Smux			error = 0;
155156230Smux			if (config->laddr != NULL) {
156156230Smux				opt = 1;
157156230Smux				(void)setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
158156230Smux				    &opt, sizeof(opt));
159156230Smux				error = bind(s, config->laddr,
160156230Smux				    config->laddrlen);
161156230Smux			}
162156230Smux			if (!error) {
163156230Smux				error = connect(s, ai->ai_addr, ai->ai_addrlen);
164156230Smux				if (error && errno == EINTR)
165156230Smux					error = proto_waitconnect(s);
166156230Smux			}
167156230Smux			if (error)
168156230Smux				close(s);
169156230Smux		}
170156230Smux		(void)getnameinfo(ai->ai_addr, ai->ai_addrlen, addrbuf,
171156230Smux		    sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
172156230Smux		if (s == -1 || error) {
173156230Smux			lprintf(0, "Cannot connect to %s: %s\n", addrbuf,
174156230Smux			    strerror(errno));
175156230Smux			continue;
176156230Smux		}
177156230Smux		lprintf(1, "Connected to %s\n", addrbuf);
178156230Smux		freeaddrinfo(res);
179156230Smux		config->socket = s;
180156230Smux		return (STATUS_SUCCESS);
181156230Smux	}
182156230Smux	freeaddrinfo(res);
183156230Smux	return (STATUS_TRANSIENTFAILURE);
184156230Smux}
185156230Smux
186156230Smux/* Greet the server. */
187156230Smuxstatic int
188156230Smuxproto_greet(struct config *config)
189156230Smux{
190156230Smux	char *line, *cmd, *msg, *swver;
191156230Smux	struct stream *s;
192156230Smux
193156230Smux	s = config->server;
194156230Smux	line = stream_getln(s, NULL);
195156230Smux	cmd = proto_get_ascii(&line);
196156230Smux	if (cmd == NULL)
197156230Smux		goto bad;
198156230Smux	if (strcmp(cmd, "OK") == 0) {
199156230Smux		(void)proto_get_ascii(&line);	/* major number */
200156230Smux		(void)proto_get_ascii(&line);	/* minor number */
201156230Smux		swver = proto_get_ascii(&line);
202156230Smux	} else if (strcmp(cmd, "!") == 0) {
203156230Smux		msg = proto_get_rest(&line);
204156230Smux		if (msg == NULL)
205156230Smux			goto bad;
206156230Smux		lprintf(-1, "Rejected by server: %s\n", msg);
207156230Smux		return (STATUS_TRANSIENTFAILURE);
208156230Smux	} else
209156230Smux		goto bad;
210156230Smux	lprintf(2, "Server software version: %s\n",
211156230Smux	    swver != NULL ? swver : ".");
212156230Smux	return (STATUS_SUCCESS);
213156230Smuxbad:
214156230Smux	lprintf(-1, "Invalid greeting from server\n");
215156230Smux	return (STATUS_FAILURE);
216156230Smux}
217156230Smux
218156230Smux/* Negotiate protocol version with the server. */
219156230Smuxstatic int
220156230Smuxproto_negproto(struct config *config)
221156230Smux{
222156230Smux	struct stream *s;
223156230Smux	char *cmd, *line, *msg;
224156230Smux	int error, maj, min;
225156230Smux
226156230Smux	s = config->server;
227156230Smux	proto_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER);
228156230Smux	stream_flush(s);
229156230Smux	line = stream_getln(s, NULL);
230156230Smux	cmd = proto_get_ascii(&line);
231156701Smux	if (cmd == NULL || line == NULL)
232156230Smux		goto bad;
233156230Smux	if (strcmp(cmd, "!") == 0) {
234156230Smux		msg = proto_get_rest(&line);
235156230Smux		lprintf(-1, "Protocol negotiation failed: %s\n", msg);
236156230Smux		return (1);
237156230Smux	} else if (strcmp(cmd, "PROTO") != 0)
238156230Smux		goto bad;
239156230Smux	error = proto_get_int(&line, &maj, 10);
240156230Smux	if (!error)
241156230Smux		error = proto_get_int(&line, &min, 10);
242156230Smux	if (error)
243156230Smux		goto bad;
244156230Smux	if (maj != PROTO_MAJ || min != PROTO_MIN) {
245156230Smux		lprintf(-1, "Server protocol version %d.%d not supported "
246156230Smux		    "by client\n", maj, min);
247156230Smux		return (STATUS_FAILURE);
248156230Smux	}
249156230Smux	return (STATUS_SUCCESS);
250156230Smuxbad:
251156230Smux	lprintf(-1, "Invalid PROTO command from server\n");
252156230Smux	return (STATUS_FAILURE);
253156230Smux}
254156230Smux
255156230Smux/*
256156230Smux * File attribute support negotiation.
257156230Smux */
258156230Smuxstatic int
259156230Smuxproto_fileattr(struct config *config)
260156230Smux{
261156230Smux	fattr_support_t support;
262156230Smux	struct stream *s;
263156230Smux	char *line, *cmd;
264156230Smux	int error, i, n, attr;
265156230Smux
266156230Smux	s = config->server;
267156230Smux	lprintf(2, "Negotiating file attribute support\n");
268156230Smux	proto_printf(s, "ATTR %d\n", FT_NUMBER);
269156230Smux	for (i = 0; i < FT_NUMBER; i++)
270156230Smux		proto_printf(s, "%x\n", fattr_supported(i));
271156230Smux	proto_printf(s, ".\n");
272156230Smux	stream_flush(s);
273156230Smux	line = stream_getln(s, NULL);
274156230Smux	if (line == NULL)
275156230Smux		goto bad;
276156230Smux	cmd = proto_get_ascii(&line);
277156230Smux	error = proto_get_int(&line, &n, 10);
278156230Smux	if (error || line != NULL || strcmp(cmd, "ATTR") != 0 || n > FT_NUMBER)
279156230Smux		goto bad;
280156230Smux	for (i = 0; i < n; i++) {
281156230Smux		line = stream_getln(s, NULL);
282156230Smux		if (line == NULL)
283156230Smux			goto bad;
284156230Smux		error = proto_get_int(&line, &attr, 16);
285156230Smux		if (error)
286156230Smux			goto bad;
287156230Smux		support[i] = fattr_supported(i) & attr;
288156230Smux	}
289156230Smux	for (i = n; i < FT_NUMBER; i++)
290156230Smux		support[i] = 0;
291156230Smux	line = stream_getln(s, NULL);
292156230Smux	if (line == NULL || strcmp(line, ".") != 0)
293156230Smux		goto bad;
294156230Smux	memcpy(config->fasupport, support, sizeof(config->fasupport));
295156230Smux	return (STATUS_SUCCESS);
296156230Smuxbad:
297156230Smux	lprintf(-1, "Protocol error negotiating attribute support\n");
298156230Smux	return (STATUS_FAILURE);
299156230Smux}
300156230Smux
301156230Smux/*
302156230Smux * Exchange collection information.
303156230Smux */
304156230Smuxstatic int
305156230Smuxproto_xchgcoll(struct config *config)
306156230Smux{
307156230Smux	struct coll *coll;
308156230Smux	struct stream *s;
309156230Smux	struct globtree *diraccept, *dirrefuse;
310156230Smux	struct globtree *fileaccept, *filerefuse;
311156230Smux	char *line, *cmd, *collname, *pat;
312156230Smux	char *msg, *release, *ident, *rcskey, *prefix;
313156230Smux	size_t i, len;
314156230Smux	int error, flags, options;
315156230Smux
316156230Smux	s = config->server;
317156230Smux	lprintf(2, "Exchanging collection information\n");
318156230Smux	STAILQ_FOREACH(coll, &config->colls, co_next) {
319186781Slulf		if (coll->co_options & CO_SKIP)
320186781Slulf			continue;
321156230Smux		proto_printf(s, "COLL %s %s %o %d\n", coll->co_name,
322156230Smux		    coll->co_release, coll->co_umask, coll->co_options);
323156230Smux		for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
324156230Smux		    proto_printf(s, "ACC %s\n",
325156230Smux			pattlist_get(coll->co_accepts, i));
326156230Smux		}
327156230Smux		for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
328156230Smux		    proto_printf(s, "REF %s\n",
329156230Smux			pattlist_get(coll->co_refusals, i));
330156230Smux		}
331156230Smux		proto_printf(s, ".\n");
332156230Smux	}
333156230Smux	proto_printf(s, ".\n");
334156230Smux	stream_flush(s);
335156701Smux
336156230Smux	STAILQ_FOREACH(coll, &config->colls, co_next) {
337156230Smux		if (coll->co_options & CO_SKIP)
338156230Smux			continue;
339156701Smux		coll->co_norsync = globtree_false();
340156230Smux		line = stream_getln(s, NULL);
341156230Smux		if (line == NULL)
342156230Smux			goto bad;
343156230Smux		cmd = proto_get_ascii(&line);
344156230Smux		collname = proto_get_ascii(&line);
345156230Smux		release = proto_get_ascii(&line);
346156230Smux		error = proto_get_int(&line, &options, 10);
347156230Smux		if (error || line != NULL)
348156230Smux			goto bad;
349156230Smux		if (strcmp(cmd, "COLL") != 0 ||
350156230Smux		    strcmp(collname, coll->co_name) != 0 ||
351156230Smux		    strcmp(release, coll->co_release) != 0)
352156230Smux			goto bad;
353156230Smux		coll->co_options =
354156230Smux		    (coll->co_options | (options & CO_SERVMAYSET)) &
355156230Smux		    ~(~options & CO_SERVMAYCLEAR);
356156230Smux		while ((line = stream_getln(s, NULL)) != NULL) {
357156230Smux		 	if (strcmp(line, ".") == 0)
358156230Smux				break;
359156230Smux			cmd = proto_get_ascii(&line);
360156230Smux			if (cmd == NULL)
361156230Smux				goto bad;
362156230Smux			if (strcmp(cmd, "!") == 0) {
363156230Smux				msg = proto_get_rest(&line);
364156230Smux				if (msg == NULL)
365156230Smux					goto bad;
366156230Smux				lprintf(-1, "Server message: %s\n", msg);
367156230Smux			} else if (strcmp(cmd, "PRFX") == 0) {
368156230Smux				prefix = proto_get_ascii(&line);
369156230Smux				if (prefix == NULL || line != NULL)
370156230Smux					goto bad;
371156230Smux				coll->co_cvsroot = xstrdup(prefix);
372156230Smux			} else if (strcmp(cmd, "KEYALIAS") == 0) {
373156230Smux				ident = proto_get_ascii(&line);
374156230Smux				rcskey = proto_get_ascii(&line);
375156230Smux				if (rcskey == NULL || line != NULL)
376156230Smux					goto bad;
377156230Smux				error = keyword_alias(coll->co_keyword, ident,
378156230Smux				    rcskey);
379156230Smux				if (error)
380156230Smux					goto bad;
381156230Smux			} else if (strcmp(cmd, "KEYON") == 0) {
382156230Smux				ident = proto_get_ascii(&line);
383156230Smux				if (ident == NULL || line != NULL)
384156230Smux					goto bad;
385156230Smux				error = keyword_enable(coll->co_keyword, ident);
386156230Smux				if (error)
387156230Smux					goto bad;
388156230Smux			} else if (strcmp(cmd, "KEYOFF") == 0) {
389156230Smux				ident = proto_get_ascii(&line);
390156230Smux				if (ident == NULL || line != NULL)
391156230Smux					goto bad;
392156230Smux				error = keyword_disable(coll->co_keyword,
393156230Smux				    ident);
394156230Smux				if (error)
395156230Smux					goto bad;
396156701Smux			} else if (strcmp(cmd, "NORS") == 0) {
397156701Smux				pat = proto_get_ascii(&line);
398156701Smux				if (pat == NULL || line != NULL)
399156701Smux					goto bad;
400156701Smux				coll->co_norsync = globtree_or(coll->co_norsync,
401156701Smux				    globtree_match(pat, FNM_PATHNAME));
402156701Smux			} else if (strcmp(cmd, "RNORS") == 0) {
403156701Smux				pat = proto_get_ascii(&line);
404156701Smux				if (pat == NULL || line != NULL)
405156701Smux					goto bad;
406156701Smux				coll->co_norsync = globtree_or(coll->co_norsync,
407156701Smux				    globtree_match(pat, FNM_PATHNAME |
408156701Smux				    FNM_LEADING_DIR));
409156701Smux			} else
410156701Smux				goto bad;
411156230Smux		}
412156230Smux		if (line == NULL)
413156230Smux			goto bad;
414156230Smux		keyword_prepare(coll->co_keyword);
415156230Smux
416156230Smux		diraccept = globtree_true();
417156230Smux		fileaccept = globtree_true();
418156230Smux		dirrefuse = globtree_false();
419156230Smux		filerefuse = globtree_false();
420156230Smux
421156230Smux		if (pattlist_size(coll->co_accepts) > 0) {
422156230Smux			globtree_free(diraccept);
423156230Smux			globtree_free(fileaccept);
424156230Smux			diraccept = globtree_false();
425156230Smux			fileaccept = globtree_false();
426156230Smux			flags = FNM_PATHNAME | FNM_LEADING_DIR |
427156230Smux			    FNM_PREFIX_DIRS;
428156230Smux			for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
429156230Smux				pat = pattlist_get(coll->co_accepts, i);
430156230Smux				diraccept = globtree_or(diraccept,
431156230Smux				    globtree_match(pat, flags));
432156230Smux
433156230Smux				len = strlen(pat);
434156230Smux				if (coll->co_options & CO_CHECKOUTMODE &&
435156230Smux				    (len == 0 || pat[len - 1] != '*')) {
436156230Smux					/* We must modify the pattern so that it
437156230Smux					   refers to the RCS file, rather than
438156230Smux					   the checked-out file. */
439156230Smux					xasprintf(&pat, "%s,v", pat);
440156230Smux					fileaccept = globtree_or(fileaccept,
441156230Smux					    globtree_match(pat, flags));
442156230Smux					free(pat);
443156230Smux				} else {
444156230Smux					fileaccept = globtree_or(fileaccept,
445156230Smux					    globtree_match(pat, flags));
446156230Smux				}
447156230Smux			}
448156230Smux		}
449156230Smux
450156230Smux		for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
451156230Smux			pat = pattlist_get(coll->co_refusals, i);
452156230Smux			dirrefuse = globtree_or(dirrefuse,
453156230Smux			    globtree_match(pat, 0));
454156230Smux			len = strlen(pat);
455156230Smux			if (coll->co_options & CO_CHECKOUTMODE &&
456156230Smux			    (len == 0 || pat[len - 1] != '*')) {
457156230Smux				/* We must modify the pattern so that it refers
458156230Smux				   to the RCS file, rather than the checked-out
459156230Smux				   file. */
460156230Smux				xasprintf(&pat, "%s,v", pat);
461156230Smux				filerefuse = globtree_or(filerefuse,
462156230Smux				    globtree_match(pat, 0));
463156230Smux				free(pat);
464156230Smux			} else {
465156230Smux				filerefuse = globtree_or(filerefuse,
466156230Smux				    globtree_match(pat, 0));
467156230Smux			}
468156230Smux		}
469156230Smux
470156230Smux		coll->co_dirfilter = globtree_and(diraccept,
471156230Smux		    globtree_not(dirrefuse));
472156230Smux		coll->co_filefilter = globtree_and(fileaccept,
473156230Smux		    globtree_not(filerefuse));
474156230Smux
475156230Smux		/* Set up a mask of file attributes that we don't want to sync
476156230Smux		   with the server. */
477156230Smux		if (!(coll->co_options & CO_SETOWNER))
478156230Smux			coll->co_attrignore |= FA_OWNER | FA_GROUP;
479156230Smux		if (!(coll->co_options & CO_SETMODE))
480156230Smux			coll->co_attrignore |= FA_MODE;
481156230Smux		if (!(coll->co_options & CO_SETFLAGS))
482156230Smux			coll->co_attrignore |= FA_FLAGS;
483156230Smux	}
484156230Smux	return (STATUS_SUCCESS);
485156230Smuxbad:
486156230Smux	lprintf(-1, "Protocol error during collection exchange\n");
487156230Smux	return (STATUS_FAILURE);
488156230Smux}
489156230Smux
490156230Smuxstatic struct mux *
491156230Smuxproto_mux(struct config *config)
492156230Smux{
493156230Smux	struct mux *m;
494156230Smux	struct stream *s, *wr;
495156230Smux	struct chan *chan0, *chan1;
496156230Smux	int id;
497156230Smux
498156230Smux	s = config->server;
499156230Smux	lprintf(2, "Establishing multiplexed-mode data connection\n");
500156230Smux	proto_printf(s, "MUX\n");
501156230Smux	stream_flush(s);
502156230Smux	m = mux_open(config->socket, &chan0);
503156230Smux	if (m == NULL) {
504156230Smux		lprintf(-1, "Cannot open the multiplexer\n");
505156230Smux		return (NULL);
506156230Smux	}
507156230Smux	id = chan_listen(m);
508156230Smux	if (id == -1) {
509156230Smux		lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno));
510156230Smux		mux_close(m);
511156230Smux		return (NULL);
512156230Smux	}
513156230Smux	wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL);
514156230Smux	proto_printf(wr, "CHAN %d\n", id);
515156230Smux	stream_close(wr);
516156230Smux	chan1 = chan_accept(m, id);
517156230Smux	if (chan1 == NULL) {
518156230Smux		lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno));
519156230Smux		mux_close(m);
520156230Smux		return (NULL);
521156230Smux	}
522156230Smux	config->chan0 = chan0;
523156230Smux	config->chan1 = chan1;
524156230Smux	return (m);
525156230Smux}
526156230Smux
527156230Smux/*
528156230Smux * Initializes the connection to the CVSup server, that is handle
529156230Smux * the protocol negotiation, logging in, exchanging file attributes
530156230Smux * support and collections information, and finally run the update
531156230Smux * session.
532156230Smux */
533156230Smuxint
534156230Smuxproto_run(struct config *config)
535156230Smux{
536156230Smux	struct thread_args lister_args;
537156230Smux	struct thread_args detailer_args;
538156230Smux	struct thread_args updater_args;
539156230Smux	struct thread_args *args;
540156230Smux	struct killer killer;
541156230Smux	struct threads *workers;
542156230Smux	struct mux *m;
543156230Smux	int i, status;
544156230Smux
545156230Smux	/*
546156230Smux	 * We pass NULL for the close() function because we'll reuse
547156230Smux	 * the socket after the stream is closed.
548156230Smux	 */
549156230Smux	config->server = stream_open_fd(config->socket, stream_read_fd,
550156230Smux	    stream_write_fd, NULL);
551156230Smux	status = proto_greet(config);
552156230Smux	if (status == STATUS_SUCCESS)
553156230Smux		status = proto_negproto(config);
554156230Smux	if (status == STATUS_SUCCESS)
555203368Slulf		status = auth_login(config);
556156230Smux	if (status == STATUS_SUCCESS)
557156230Smux		status = proto_fileattr(config);
558156230Smux	if (status == STATUS_SUCCESS)
559156230Smux		status = proto_xchgcoll(config);
560156230Smux	if (status != STATUS_SUCCESS)
561156230Smux		return (status);
562156230Smux
563156230Smux	/* Multi-threaded action starts here. */
564156230Smux	m = proto_mux(config);
565156230Smux	if (m == NULL)
566156230Smux		return (STATUS_FAILURE);
567156230Smux
568156230Smux	stream_close(config->server);
569156230Smux	config->server = NULL;
570156230Smux	config->fixups = fixups_new();
571156230Smux	killer_start(&killer, m);
572156230Smux
573156230Smux	/* Start the worker threads. */
574156230Smux	workers = threads_new();
575156230Smux	args = &lister_args;
576156230Smux	args->config = config;
577156230Smux	args->status = -1;
578156230Smux	args->errmsg = NULL;
579156230Smux	args->rd = NULL;
580156230Smux	args->wr = stream_open(config->chan0,
581156230Smux	    NULL, (stream_writefn_t *)chan_write, NULL);
582156230Smux	threads_create(workers, lister, args);
583156230Smux
584156230Smux	args = &detailer_args;
585156230Smux	args->config = config;
586156230Smux	args->status = -1;
587156230Smux	args->errmsg = NULL;
588156230Smux	args->rd = stream_open(config->chan0,
589156230Smux	    (stream_readfn_t *)chan_read, NULL, NULL);
590156230Smux	args->wr = stream_open(config->chan1,
591156230Smux	    NULL, (stream_writefn_t *)chan_write, NULL);
592156230Smux	threads_create(workers, detailer, args);
593156230Smux
594156230Smux	args = &updater_args;
595156230Smux	args->config = config;
596156230Smux	args->status = -1;
597156230Smux	args->errmsg = NULL;
598156230Smux	args->rd = stream_open(config->chan1,
599156230Smux	    (stream_readfn_t *)chan_read, NULL, NULL);
600156230Smux	args->wr = NULL;
601156230Smux	threads_create(workers, updater, args);
602156230Smux
603156230Smux	lprintf(2, "Running\n");
604156230Smux	/* Wait for all the worker threads to finish. */
605156230Smux	status = STATUS_SUCCESS;
606156230Smux	for (i = 0; i < 3; i++) {
607156230Smux		args = threads_wait(workers);
608156230Smux		if (args->rd != NULL)
609156230Smux			stream_close(args->rd);
610156230Smux		if (args->wr != NULL)
611156230Smux			stream_close(args->wr);
612156230Smux		if (args->status != STATUS_SUCCESS) {
613156230Smux			assert(args->errmsg != NULL);
614156230Smux			if (status == STATUS_SUCCESS) {
615156230Smux				status = args->status;
616156230Smux				/* Shutdown the multiplexer to wake up all
617156230Smux				   the other threads. */
618156230Smux				mux_shutdown(m, args->errmsg, status);
619156230Smux			}
620156230Smux			free(args->errmsg);
621156230Smux		}
622156230Smux	}
623156230Smux	threads_free(workers);
624156230Smux	if (status == STATUS_SUCCESS) {
625156230Smux		lprintf(2, "Shutting down connection to server\n");
626156230Smux		chan_close(config->chan0);
627156230Smux		chan_close(config->chan1);
628156230Smux		chan_wait(config->chan0);
629156230Smux		chan_wait(config->chan1);
630156230Smux		mux_shutdown(m, NULL, STATUS_SUCCESS);
631156230Smux	}
632156230Smux	killer_stop(&killer);
633156230Smux	fixups_free(config->fixups);
634156230Smux	status = mux_close(m);
635156230Smux	if (status == STATUS_SUCCESS) {
636156230Smux		lprintf(1, "Finished successfully\n");
637156230Smux	} else if (status == STATUS_INTERRUPTED) {
638156230Smux		lprintf(-1, "Interrupted\n");
639156230Smux		if (killer.killedby != -1)
640156230Smux			kill(getpid(), killer.killedby);
641156230Smux	}
642156230Smux	return (status);
643156230Smux}
644156230Smux
645156230Smux/*
646156230Smux * Write a string into the stream, escaping characters as needed.
647156230Smux * Characters escaped:
648156230Smux *
649156230Smux * SPACE	-> "\_"
650156230Smux * TAB		->  "\t"
651156230Smux * NEWLINE	-> "\n"
652156230Smux * CR		-> "\r"
653156230Smux * \		-> "\\"
654156230Smux */
655156230Smuxstatic int
656156230Smuxproto_escape(struct stream *wr, const char *s)
657156230Smux{
658156230Smux	size_t len;
659156230Smux	ssize_t n;
660156230Smux	char c;
661156230Smux
662156230Smux	/* Handle characters that need escaping. */
663156230Smux	do {
664156230Smux		len = strcspn(s, " \t\r\n\\");
665156230Smux		n = stream_write(wr, s, len);
666156230Smux		if (n == -1)
667156230Smux			return (-1);
668156230Smux		c = s[len];
669156230Smux		switch (c) {
670156230Smux		case ' ':
671156230Smux			n = stream_write(wr, "\\_", 2);
672156230Smux			break;
673156230Smux		case '\t':
674156230Smux			n = stream_write(wr, "\\t", 2);
675156230Smux			break;
676156230Smux		case '\r':
677156230Smux			n = stream_write(wr, "\\r", 2);
678156230Smux			break;
679156230Smux		case '\n':
680156230Smux			n = stream_write(wr, "\\n", 2);
681156230Smux			break;
682156230Smux		case '\\':
683156230Smux			n = stream_write(wr, "\\\\", 2);
684156230Smux			break;
685156230Smux		}
686156230Smux		if (n == -1)
687156230Smux			return (-1);
688156230Smux		s += len + 1;
689156230Smux	} while (c != '\0');
690156230Smux	return (0);
691156230Smux}
692156230Smux
693156230Smux/*
694156230Smux * A simple printf() implementation specifically tailored for csup.
695156230Smux * List of the supported formats:
696156230Smux *
697156230Smux * %c		Print a char.
698156230Smux * %d or %i	Print an int as decimal.
699156230Smux * %x		Print an int as hexadecimal.
700156230Smux * %o		Print an int as octal.
701156230Smux * %t		Print a time_t as decimal.
702156230Smux * %s		Print a char * escaping some characters as needed.
703156230Smux * %S		Print a char * without escaping.
704156230Smux * %f		Print an encoded struct fattr *.
705156230Smux * %F		Print an encoded struct fattr *, specifying the supported
706156230Smux * 		attributes.
707156230Smux */
708156230Smuxint
709156230Smuxproto_printf(struct stream *wr, const char *format, ...)
710156230Smux{
711156230Smux	fattr_support_t *support;
712156230Smux	long long longval;
713156230Smux	struct fattr *fa;
714156230Smux	const char *fmt;
715156230Smux	va_list ap;
716156230Smux	char *cp, *s, *attr;
717156230Smux	ssize_t n;
718186781Slulf	size_t size;
719186781Slulf	off_t off;
720156230Smux	int rv, val, ignore;
721156230Smux	char c;
722156230Smux
723156230Smux	n = 0;
724156230Smux	rv = 0;
725156230Smux	fmt = format;
726156230Smux	va_start(ap, format);
727156230Smux	while ((cp = strchr(fmt, '%')) != NULL) {
728156230Smux		if (cp > fmt) {
729156230Smux			n = stream_write(wr, fmt, cp - fmt);
730156230Smux			if (n == -1)
731156230Smux				return (-1);
732156230Smux		}
733156230Smux		if (*++cp == '\0')
734156230Smux			goto done;
735156230Smux		switch (*cp) {
736156230Smux		case 'c':
737156230Smux			c = va_arg(ap, int);
738156230Smux			rv = stream_printf(wr, "%c", c);
739156230Smux			break;
740156230Smux		case 'd':
741156230Smux		case 'i':
742156230Smux			val = va_arg(ap, int);
743156230Smux			rv = stream_printf(wr, "%d", val);
744156230Smux			break;
745156230Smux		case 'x':
746156230Smux			val = va_arg(ap, int);
747156230Smux			rv = stream_printf(wr, "%x", val);
748156230Smux			break;
749156230Smux		case 'o':
750156230Smux			val = va_arg(ap, int);
751156230Smux			rv = stream_printf(wr, "%o", val);
752156230Smux			break;
753186781Slulf		case 'O':
754186781Slulf			off = va_arg(ap, off_t);
755229184Sdim			rv = stream_printf(wr, "%" PRId64, off);
756186781Slulf			break;
757156230Smux		case 'S':
758156230Smux			s = va_arg(ap, char *);
759156230Smux			assert(s != NULL);
760156230Smux			rv = stream_printf(wr, "%s", s);
761156230Smux			break;
762156230Smux		case 's':
763156230Smux			s = va_arg(ap, char *);
764156230Smux			assert(s != NULL);
765156230Smux			rv = proto_escape(wr, s);
766156230Smux			break;
767156230Smux		case 't':
768156230Smux			longval = (long long)va_arg(ap, time_t);
769156230Smux			rv = stream_printf(wr, "%lld", longval);
770156230Smux			break;
771156230Smux		case 'f':
772156230Smux			fa = va_arg(ap, struct fattr *);
773156230Smux			attr = fattr_encode(fa, NULL, 0);
774156230Smux			rv = proto_escape(wr, attr);
775156230Smux			free(attr);
776156230Smux			break;
777156230Smux		case 'F':
778156230Smux			fa = va_arg(ap, struct fattr *);
779156230Smux			support = va_arg(ap, fattr_support_t *);
780156230Smux			ignore = va_arg(ap, int);
781156230Smux			attr = fattr_encode(fa, *support, ignore);
782156230Smux			rv = proto_escape(wr, attr);
783156230Smux			free(attr);
784156230Smux			break;
785186781Slulf		case 'z':
786186781Slulf			size = va_arg(ap, size_t);
787186781Slulf			rv = stream_printf(wr, "%zu", size);
788186781Slulf			break;
789186781Slulf
790156230Smux		case '%':
791156230Smux			n = stream_write(wr, "%", 1);
792156230Smux			if (n == -1)
793156230Smux				return (-1);
794156230Smux			break;
795156230Smux		}
796156230Smux		if (rv == -1)
797156230Smux			return (-1);
798156230Smux		fmt = cp + 1;
799156230Smux	}
800156230Smux	if (*fmt != '\0') {
801156230Smux		rv = stream_printf(wr, "%s", fmt);
802156230Smux		if (rv == -1)
803156230Smux			return (-1);
804156230Smux	}
805156230Smuxdone:
806156230Smux	va_end(ap);
807156230Smux	return (0);
808156230Smux}
809156230Smux
810156230Smux/*
811156230Smux * Unescape the string, see proto_escape().
812156230Smux */
813156230Smuxstatic void
814156230Smuxproto_unescape(char *s)
815156230Smux{
816156230Smux	char *cp, *cp2;
817156230Smux
818156230Smux	cp = s;
819156230Smux	while ((cp = strchr(cp, '\\')) != NULL) {
820156230Smux		switch (cp[1]) {
821156230Smux		case '_':
822156230Smux			*cp = ' ';
823156230Smux			break;
824156230Smux		case 't':
825156230Smux			*cp = '\t';
826156230Smux			break;
827156230Smux		case 'r':
828156230Smux			*cp = '\r';
829156230Smux			break;
830156230Smux		case 'n':
831156230Smux			*cp = '\n';
832156230Smux			break;
833156230Smux		case '\\':
834156230Smux			*cp = '\\';
835156230Smux			break;
836156230Smux		default:
837156230Smux			*cp = *(cp + 1);
838156230Smux		}
839156230Smux		cp2 = ++cp;
840156230Smux		while (*cp2 != '\0') {
841156230Smux			*cp2 = *(cp2 + 1);
842156230Smux			cp2++;
843156230Smux		}
844156230Smux	}
845156230Smux}
846156230Smux
847156230Smux/*
848156230Smux * Get an ascii token in the string.
849156230Smux */
850156230Smuxchar *
851156230Smuxproto_get_ascii(char **s)
852156230Smux{
853156230Smux	char *ret;
854156230Smux
855156230Smux	ret = strsep(s, " ");
856156230Smux	if (ret == NULL)
857156230Smux		return (NULL);
858156230Smux	/* Make sure we disallow 0-length fields. */
859156230Smux	if (*ret == '\0') {
860156230Smux		*s = NULL;
861156230Smux		return (NULL);
862156230Smux	}
863156230Smux	proto_unescape(ret);
864156230Smux	return (ret);
865156230Smux}
866156230Smux
867156230Smux/*
868156230Smux * Get the rest of the string.
869156230Smux */
870156230Smuxchar *
871156230Smuxproto_get_rest(char **s)
872156230Smux{
873156230Smux	char *ret;
874156230Smux
875156230Smux	if (s == NULL)
876156230Smux		return (NULL);
877156230Smux	ret = *s;
878156230Smux	proto_unescape(ret);
879156230Smux	*s = NULL;
880156230Smux	return (ret);
881156230Smux}
882156230Smux
883156230Smux/*
884156230Smux * Get an int token.
885156230Smux */
886156230Smuxint
887156230Smuxproto_get_int(char **s, int *val, int base)
888156230Smux{
889156701Smux	char *cp;
890156701Smux	int error;
891156230Smux
892156230Smux	cp = proto_get_ascii(s);
893156230Smux	if (cp == NULL)
894156230Smux		return (-1);
895156701Smux	error = asciitoint(cp, val, base);
896156701Smux	return (error);
897156230Smux}
898156230Smux
899156230Smux/*
900186781Slulf * Get a size_t token.
901186781Slulf */
902186781Slulfint
903186781Slulfproto_get_sizet(char **s, size_t *val, int base)
904186781Slulf{
905186781Slulf	unsigned long long tmp;
906186781Slulf	char *cp, *end;
907186781Slulf
908186781Slulf	cp = proto_get_ascii(s);
909186781Slulf	if (cp == NULL)
910186781Slulf		return (-1);
911186781Slulf	errno = 0;
912186781Slulf	tmp = strtoll(cp, &end, base);
913186781Slulf	if (errno || *end != '\0')
914186781Slulf		return (-1);
915186781Slulf	*val = (size_t)tmp;
916186781Slulf	return (0);
917186781Slulf}
918186781Slulf
919186781Slulf/*
920156230Smux * Get a time_t token.
921156230Smux *
922156230Smux * Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
923156230Smux * is more portable and 64bits should be enough for a timestamp.
924156230Smux */
925156230Smuxint
926156230Smuxproto_get_time(char **s, time_t *val)
927156230Smux{
928156230Smux	long long tmp;
929156230Smux	char *cp, *end;
930156230Smux
931156230Smux	cp = proto_get_ascii(s);
932156230Smux	if (cp == NULL)
933156230Smux		return (-1);
934156230Smux	errno = 0;
935156230Smux	tmp = strtoll(cp, &end, 10);
936156230Smux	if (errno || *end != '\0')
937156230Smux		return (-1);
938156230Smux	*val = (time_t)tmp;
939156230Smux	return (0);
940156230Smux}
941156230Smux
942156230Smux/* Start the killer thread.  It is used to protect against some signals
943156230Smux   during the multi-threaded run so that we can gracefully fail.  */
944156230Smuxstatic void
945156230Smuxkiller_start(struct killer *k, struct mux *m)
946156230Smux{
947156230Smux	int error;
948156230Smux
949156230Smux	k->mux = m;
950156230Smux	k->killedby = -1;
951156230Smux	sigemptyset(&k->sigset);
952156230Smux	sigaddset(&k->sigset, SIGINT);
953156230Smux	sigaddset(&k->sigset, SIGHUP);
954156230Smux	sigaddset(&k->sigset, SIGTERM);
955156230Smux	sigaddset(&k->sigset, SIGPIPE);
956156230Smux	pthread_sigmask(SIG_BLOCK, &k->sigset, NULL);
957156230Smux	error = pthread_create(&k->thread, NULL, killer_run, k);
958156230Smux	if (error)
959156230Smux		err(1, "pthread_create");
960156230Smux}
961156230Smux
962156230Smux/* The main loop of the killer thread. */
963156230Smuxstatic void *
964156230Smuxkiller_run(void *arg)
965156230Smux{
966156230Smux	struct killer *k;
967156230Smux	int error, sig, old;
968156230Smux
969156230Smux	k = arg;
970156230Smuxagain:
971156230Smux	error = sigwait(&k->sigset, &sig);
972156230Smux	assert(!error);
973156230Smux	if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
974156230Smux		if (k->killedby == -1) {
975156230Smux			k->killedby = sig;
976156230Smux			/* Ensure we don't get canceled during the shutdown. */
977156230Smux			pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
978156230Smux			mux_shutdown(k->mux, "Cleaning up ...",
979156230Smux			    STATUS_INTERRUPTED);
980156230Smux			pthread_setcancelstate(old, NULL);
981156230Smux		}
982156230Smux	}
983156230Smux	goto again;
984156230Smux}
985156230Smux
986156230Smux/* Stop the killer thread. */
987156230Smuxstatic void
988156230Smuxkiller_stop(struct killer *k)
989156230Smux{
990156230Smux	void *val;
991156230Smux	int error;
992156230Smux
993156230Smux	error = pthread_cancel(k->thread);
994156230Smux	assert(!error);
995156230Smux	pthread_join(k->thread, &val);
996156230Smux	assert(val == PTHREAD_CANCELED);
997156230Smux	pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL);
998156230Smux}
999