1 /*- 2 * Copyright (c) 2015-2021 Baptiste Daroussin <bapt@FreeBSD.org> 3 * Copyright (c) 2015 Xin LI <delphij@FreeBSD.org> 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer 10 * in this position and unchanged. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 #include <sys/capsicum.h> 29 #include <sys/types.h> 30 #include <sys/sbuf.h> 31 32 #include <capsicum_helpers.h> 33 #include <ctype.h> 34 #include <err.h> 35 #include <errno.h> 36 #include <stdbool.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 #include <xlocale.h> 42 43 typedef enum { 44 /* state condition to transit to next state */ 45 INIT, /* '$' */ 46 DELIM_SEEN, /* letter */ 47 KEYWORD, /* punctuation mark */ 48 PUNC_SEEN, /* ':' -> _SVN; space -> TEXT */ 49 PUNC_SEEN_SVN, /* space */ 50 TEXT 51 } analyzer_states; 52 53 static int 54 scan(FILE *fp, const char *name, bool quiet) 55 { 56 int c; 57 bool hasid = false; 58 bool subversion = false; 59 analyzer_states state = INIT; 60 FILE* buffp; 61 char *buf; 62 size_t sz; 63 locale_t l; 64 65 l = newlocale(LC_ALL_MASK, "C", NULL); 66 sz = 0; 67 buf = NULL; 68 buffp = open_memstream(&buf, &sz); 69 if (buffp == NULL) 70 err(EXIT_FAILURE, "open_memstream()"); 71 72 if (name != NULL) 73 printf("%s:\n", name); 74 75 while ((c = fgetc(fp)) != EOF) { 76 switch (state) { 77 case INIT: 78 if (c == '$') { 79 /* Transit to DELIM_SEEN if we see $ */ 80 state = DELIM_SEEN; 81 } else { 82 /* Otherwise, stay in INIT state */ 83 continue; 84 } 85 break; 86 case DELIM_SEEN: 87 if (isalpha_l(c, l)) { 88 /* Transit to KEYWORD if we see letter */ 89 if (buf != NULL) 90 memset(buf, 0, sz); 91 rewind(buffp); 92 fputc('$', buffp); 93 fputc(c, buffp); 94 state = KEYWORD; 95 96 continue; 97 } else if (c == '$') { 98 /* Or, stay in DELIM_SEEN if more $ */ 99 continue; 100 } else { 101 /* Otherwise, transit back to INIT */ 102 state = INIT; 103 } 104 break; 105 case KEYWORD: 106 fputc(c, buffp); 107 108 if (isalpha_l(c, l)) { 109 /* 110 * Stay in KEYWORD if additional letter is seen 111 */ 112 continue; 113 } else if (c == ':') { 114 /* 115 * See ':' for the first time, transit to 116 * PUNC_SEEN. 117 */ 118 state = PUNC_SEEN; 119 subversion = false; 120 } else if (c == '$') { 121 /* 122 * Incomplete ident. Go back to DELIM_SEEN 123 * state because we see a '$' which could be 124 * the beginning of a keyword. 125 */ 126 state = DELIM_SEEN; 127 } else { 128 /* 129 * Go back to INIT state otherwise. 130 */ 131 state = INIT; 132 } 133 break; 134 case PUNC_SEEN: 135 case PUNC_SEEN_SVN: 136 fputc(c, buffp); 137 138 switch (c) { 139 case ':': 140 /* 141 * If we see '::' (seen : in PUNC_SEEN), 142 * activate subversion treatment and transit 143 * to PUNC_SEEN_SVN state. 144 * 145 * If more than two :'s were seen, the ident 146 * is invalid and we would therefore go back 147 * to INIT state. 148 */ 149 if (state == PUNC_SEEN) { 150 state = PUNC_SEEN_SVN; 151 subversion = true; 152 } else { 153 state = INIT; 154 } 155 break; 156 case ' ': 157 /* 158 * A space after ':' or '::' indicates we are at the 159 * last component of potential ident. 160 */ 161 state = TEXT; 162 break; 163 default: 164 /* All other characters are invalid */ 165 state = INIT; 166 break; 167 } 168 break; 169 case TEXT: 170 fputc(c, buffp); 171 172 if (iscntrl_l(c, l)) { 173 /* Control characters are not allowed in this state */ 174 state = INIT; 175 } else if (c == '$') { 176 fflush(buffp); 177 /* 178 * valid ident should end with a space. 179 * 180 * subversion extension uses '#' to indicate that 181 * the keyword expansion have exceeded the fixed 182 * width, so it is also permitted if we are in 183 * subversion mode. No length check is enforced 184 * because GNU RCS ident(1) does not do it either. 185 */ 186 c = buf[strlen(buf) -2 ]; 187 if (c == ' ' || (subversion && c == '#')) { 188 printf(" %s\n", buf); 189 hasid = true; 190 } 191 state = INIT; 192 } 193 /* Other characters: stay in the state */ 194 break; 195 } 196 } 197 fclose(buffp); 198 free(buf); 199 freelocale(l); 200 201 if (!hasid) { 202 if (!quiet) 203 fprintf(stderr, "%s warning: no id keywords in %s\n", 204 getprogname(), name ? name : "standard input"); 205 206 return (EXIT_FAILURE); 207 } 208 209 return (EXIT_SUCCESS); 210 } 211 212 int 213 main(int argc, char **argv) 214 { 215 bool quiet = false; 216 int ch, i, *fds, fd; 217 int ret = EXIT_SUCCESS; 218 size_t nfds; 219 FILE *fp; 220 221 while ((ch = getopt(argc, argv, "qV")) != -1) { 222 switch (ch) { 223 case 'q': 224 quiet = true; 225 break; 226 case 'V': 227 /* Do nothing, compat with GNU rcs's ident */ 228 return (EXIT_SUCCESS); 229 default: 230 errx(EXIT_FAILURE, "usage: %s [-q] [-V] [file...]", 231 getprogname()); 232 } 233 } 234 235 argc -= optind; 236 argv += optind; 237 238 if (caph_limit_stdio() < 0) 239 err(EXIT_FAILURE, "unable to limit stdio"); 240 241 if (argc == 0) { 242 nfds = 1; 243 fds = malloc(sizeof(*fds)); 244 if (fds == NULL) 245 err(EXIT_FAILURE, "unable to allocate fds array"); 246 fds[0] = STDIN_FILENO; 247 } else { 248 nfds = argc; 249 fds = malloc(sizeof(*fds) * nfds); 250 if (fds == NULL) 251 err(EXIT_FAILURE, "unable to allocate fds array"); 252 253 for (i = 0; i < argc; i++) { 254 fds[i] = fd = open(argv[i], O_RDONLY); 255 if (fd < 0) { 256 warn("%s", argv[i]); 257 ret = EXIT_FAILURE; 258 continue; 259 } 260 if (caph_limit_stream(fd, CAPH_READ) < 0) 261 err(EXIT_FAILURE, 262 "unable to limit fcntls/rights for %s", 263 argv[i]); 264 } 265 } 266 267 /* Enter Capsicum sandbox. */ 268 if (caph_enter() < 0) 269 err(EXIT_FAILURE, "unable to enter capability mode"); 270 271 for (i = 0; i < (int)nfds; i++) { 272 if (fds[i] < 0) 273 continue; 274 275 fp = fdopen(fds[i], "r"); 276 if (fp == NULL) { 277 warn("%s", argv[i]); 278 ret = EXIT_FAILURE; 279 continue; 280 } 281 if (scan(fp, argc == 0 ? NULL : argv[i], quiet) != EXIT_SUCCESS) 282 ret = EXIT_FAILURE; 283 fclose(fp); 284 } 285 286 return (ret); 287 } 288