1322249Sbapt/*	$Id: mandocd.c,v 1.6 2017/06/24 14:38:32 schwarze Exp $ */
2313956Sbapt/*
3313956Sbapt * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
4313956Sbapt * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org>
5313956Sbapt *
6313956Sbapt * Permission to use, copy, modify, and distribute this software for any
7313956Sbapt * purpose with or without fee is hereby granted, provided that the above
8313956Sbapt * copyright notice and this permission notice appear in all copies.
9313956Sbapt *
10313956Sbapt * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11313956Sbapt * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12313956Sbapt * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13313956Sbapt * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14313956Sbapt * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15313956Sbapt * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16313956Sbapt * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17313956Sbapt */
18313956Sbapt#include "config.h"
19313956Sbapt
20313956Sbapt#if HAVE_CMSG_XPG42
21313956Sbapt#define _XPG4_2
22313956Sbapt#endif
23313956Sbapt
24313956Sbapt#include <sys/types.h>
25313956Sbapt#include <sys/socket.h>
26313956Sbapt
27313956Sbapt#if HAVE_ERR
28313956Sbapt#include <err.h>
29313956Sbapt#endif
30313956Sbapt#include <limits.h>
31313956Sbapt#include <stdint.h>
32313956Sbapt#include <stdio.h>
33313956Sbapt#include <stdlib.h>
34313956Sbapt#include <string.h>
35313956Sbapt#include <unistd.h>
36313956Sbapt
37313956Sbapt#include "mandoc.h"
38313956Sbapt#include "roff.h"
39313956Sbapt#include "mdoc.h"
40313956Sbapt#include "man.h"
41313956Sbapt#include "main.h"
42313956Sbapt#include "manconf.h"
43313956Sbapt
44313956Sbaptenum	outt {
45313956Sbapt	OUTT_ASCII = 0,
46313956Sbapt	OUTT_UTF8,
47313956Sbapt	OUTT_HTML
48313956Sbapt};
49313956Sbapt
50313956Sbaptstatic	void	  process(struct mparse *, enum outt, void *);
51313956Sbaptstatic	int	  read_fds(int, int *);
52313956Sbaptstatic	void	  usage(void) __attribute__((__noreturn__));
53313956Sbapt
54313956Sbapt
55313956Sbapt#define NUM_FDS 3
56313956Sbaptstatic int
57313956Sbaptread_fds(int clientfd, int *fds)
58313956Sbapt{
59313956Sbapt	struct msghdr	 msg;
60313956Sbapt	struct iovec	 iov[1];
61313956Sbapt	unsigned char	 dummy[1];
62313956Sbapt	struct cmsghdr	*cmsg;
63313956Sbapt	int		*walk;
64313956Sbapt	int		 cnt;
65313956Sbapt
66313956Sbapt	/* Union used for alignment. */
67313956Sbapt	union {
68313956Sbapt		uint8_t controlbuf[CMSG_SPACE(NUM_FDS * sizeof(int))];
69313956Sbapt		struct cmsghdr align;
70313956Sbapt	} u;
71313956Sbapt
72313956Sbapt	memset(&msg, '\0', sizeof(msg));
73313956Sbapt	msg.msg_control = u.controlbuf;
74313956Sbapt	msg.msg_controllen = sizeof(u.controlbuf);
75313956Sbapt
76313956Sbapt	/*
77313956Sbapt	 * Read a dummy byte - sendmsg cannot send an empty message,
78313956Sbapt	 * even if we are only interested in the OOB data.
79313956Sbapt	 */
80313956Sbapt
81313956Sbapt	iov[0].iov_base = dummy;
82313956Sbapt	iov[0].iov_len = sizeof(dummy);
83313956Sbapt	msg.msg_iov = iov;
84313956Sbapt	msg.msg_iovlen = 1;
85313956Sbapt
86313956Sbapt	switch (recvmsg(clientfd, &msg, 0)) {
87313956Sbapt	case -1:
88313956Sbapt		warn("recvmsg");
89313956Sbapt		return -1;
90313956Sbapt	case 0:
91313956Sbapt		return 0;
92313956Sbapt	default:
93313956Sbapt		break;
94313956Sbapt	}
95313956Sbapt
96313956Sbapt	if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) {
97313956Sbapt		warnx("CMSG_FIRSTHDR: missing control message");
98313956Sbapt		return -1;
99313956Sbapt	}
100313956Sbapt
101313956Sbapt	if (cmsg->cmsg_level != SOL_SOCKET ||
102313956Sbapt	    cmsg->cmsg_type != SCM_RIGHTS ||
103313956Sbapt	    cmsg->cmsg_len != CMSG_LEN(NUM_FDS * sizeof(int))) {
104313956Sbapt		warnx("CMSG_FIRSTHDR: invalid control message");
105313956Sbapt		return -1;
106313956Sbapt	}
107313956Sbapt
108313956Sbapt	walk = (int *)CMSG_DATA(cmsg);
109313956Sbapt	for (cnt = 0; cnt < NUM_FDS; cnt++)
110313956Sbapt		fds[cnt] = *walk++;
111313956Sbapt
112313956Sbapt	return 1;
113313956Sbapt}
114313956Sbapt
115313956Sbaptint
116313956Sbaptmain(int argc, char *argv[])
117313956Sbapt{
118313956Sbapt	struct manoutput	 options;
119313956Sbapt	struct mparse		*parser;
120313956Sbapt	void			*formatter;
121313956Sbapt	const char		*defos;
122313956Sbapt	const char		*errstr;
123313956Sbapt	int			 clientfd;
124313956Sbapt	int			 old_stdin;
125313956Sbapt	int			 old_stdout;
126313956Sbapt	int			 old_stderr;
127313956Sbapt	int			 fds[3];
128313956Sbapt	int			 state, opt;
129313956Sbapt	enum outt		 outtype;
130313956Sbapt
131313956Sbapt	defos = NULL;
132313956Sbapt	outtype = OUTT_ASCII;
133313956Sbapt	while ((opt = getopt(argc, argv, "I:T:")) != -1) {
134313956Sbapt		switch (opt) {
135313956Sbapt		case 'I':
136313956Sbapt			if (strncmp(optarg, "os=", 3) == 0)
137313956Sbapt				defos = optarg + 3;
138313956Sbapt			else {
139313956Sbapt				warnx("-I %s: Bad argument", optarg);
140313956Sbapt				usage();
141313956Sbapt			}
142313956Sbapt			break;
143313956Sbapt		case 'T':
144313956Sbapt			if (strcmp(optarg, "ascii") == 0)
145313956Sbapt				outtype = OUTT_ASCII;
146313956Sbapt			else if (strcmp(optarg, "utf8") == 0)
147313956Sbapt				outtype = OUTT_UTF8;
148313956Sbapt			else if (strcmp(optarg, "html") == 0)
149313956Sbapt				outtype = OUTT_HTML;
150313956Sbapt			else {
151313956Sbapt				warnx("-T %s: Bad argument", optarg);
152313956Sbapt				usage();
153313956Sbapt			}
154313956Sbapt			break;
155313956Sbapt		default:
156313956Sbapt			usage();
157313956Sbapt		}
158313956Sbapt	}
159313956Sbapt
160313956Sbapt	if (argc > 0) {
161313956Sbapt		argc -= optind;
162313956Sbapt		argv += optind;
163313956Sbapt	}
164313956Sbapt	if (argc != 1)
165313956Sbapt		usage();
166313956Sbapt
167313956Sbapt	errstr = NULL;
168313956Sbapt	clientfd = strtonum(argv[0], 3, INT_MAX, &errstr);
169313956Sbapt	if (errstr)
170313956Sbapt		errx(1, "file descriptor %s %s", argv[1], errstr);
171313956Sbapt
172313956Sbapt	mchars_alloc();
173313956Sbapt	parser = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
174322249Sbapt	    MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, defos);
175313956Sbapt
176313956Sbapt	memset(&options, 0, sizeof(options));
177313956Sbapt	switch (outtype) {
178313956Sbapt	case OUTT_ASCII:
179313956Sbapt		formatter = ascii_alloc(&options);
180313956Sbapt		break;
181313956Sbapt	case OUTT_UTF8:
182313956Sbapt		formatter = utf8_alloc(&options);
183313956Sbapt		break;
184313956Sbapt	case OUTT_HTML:
185313956Sbapt		options.fragment = 1;
186313956Sbapt		formatter = html_alloc(&options);
187313956Sbapt		break;
188313956Sbapt	}
189313956Sbapt
190313956Sbapt	state = 1;  /* work to do */
191313956Sbapt	fflush(stdout);
192313956Sbapt	fflush(stderr);
193313956Sbapt	if ((old_stdin = dup(STDIN_FILENO)) == -1 ||
194313956Sbapt	    (old_stdout = dup(STDOUT_FILENO)) == -1 ||
195313956Sbapt	    (old_stderr = dup(STDERR_FILENO)) == -1) {
196313956Sbapt		warn("dup");
197313956Sbapt		state = -1;  /* error */
198313956Sbapt	}
199313956Sbapt
200313956Sbapt	while (state == 1 && (state = read_fds(clientfd, fds)) == 1) {
201313956Sbapt		if (dup2(fds[0], STDIN_FILENO) == -1 ||
202313956Sbapt		    dup2(fds[1], STDOUT_FILENO) == -1 ||
203313956Sbapt		    dup2(fds[2], STDERR_FILENO) == -1) {
204313956Sbapt			warn("dup2");
205313956Sbapt			state = -1;
206313956Sbapt			break;
207313956Sbapt		}
208313956Sbapt
209313956Sbapt		close(fds[0]);
210313956Sbapt		close(fds[1]);
211313956Sbapt		close(fds[2]);
212313956Sbapt
213313956Sbapt		process(parser, outtype, formatter);
214313956Sbapt		mparse_reset(parser);
215313956Sbapt
216313956Sbapt		fflush(stdout);
217313956Sbapt		fflush(stderr);
218313956Sbapt		/* Close file descriptors by restoring the old ones. */
219313956Sbapt		if (dup2(old_stderr, STDERR_FILENO) == -1 ||
220313956Sbapt		    dup2(old_stdout, STDOUT_FILENO) == -1 ||
221313956Sbapt		    dup2(old_stdin, STDIN_FILENO) == -1) {
222313956Sbapt			warn("dup2");
223313956Sbapt			state = -1;
224313956Sbapt			break;
225313956Sbapt		}
226313956Sbapt	}
227313956Sbapt
228313956Sbapt	close(clientfd);
229313956Sbapt	switch (outtype) {
230313956Sbapt	case OUTT_ASCII:
231313956Sbapt	case OUTT_UTF8:
232313956Sbapt		ascii_free(formatter);
233313956Sbapt		break;
234313956Sbapt	case OUTT_HTML:
235313956Sbapt		html_free(formatter);
236313956Sbapt		break;
237313956Sbapt	}
238313956Sbapt	mparse_free(parser);
239313956Sbapt	mchars_free();
240313956Sbapt	return state == -1 ? 1 : 0;
241313956Sbapt}
242313956Sbapt
243313956Sbaptstatic void
244313956Sbaptprocess(struct mparse *parser, enum outt outtype, void *formatter)
245313956Sbapt{
246313956Sbapt	struct roff_man	 *man;
247313956Sbapt
248313956Sbapt	mparse_readfd(parser, STDIN_FILENO, "<unixfd>");
249313956Sbapt	mparse_result(parser, &man, NULL);
250313956Sbapt
251313956Sbapt	if (man == NULL)
252313956Sbapt		return;
253313956Sbapt
254313956Sbapt	if (man->macroset == MACROSET_MDOC) {
255313956Sbapt		mdoc_validate(man);
256313956Sbapt		switch (outtype) {
257313956Sbapt		case OUTT_ASCII:
258313956Sbapt		case OUTT_UTF8:
259313956Sbapt			terminal_mdoc(formatter, man);
260313956Sbapt			break;
261313956Sbapt		case OUTT_HTML:
262313956Sbapt			html_mdoc(formatter, man);
263313956Sbapt			break;
264313956Sbapt		}
265313956Sbapt	}
266313956Sbapt	if (man->macroset == MACROSET_MAN) {
267313956Sbapt		man_validate(man);
268313956Sbapt		switch (outtype) {
269313956Sbapt		case OUTT_ASCII:
270313956Sbapt		case OUTT_UTF8:
271313956Sbapt			terminal_man(formatter, man);
272313956Sbapt			break;
273313956Sbapt		case OUTT_HTML:
274313956Sbapt			html_man(formatter, man);
275313956Sbapt			break;
276313956Sbapt		}
277313956Sbapt	}
278313956Sbapt}
279313956Sbapt
280313956Sbaptvoid
281313956Sbaptusage(void)
282313956Sbapt{
283313956Sbapt	fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n");
284313956Sbapt	exit(1);
285313956Sbapt}
286