1/*	$Id: mandocd.c,v 1.6 2017/06/24 14:38:32 schwarze Exp $ */
2/*
3 * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
4 * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#include "config.h"
19
20#if HAVE_CMSG_XPG42
21#define _XPG4_2
22#endif
23
24#include <sys/types.h>
25#include <sys/socket.h>
26
27#if HAVE_ERR
28#include <err.h>
29#endif
30#include <limits.h>
31#include <stdint.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36
37#include "mandoc.h"
38#include "roff.h"
39#include "mdoc.h"
40#include "man.h"
41#include "main.h"
42#include "manconf.h"
43
44enum	outt {
45	OUTT_ASCII = 0,
46	OUTT_UTF8,
47	OUTT_HTML
48};
49
50static	void	  process(struct mparse *, enum outt, void *);
51static	int	  read_fds(int, int *);
52static	void	  usage(void) __attribute__((__noreturn__));
53
54
55#define NUM_FDS 3
56static int
57read_fds(int clientfd, int *fds)
58{
59	struct msghdr	 msg;
60	struct iovec	 iov[1];
61	unsigned char	 dummy[1];
62	struct cmsghdr	*cmsg;
63	int		*walk;
64	int		 cnt;
65
66	/* Union used for alignment. */
67	union {
68		uint8_t controlbuf[CMSG_SPACE(NUM_FDS * sizeof(int))];
69		struct cmsghdr align;
70	} u;
71
72	memset(&msg, '\0', sizeof(msg));
73	msg.msg_control = u.controlbuf;
74	msg.msg_controllen = sizeof(u.controlbuf);
75
76	/*
77	 * Read a dummy byte - sendmsg cannot send an empty message,
78	 * even if we are only interested in the OOB data.
79	 */
80
81	iov[0].iov_base = dummy;
82	iov[0].iov_len = sizeof(dummy);
83	msg.msg_iov = iov;
84	msg.msg_iovlen = 1;
85
86	switch (recvmsg(clientfd, &msg, 0)) {
87	case -1:
88		warn("recvmsg");
89		return -1;
90	case 0:
91		return 0;
92	default:
93		break;
94	}
95
96	if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) {
97		warnx("CMSG_FIRSTHDR: missing control message");
98		return -1;
99	}
100
101	if (cmsg->cmsg_level != SOL_SOCKET ||
102	    cmsg->cmsg_type != SCM_RIGHTS ||
103	    cmsg->cmsg_len != CMSG_LEN(NUM_FDS * sizeof(int))) {
104		warnx("CMSG_FIRSTHDR: invalid control message");
105		return -1;
106	}
107
108	walk = (int *)CMSG_DATA(cmsg);
109	for (cnt = 0; cnt < NUM_FDS; cnt++)
110		fds[cnt] = *walk++;
111
112	return 1;
113}
114
115int
116main(int argc, char *argv[])
117{
118	struct manoutput	 options;
119	struct mparse		*parser;
120	void			*formatter;
121	const char		*defos;
122	const char		*errstr;
123	int			 clientfd;
124	int			 old_stdin;
125	int			 old_stdout;
126	int			 old_stderr;
127	int			 fds[3];
128	int			 state, opt;
129	enum outt		 outtype;
130
131	defos = NULL;
132	outtype = OUTT_ASCII;
133	while ((opt = getopt(argc, argv, "I:T:")) != -1) {
134		switch (opt) {
135		case 'I':
136			if (strncmp(optarg, "os=", 3) == 0)
137				defos = optarg + 3;
138			else {
139				warnx("-I %s: Bad argument", optarg);
140				usage();
141			}
142			break;
143		case 'T':
144			if (strcmp(optarg, "ascii") == 0)
145				outtype = OUTT_ASCII;
146			else if (strcmp(optarg, "utf8") == 0)
147				outtype = OUTT_UTF8;
148			else if (strcmp(optarg, "html") == 0)
149				outtype = OUTT_HTML;
150			else {
151				warnx("-T %s: Bad argument", optarg);
152				usage();
153			}
154			break;
155		default:
156			usage();
157		}
158	}
159
160	if (argc > 0) {
161		argc -= optind;
162		argv += optind;
163	}
164	if (argc != 1)
165		usage();
166
167	errstr = NULL;
168	clientfd = strtonum(argv[0], 3, INT_MAX, &errstr);
169	if (errstr)
170		errx(1, "file descriptor %s %s", argv[1], errstr);
171
172	mchars_alloc();
173	parser = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
174	    MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, defos);
175
176	memset(&options, 0, sizeof(options));
177	switch (outtype) {
178	case OUTT_ASCII:
179		formatter = ascii_alloc(&options);
180		break;
181	case OUTT_UTF8:
182		formatter = utf8_alloc(&options);
183		break;
184	case OUTT_HTML:
185		options.fragment = 1;
186		formatter = html_alloc(&options);
187		break;
188	}
189
190	state = 1;  /* work to do */
191	fflush(stdout);
192	fflush(stderr);
193	if ((old_stdin = dup(STDIN_FILENO)) == -1 ||
194	    (old_stdout = dup(STDOUT_FILENO)) == -1 ||
195	    (old_stderr = dup(STDERR_FILENO)) == -1) {
196		warn("dup");
197		state = -1;  /* error */
198	}
199
200	while (state == 1 && (state = read_fds(clientfd, fds)) == 1) {
201		if (dup2(fds[0], STDIN_FILENO) == -1 ||
202		    dup2(fds[1], STDOUT_FILENO) == -1 ||
203		    dup2(fds[2], STDERR_FILENO) == -1) {
204			warn("dup2");
205			state = -1;
206			break;
207		}
208
209		close(fds[0]);
210		close(fds[1]);
211		close(fds[2]);
212
213		process(parser, outtype, formatter);
214		mparse_reset(parser);
215
216		fflush(stdout);
217		fflush(stderr);
218		/* Close file descriptors by restoring the old ones. */
219		if (dup2(old_stderr, STDERR_FILENO) == -1 ||
220		    dup2(old_stdout, STDOUT_FILENO) == -1 ||
221		    dup2(old_stdin, STDIN_FILENO) == -1) {
222			warn("dup2");
223			state = -1;
224			break;
225		}
226	}
227
228	close(clientfd);
229	switch (outtype) {
230	case OUTT_ASCII:
231	case OUTT_UTF8:
232		ascii_free(formatter);
233		break;
234	case OUTT_HTML:
235		html_free(formatter);
236		break;
237	}
238	mparse_free(parser);
239	mchars_free();
240	return state == -1 ? 1 : 0;
241}
242
243static void
244process(struct mparse *parser, enum outt outtype, void *formatter)
245{
246	struct roff_man	 *man;
247
248	mparse_readfd(parser, STDIN_FILENO, "<unixfd>");
249	mparse_result(parser, &man, NULL);
250
251	if (man == NULL)
252		return;
253
254	if (man->macroset == MACROSET_MDOC) {
255		mdoc_validate(man);
256		switch (outtype) {
257		case OUTT_ASCII:
258		case OUTT_UTF8:
259			terminal_mdoc(formatter, man);
260			break;
261		case OUTT_HTML:
262			html_mdoc(formatter, man);
263			break;
264		}
265	}
266	if (man->macroset == MACROSET_MAN) {
267		man_validate(man);
268		switch (outtype) {
269		case OUTT_ASCII:
270		case OUTT_UTF8:
271			terminal_man(formatter, man);
272			break;
273		case OUTT_HTML:
274			html_man(formatter, man);
275			break;
276		}
277	}
278}
279
280void
281usage(void)
282{
283	fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n");
284	exit(1);
285}
286