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>
38228626Sdim#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);
730241049Skevlo			if (n == -1) {
731241049Skevlo				va_end(ap);
732156230Smux				return (-1);
733241049Skevlo			}
734156230Smux		}
735156230Smux		if (*++cp == '\0')
736156230Smux			goto done;
737156230Smux		switch (*cp) {
738156230Smux		case 'c':
739156230Smux			c = va_arg(ap, int);
740156230Smux			rv = stream_printf(wr, "%c", c);
741156230Smux			break;
742156230Smux		case 'd':
743156230Smux		case 'i':
744156230Smux			val = va_arg(ap, int);
745156230Smux			rv = stream_printf(wr, "%d", val);
746156230Smux			break;
747156230Smux		case 'x':
748156230Smux			val = va_arg(ap, int);
749156230Smux			rv = stream_printf(wr, "%x", val);
750156230Smux			break;
751156230Smux		case 'o':
752156230Smux			val = va_arg(ap, int);
753156230Smux			rv = stream_printf(wr, "%o", val);
754156230Smux			break;
755186781Slulf		case 'O':
756186781Slulf			off = va_arg(ap, off_t);
757228626Sdim			rv = stream_printf(wr, "%" PRId64, off);
758186781Slulf			break;
759156230Smux		case 'S':
760156230Smux			s = va_arg(ap, char *);
761156230Smux			assert(s != NULL);
762156230Smux			rv = stream_printf(wr, "%s", s);
763156230Smux			break;
764156230Smux		case 's':
765156230Smux			s = va_arg(ap, char *);
766156230Smux			assert(s != NULL);
767156230Smux			rv = proto_escape(wr, s);
768156230Smux			break;
769156230Smux		case 't':
770156230Smux			longval = (long long)va_arg(ap, time_t);
771156230Smux			rv = stream_printf(wr, "%lld", longval);
772156230Smux			break;
773156230Smux		case 'f':
774156230Smux			fa = va_arg(ap, struct fattr *);
775156230Smux			attr = fattr_encode(fa, NULL, 0);
776156230Smux			rv = proto_escape(wr, attr);
777156230Smux			free(attr);
778156230Smux			break;
779156230Smux		case 'F':
780156230Smux			fa = va_arg(ap, struct fattr *);
781156230Smux			support = va_arg(ap, fattr_support_t *);
782156230Smux			ignore = va_arg(ap, int);
783156230Smux			attr = fattr_encode(fa, *support, ignore);
784156230Smux			rv = proto_escape(wr, attr);
785156230Smux			free(attr);
786156230Smux			break;
787186781Slulf		case 'z':
788186781Slulf			size = va_arg(ap, size_t);
789186781Slulf			rv = stream_printf(wr, "%zu", size);
790186781Slulf			break;
791186781Slulf
792156230Smux		case '%':
793156230Smux			n = stream_write(wr, "%", 1);
794241021Skevlo			if (n == -1) {
795241021Skevlo				va_end(ap);
796156230Smux				return (-1);
797241021Skevlo			}
798156230Smux			break;
799156230Smux		}
800241021Skevlo		if (rv == -1) {
801241021Skevlo			va_end(ap);
802156230Smux			return (-1);
803241021Skevlo		}
804156230Smux		fmt = cp + 1;
805156230Smux	}
806156230Smux	if (*fmt != '\0') {
807156230Smux		rv = stream_printf(wr, "%s", fmt);
808241021Skevlo		if (rv == -1) {
809241021Skevlo			va_end(ap);
810156230Smux			return (-1);
811241021Skevlo		}
812156230Smux	}
813156230Smuxdone:
814156230Smux	va_end(ap);
815156230Smux	return (0);
816156230Smux}
817156230Smux
818156230Smux/*
819156230Smux * Unescape the string, see proto_escape().
820156230Smux */
821156230Smuxstatic void
822156230Smuxproto_unescape(char *s)
823156230Smux{
824156230Smux	char *cp, *cp2;
825156230Smux
826156230Smux	cp = s;
827156230Smux	while ((cp = strchr(cp, '\\')) != NULL) {
828156230Smux		switch (cp[1]) {
829156230Smux		case '_':
830156230Smux			*cp = ' ';
831156230Smux			break;
832156230Smux		case 't':
833156230Smux			*cp = '\t';
834156230Smux			break;
835156230Smux		case 'r':
836156230Smux			*cp = '\r';
837156230Smux			break;
838156230Smux		case 'n':
839156230Smux			*cp = '\n';
840156230Smux			break;
841156230Smux		case '\\':
842156230Smux			*cp = '\\';
843156230Smux			break;
844156230Smux		default:
845156230Smux			*cp = *(cp + 1);
846156230Smux		}
847156230Smux		cp2 = ++cp;
848156230Smux		while (*cp2 != '\0') {
849156230Smux			*cp2 = *(cp2 + 1);
850156230Smux			cp2++;
851156230Smux		}
852156230Smux	}
853156230Smux}
854156230Smux
855156230Smux/*
856156230Smux * Get an ascii token in the string.
857156230Smux */
858156230Smuxchar *
859156230Smuxproto_get_ascii(char **s)
860156230Smux{
861156230Smux	char *ret;
862156230Smux
863156230Smux	ret = strsep(s, " ");
864156230Smux	if (ret == NULL)
865156230Smux		return (NULL);
866156230Smux	/* Make sure we disallow 0-length fields. */
867156230Smux	if (*ret == '\0') {
868156230Smux		*s = NULL;
869156230Smux		return (NULL);
870156230Smux	}
871156230Smux	proto_unescape(ret);
872156230Smux	return (ret);
873156230Smux}
874156230Smux
875156230Smux/*
876156230Smux * Get the rest of the string.
877156230Smux */
878156230Smuxchar *
879156230Smuxproto_get_rest(char **s)
880156230Smux{
881156230Smux	char *ret;
882156230Smux
883156230Smux	if (s == NULL)
884156230Smux		return (NULL);
885156230Smux	ret = *s;
886156230Smux	proto_unescape(ret);
887156230Smux	*s = NULL;
888156230Smux	return (ret);
889156230Smux}
890156230Smux
891156230Smux/*
892156230Smux * Get an int token.
893156230Smux */
894156230Smuxint
895156230Smuxproto_get_int(char **s, int *val, int base)
896156230Smux{
897156701Smux	char *cp;
898156701Smux	int error;
899156230Smux
900156230Smux	cp = proto_get_ascii(s);
901156230Smux	if (cp == NULL)
902156230Smux		return (-1);
903156701Smux	error = asciitoint(cp, val, base);
904156701Smux	return (error);
905156230Smux}
906156230Smux
907156230Smux/*
908186781Slulf * Get a size_t token.
909186781Slulf */
910186781Slulfint
911186781Slulfproto_get_sizet(char **s, size_t *val, int base)
912186781Slulf{
913186781Slulf	unsigned long long tmp;
914186781Slulf	char *cp, *end;
915186781Slulf
916186781Slulf	cp = proto_get_ascii(s);
917186781Slulf	if (cp == NULL)
918186781Slulf		return (-1);
919186781Slulf	errno = 0;
920186781Slulf	tmp = strtoll(cp, &end, base);
921186781Slulf	if (errno || *end != '\0')
922186781Slulf		return (-1);
923186781Slulf	*val = (size_t)tmp;
924186781Slulf	return (0);
925186781Slulf}
926186781Slulf
927186781Slulf/*
928156230Smux * Get a time_t token.
929156230Smux *
930156230Smux * Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
931156230Smux * is more portable and 64bits should be enough for a timestamp.
932156230Smux */
933156230Smuxint
934156230Smuxproto_get_time(char **s, time_t *val)
935156230Smux{
936156230Smux	long long tmp;
937156230Smux	char *cp, *end;
938156230Smux
939156230Smux	cp = proto_get_ascii(s);
940156230Smux	if (cp == NULL)
941156230Smux		return (-1);
942156230Smux	errno = 0;
943156230Smux	tmp = strtoll(cp, &end, 10);
944156230Smux	if (errno || *end != '\0')
945156230Smux		return (-1);
946156230Smux	*val = (time_t)tmp;
947156230Smux	return (0);
948156230Smux}
949156230Smux
950156230Smux/* Start the killer thread.  It is used to protect against some signals
951156230Smux   during the multi-threaded run so that we can gracefully fail.  */
952156230Smuxstatic void
953156230Smuxkiller_start(struct killer *k, struct mux *m)
954156230Smux{
955156230Smux	int error;
956156230Smux
957156230Smux	k->mux = m;
958156230Smux	k->killedby = -1;
959156230Smux	sigemptyset(&k->sigset);
960156230Smux	sigaddset(&k->sigset, SIGINT);
961156230Smux	sigaddset(&k->sigset, SIGHUP);
962156230Smux	sigaddset(&k->sigset, SIGTERM);
963156230Smux	sigaddset(&k->sigset, SIGPIPE);
964156230Smux	pthread_sigmask(SIG_BLOCK, &k->sigset, NULL);
965156230Smux	error = pthread_create(&k->thread, NULL, killer_run, k);
966156230Smux	if (error)
967156230Smux		err(1, "pthread_create");
968156230Smux}
969156230Smux
970156230Smux/* The main loop of the killer thread. */
971156230Smuxstatic void *
972156230Smuxkiller_run(void *arg)
973156230Smux{
974156230Smux	struct killer *k;
975156230Smux	int error, sig, old;
976156230Smux
977156230Smux	k = arg;
978156230Smuxagain:
979156230Smux	error = sigwait(&k->sigset, &sig);
980156230Smux	assert(!error);
981156230Smux	if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
982156230Smux		if (k->killedby == -1) {
983156230Smux			k->killedby = sig;
984156230Smux			/* Ensure we don't get canceled during the shutdown. */
985156230Smux			pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
986156230Smux			mux_shutdown(k->mux, "Cleaning up ...",
987156230Smux			    STATUS_INTERRUPTED);
988156230Smux			pthread_setcancelstate(old, NULL);
989156230Smux		}
990156230Smux	}
991156230Smux	goto again;
992156230Smux}
993156230Smux
994156230Smux/* Stop the killer thread. */
995156230Smuxstatic void
996156230Smuxkiller_stop(struct killer *k)
997156230Smux{
998156230Smux	void *val;
999156230Smux	int error;
1000156230Smux
1001156230Smux	error = pthread_cancel(k->thread);
1002156230Smux	assert(!error);
1003156230Smux	pthread_join(k->thread, &val);
1004156230Smux	assert(val == PTHREAD_CANCELED);
1005156230Smux	pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL);
1006156230Smux}
1007