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