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 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 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 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 315 usage(void) 316 { 317 fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n"); 318 exit(1); 319 } 320