1 /* $Id: mandocd.c,v 1.11 2019/03/03 13:02:11 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 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 "mandoc_parse.h" 42 #include "main.h" 43 #include "manconf.h" 44 45 enum outt { 46 OUTT_ASCII = 0, 47 OUTT_UTF8, 48 OUTT_HTML 49 }; 50 51 static void process(struct mparse *, enum outt, void *); 52 static int read_fds(int, int *); 53 static void usage(void) __attribute__((__noreturn__)); 54 55 56 #define NUM_FDS 3 57 static int 58 read_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 116 int 117 main(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 246 static void 247 process(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 277 void 278 usage(void) 279 { 280 fprintf(stderr, "usage: mandocd [-I os=name] [-T output] socket_fd\n"); 281 exit(1); 282 } 283