1/*	$Id: mandocd.c,v 1.12 2020/06/14 23:40:31 schwarze Exp $ */
2/*
3 * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org>
4 * Copyright (c) 2017, 2019 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 NEED_XPG4_2
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 "mandoc_parse.h"
42#include "main.h"
43#include "manconf.h"
44
45enum	outt {
46	OUTT_ASCII = 0,
47	OUTT_UTF8,
48	OUTT_HTML
49};
50
51static	void	  process(struct mparse *, enum outt, void *);
52static	int	  read_fds(int, int *);
53static	void	  usage(void) __attribute__((__noreturn__));
54
55
56#define NUM_FDS 3
57static int
58read_fds(int clientfd, int *fds)
59{
60	struct msghdr	 msg;
61	struct iovec	 iov[1];
62	unsigned char	 dummy[1];
63	struct cmsghdr	*cmsg;
64	int		*walk;
65	int		 cnt;
66
67	/* Union used for alignment. */
68	union {
69		uint8_t controlbuf[CMSG_SPACE(NUM_FDS * sizeof(int))];
70		struct cmsghdr align;
71	} u;
72
73	memset(&msg, '\0', sizeof(msg));
74	msg.msg_control = u.controlbuf;
75	msg.msg_controllen = sizeof(u.controlbuf);
76
77	/*
78	 * Read a dummy byte - sendmsg cannot send an empty message,
79	 * even if we are only interested in the OOB data.
80	 */
81
82	iov[0].iov_base = dummy;
83	iov[0].iov_len = sizeof(dummy);
84	msg.msg_iov = iov;
85	msg.msg_iovlen = 1;
86
87	switch (recvmsg(clientfd, &msg, 0)) {
88	case -1:
89		warn("recvmsg");
90		return -1;
91	case 0:
92		return 0;
93	default:
94		break;
95	}
96
97	if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) {
98		warnx("CMSG_FIRSTHDR: missing control message");
99		return -1;
100	}
101
102	if (cmsg->cmsg_level != SOL_SOCKET ||
103	    cmsg->cmsg_type != SCM_RIGHTS ||
104	    cmsg->cmsg_len != CMSG_LEN(NUM_FDS * sizeof(int))) {
105		warnx("CMSG_FIRSTHDR: invalid control message");
106		return -1;
107	}
108
109	walk = (int *)CMSG_DATA(cmsg);
110	for (cnt = 0; cnt < NUM_FDS; cnt++)
111		fds[cnt] = *walk++;
112
113	return 1;
114}
115
116int
117main(int argc, char *argv[])
118{
119	struct manoutput	 options;
120	struct mparse		*parser;
121	void			*formatter;
122	const char		*defos;
123	const char		*errstr;
124	int			 clientfd;
125	int			 old_stdin;
126	int			 old_stdout;
127	int			 old_stderr;
128	int			 fds[3];
129	int			 state, opt;
130	enum outt		 outtype;
131
132	defos = NULL;
133	outtype = OUTT_ASCII;
134	while ((opt = getopt(argc, argv, "I:T:")) != -1) {
135		switch (opt) {
136		case 'I':
137			if (strncmp(optarg, "os=", 3) == 0)
138				defos = optarg + 3;
139			else {
140				warnx("-I %s: Bad argument", optarg);
141				usage();
142			}
143			break;
144		case 'T':
145			if (strcmp(optarg, "ascii") == 0)
146				outtype = OUTT_ASCII;
147			else if (strcmp(optarg, "utf8") == 0)
148				outtype = OUTT_UTF8;
149			else if (strcmp(optarg, "html") == 0)
150				outtype = OUTT_HTML;
151			else {
152				warnx("-T %s: Bad argument", optarg);
153				usage();
154			}
155			break;
156		default:
157			usage();
158		}
159	}
160
161	if (argc > 0) {
162		argc -= optind;
163		argv += optind;
164	}
165	if (argc != 1)
166		usage();
167
168	errstr = NULL;
169	clientfd = strtonum(argv[0], 3, INT_MAX, &errstr);
170	if (errstr)
171		errx(1, "file descriptor %s %s", argv[1], errstr);
172
173	mchars_alloc();
174	parser = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
175	    MPARSE_VALIDATE, MANDOC_OS_OTHER, defos);
176
177	memset(&options, 0, sizeof(options));
178	switch (outtype) {
179	case OUTT_ASCII:
180		formatter = ascii_alloc(&options);
181		break;
182	case OUTT_UTF8:
183		formatter = utf8_alloc(&options);
184		break;
185	case OUTT_HTML:
186		options.fragment = 1;
187		formatter = html_alloc(&options);
188		break;
189	}
190
191	state = 1;  /* work to do */
192	fflush(stdout);
193	fflush(stderr);
194	if ((old_stdin = dup(STDIN_FILENO)) == -1 ||
195	    (old_stdout = dup(STDOUT_FILENO)) == -1 ||
196	    (old_stderr = dup(STDERR_FILENO)) == -1) {
197		warn("dup");
198		state = -1;  /* error */
199	}
200
201	while (state == 1 && (state = read_fds(clientfd, fds)) == 1) {
202		if (dup2(fds[0], STDIN_FILENO) == -1 ||
203		    dup2(fds[1], STDOUT_FILENO) == -1 ||
204		    dup2(fds[2], STDERR_FILENO) == -1) {
205			warn("dup2");
206			state = -1;
207			break;
208		}
209
210		close(fds[0]);
211		close(fds[1]);
212		close(fds[2]);
213
214		process(parser, outtype, formatter);
215		mparse_reset(parser);
216		if (outtype == OUTT_HTML)
217			html_reset(formatter);
218
219		fflush(stdout);
220		fflush(stderr);
221		/* Close file descriptors by restoring the old ones. */
222		if (dup2(old_stderr, STDERR_FILENO) == -1 ||
223		    dup2(old_stdout, STDOUT_FILENO) == -1 ||
224		    dup2(old_stdin, STDIN_FILENO) == -1) {
225			warn("dup2");
226			state = -1;
227			break;
228		}
229	}
230
231	close(clientfd);
232	switch (outtype) {
233	case OUTT_ASCII:
234	case OUTT_UTF8:
235		ascii_free(formatter);
236		break;
237	case OUTT_HTML:
238		html_free(formatter);
239		break;
240	}
241	mparse_free(parser);
242	mchars_free();
243	return state == -1 ? 1 : 0;
244}
245
246static void
247process(struct mparse *parser, enum outt outtype, void *formatter)
248{
249	struct roff_meta *meta;
250
251	mparse_readfd(parser, STDIN_FILENO, "<unixfd>");
252	meta = mparse_result(parser);
253	if (meta->macroset == MACROSET_MDOC) {
254		switch (outtype) {
255		case OUTT_ASCII:
256		case OUTT_UTF8:
257			terminal_mdoc(formatter, meta);
258			break;
259		case OUTT_HTML:
260			html_mdoc(formatter, meta);
261			break;
262		}
263	}
264	if (meta->macroset == MACROSET_MAN) {
265		switch (outtype) {
266		case OUTT_ASCII:
267		case OUTT_UTF8:
268			terminal_man(formatter, meta);
269			break;
270		case OUTT_HTML:
271			html_man(formatter, meta);
272			break;
273		}
274	}
275}
276
277void
278usage(void)
279{
280	fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n");
281	exit(1);
282}
283