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