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 __FBSDID("$FreeBSD$"); 29 30 #include <sys/capsicum.h> 31 #include <sys/types.h> 32 #include <sys/sbuf.h> 33 34 #include <capsicum_helpers.h> 35 #include <ctype.h> 36 #include <err.h> 37 #include <errno.h> 38 #include <stdbool.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 #include <xlocale.h> 44 45 typedef enum { 46 /* state condition to transit to next state */ 47 INIT, /* '$' */ 48 DELIM_SEEN, /* letter */ 49 KEYWORD, /* punctuation mark */ 50 PUNC_SEEN, /* ':' -> _SVN; space -> TEXT */ 51 PUNC_SEEN_SVN, /* space */ 52 TEXT 53 } analyzer_states; 54 55 static int 56 scan(FILE *fp, const char *name, bool quiet) 57 { 58 int c; 59 bool hasid = false; 60 bool subversion = false; 61 analyzer_states state = INIT; 62 FILE* buffp; 63 char *buf; 64 size_t sz; 65 locale_t l; 66 67 l = newlocale(LC_ALL_MASK, "C", NULL); 68 sz = 0; 69 buf = NULL; 70 buffp = open_memstream(&buf, &sz); 71 if (buffp == NULL) 72 err(EXIT_FAILURE, "open_memstream()"); 73 74 if (name != NULL) 75 printf("%s:\n", name); 76 77 while ((c = fgetc(fp)) != EOF) { 78 switch (state) { 79 case INIT: 80 if (c == '$') { 81 /* Transit to DELIM_SEEN if we see $ */ 82 state = DELIM_SEEN; 83 } else { 84 /* Otherwise, stay in INIT state */ 85 continue; 86 } 87 break; 88 case DELIM_SEEN: 89 if (isalpha_l(c, l)) { 90 /* Transit to KEYWORD if we see letter */ 91 if (buf != NULL) 92 memset(buf, 0, sz); 93 rewind(buffp); 94 fputc('$', buffp); 95 fputc(c, buffp); 96 state = KEYWORD; 97 98 continue; 99 } else if (c == '$') { 100 /* Or, stay in DELIM_SEEN if more $ */ 101 continue; 102 } else { 103 /* Otherwise, transit back to INIT */ 104 state = INIT; 105 } 106 break; 107 case KEYWORD: 108 fputc(c, buffp); 109 110 if (isalpha_l(c, l)) { 111 /* 112 * Stay in KEYWORD if additional letter is seen 113 */ 114 continue; 115 } else if (c == ':') { 116 /* 117 * See ':' for the first time, transit to 118 * PUNC_SEEN. 119 */ 120 state = PUNC_SEEN; 121 subversion = false; 122 } else if (c == '$') { 123 /* 124 * Incomplete ident. Go back to DELIM_SEEN 125 * state because we see a '$' which could be 126 * the beginning of a keyword. 127 */ 128 state = DELIM_SEEN; 129 } else { 130 /* 131 * Go back to INIT state otherwise. 132 */ 133 state = INIT; 134 } 135 break; 136 case PUNC_SEEN: 137 case PUNC_SEEN_SVN: 138 fputc(c, buffp); 139 140 switch (c) { 141 case ':': 142 /* 143 * If we see '::' (seen : in PUNC_SEEN), 144 * activate subversion treatment and transit 145 * to PUNC_SEEN_SVN state. 146 * 147 * If more than two :'s were seen, the ident 148 * is invalid and we would therefore go back 149 * to INIT state. 150 */ 151 if (state == PUNC_SEEN) { 152 state = PUNC_SEEN_SVN; 153 subversion = true; 154 } else { 155 state = INIT; 156 } 157 break; 158 case ' ': 159 /* 160 * A space after ':' or '::' indicates we are at the 161 * last component of potential ident. 162 */ 163 state = TEXT; 164 break; 165 default: 166 /* All other characters are invalid */ 167 state = INIT; 168 break; 169 } 170 break; 171 case TEXT: 172 fputc(c, buffp); 173 174 if (iscntrl_l(c, l)) { 175 /* Control characters are not allowed in this state */ 176 state = INIT; 177 } else if (c == '$') { 178 fflush(buffp); 179 /* 180 * valid ident should end with a space. 181 * 182 * subversion extension uses '#' to indicate that 183 * the keyword expansion have exceeded the fixed 184 * width, so it is also permitted if we are in 185 * subversion mode. No length check is enforced 186 * because GNU RCS ident(1) does not do it either. 187 */ 188 c = buf[strlen(buf) -2 ]; 189 if (c == ' ' || (subversion && c == '#')) { 190 printf(" %s\n", buf); 191 hasid = true; 192 } 193 state = INIT; 194 } 195 /* Other characters: stay in the state */ 196 break; 197 } 198 } 199 fclose(buffp); 200 free(buf); 201 freelocale(l); 202 203 if (!hasid) { 204 if (!quiet) 205 fprintf(stderr, "%s warning: no id keywords in %s\n", 206 getprogname(), name ? name : "standard input"); 207 208 return (EXIT_FAILURE); 209 } 210 211 return (EXIT_SUCCESS); 212 } 213 214 int 215 main(int argc, char **argv) 216 { 217 bool quiet = false; 218 int ch, i, *fds, fd; 219 int ret = EXIT_SUCCESS; 220 size_t nfds; 221 FILE *fp; 222 223 while ((ch = getopt(argc, argv, "qV")) != -1) { 224 switch (ch) { 225 case 'q': 226 quiet = true; 227 break; 228 case 'V': 229 /* Do nothing, compat with GNU rcs's ident */ 230 return (EXIT_SUCCESS); 231 default: 232 errx(EXIT_FAILURE, "usage: %s [-q] [-V] [file...]", 233 getprogname()); 234 } 235 } 236 237 argc -= optind; 238 argv += optind; 239 240 if (caph_limit_stdio() < 0) 241 err(EXIT_FAILURE, "unable to limit stdio"); 242 243 if (argc == 0) { 244 nfds = 1; 245 fds = malloc(sizeof(*fds)); 246 if (fds == NULL) 247 err(EXIT_FAILURE, "unable to allocate fds array"); 248 fds[0] = STDIN_FILENO; 249 } else { 250 nfds = argc; 251 fds = malloc(sizeof(*fds) * nfds); 252 if (fds == NULL) 253 err(EXIT_FAILURE, "unable to allocate fds array"); 254 255 for (i = 0; i < argc; i++) { 256 fds[i] = fd = open(argv[i], O_RDONLY); 257 if (fd < 0) { 258 warn("%s", argv[i]); 259 ret = EXIT_FAILURE; 260 continue; 261 } 262 if (caph_limit_stream(fd, CAPH_READ) < 0) 263 err(EXIT_FAILURE, 264 "unable to limit fcntls/rights for %s", 265 argv[i]); 266 } 267 } 268 269 /* Enter Capsicum sandbox. */ 270 if (caph_enter() < 0) 271 err(EXIT_FAILURE, "unable to enter capability mode"); 272 273 for (i = 0; i < (int)nfds; i++) { 274 if (fds[i] < 0) 275 continue; 276 277 fp = fdopen(fds[i], "r"); 278 if (fp == NULL) { 279 warn("%s", argv[i]); 280 ret = EXIT_FAILURE; 281 continue; 282 } 283 if (scan(fp, argc == 0 ? NULL : argv[i], quiet) != EXIT_SUCCESS) 284 ret = EXIT_FAILURE; 285 fclose(fp); 286 } 287 288 return (ret); 289 } 290