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