| File: | src/usr.bin/rsync/rules.c |
| Warning: | line 430, column 9 Null pointer passed as 1st argument to string comparison function |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* $OpenBSD: rules.c,v 1.4 2021/11/03 14:42:12 deraadt Exp $ */ | |||
| 2 | /* | |||
| 3 | * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org> | |||
| 4 | * | |||
| 5 | * Permission to use, copy, modify, and distribute this software for any | |||
| 6 | * purpose with or without fee is hereby granted, provided that the above | |||
| 7 | * copyright notice and this permission notice appear in all copies. | |||
| 8 | * | |||
| 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| 16 | */ | |||
| 17 | #include <err.h> | |||
| 18 | #include <stdlib.h> | |||
| 19 | #include <stdio.h> | |||
| 20 | #include <string.h> | |||
| 21 | ||||
| 22 | #include "extern.h" | |||
| 23 | ||||
| 24 | struct rule { | |||
| 25 | char *pattern; | |||
| 26 | enum rule_type type; | |||
| 27 | #ifdef NOTYET | |||
| 28 | unsigned int modifiers; | |||
| 29 | #endif | |||
| 30 | short numseg; | |||
| 31 | unsigned char anchored; | |||
| 32 | unsigned char fileonly; | |||
| 33 | unsigned char nowild; | |||
| 34 | unsigned char onlydir; | |||
| 35 | unsigned char leadingdir; | |||
| 36 | }; | |||
| 37 | ||||
| 38 | static struct rule *rules; | |||
| 39 | static size_t numrules; /* number of rules */ | |||
| 40 | static size_t rulesz; /* available size */ | |||
| 41 | ||||
| 42 | /* up to protocol 29 filter rules only support - + ! and no modifiers */ | |||
| 43 | ||||
| 44 | const struct command { | |||
| 45 | enum rule_type type; | |||
| 46 | char sopt; | |||
| 47 | const char *lopt; | |||
| 48 | } commands[] = { | |||
| 49 | { RULE_EXCLUDE, '-', "exclude" }, | |||
| 50 | { RULE_INCLUDE, '+', "include" }, | |||
| 51 | { RULE_CLEAR, '!', "clear" }, | |||
| 52 | #ifdef NOTYET | |||
| 53 | { RULE_MERGE, '.', "merge" }, | |||
| 54 | { RULE_DIR_MERGE, ':', "dir-merge" }, | |||
| 55 | { RULE_SHOW, 'S', "show" }, | |||
| 56 | { RULE_HIDE, 'H', "hide" }, | |||
| 57 | { RULE_PROTECT, 'P', "protect" }, | |||
| 58 | { RULE_RISK, 'R', "risk" }, | |||
| 59 | #endif | |||
| 60 | { 0 } | |||
| 61 | }; | |||
| 62 | ||||
| 63 | #ifdef NOTYET | |||
| 64 | #define MOD_ABSOLUTE 0x0001 | |||
| 65 | #define MOD_NEGATE 0x0002 | |||
| 66 | #define MOD_CVSEXCLUDE 0x0004 | |||
| 67 | #define MOD_SENDING 0x0008 | |||
| 68 | #define MOD_RECEIVING 0x0010 | |||
| 69 | #define MOD_PERISHABLE 0x0020 | |||
| 70 | #define MOD_XATTR 0x0040 | |||
| 71 | #define MOD_MERGE_EXCLUDE 0x0080 | |||
| 72 | #define MOD_MERGE_INCLUDE 0x0100 | |||
| 73 | #define MOD_MERGE_CVSCOMPAT 0x0200 | |||
| 74 | #define MOD_MERGE_EXCLUDE_FILE 0x0400 | |||
| 75 | #define MOD_MERGE_NO_INHERIT 0x0800 | |||
| 76 | #define MOD_MERGE_WORDSPLIT 0x1000 | |||
| 77 | ||||
| 78 | /* maybe support absolute and negate */ | |||
| 79 | const struct modifier { | |||
| 80 | unsigned int modifier; | |||
| 81 | char sopt; | |||
| 82 | } modifiers[] = { | |||
| 83 | { MOD_ABSOLUTE, '/' }, | |||
| 84 | { MOD_NEGATE, '!' }, | |||
| 85 | { MOD_CVSEXCLUDE, 'C' }, | |||
| 86 | { MOD_SENDING, 's' }, | |||
| 87 | { MOD_RECEIVING, 'r' }, | |||
| 88 | { MOD_PERISHABLE, 'p' }, | |||
| 89 | { MOD_XATTR, 'x' }, | |||
| 90 | /* for '.' and ':' types */ | |||
| 91 | { MOD_MERGE_EXCLUDE, '-' }, | |||
| 92 | { MOD_MERGE_INCLUDE, '+' }, | |||
| 93 | { MOD_MERGE_CVSCOMPAT, 'C' }, | |||
| 94 | { MOD_MERGE_EXCLUDE_FILE, 'e' }, | |||
| 95 | { MOD_MERGE_NO_INHERIT, 'n' }, | |||
| 96 | { MOD_MERGE_WORDSPLIT, 'w' }, | |||
| 97 | { 0 } | |||
| 98 | } | |||
| 99 | #endif | |||
| 100 | ||||
| 101 | static struct rule * | |||
| 102 | get_next_rule(void) | |||
| 103 | { | |||
| 104 | struct rule *new; | |||
| 105 | size_t newsz; | |||
| 106 | ||||
| 107 | if (++numrules > rulesz) { | |||
| 108 | if (rulesz == 0) | |||
| 109 | newsz = 16; | |||
| 110 | else | |||
| 111 | newsz = rulesz * 2; | |||
| 112 | ||||
| 113 | new = recallocarray(rules, rulesz, newsz, sizeof(*rules)); | |||
| 114 | if (new == NULL((void *)0)) | |||
| 115 | err(ERR_NOMEM22, NULL((void *)0)); | |||
| 116 | ||||
| 117 | rules = new; | |||
| 118 | rulesz = newsz; | |||
| 119 | } | |||
| 120 | ||||
| 121 | return rules + numrules - 1; | |||
| 122 | } | |||
| 123 | ||||
| 124 | static enum rule_type | |||
| 125 | parse_command(const char *command, size_t len) | |||
| 126 | { | |||
| 127 | const char *mod; | |||
| 128 | size_t i; | |||
| 129 | ||||
| 130 | mod = memchr(command, ',', len); | |||
| 131 | if (mod != NULL((void *)0)) { | |||
| 132 | /* XXX modifiers not yet implemented */ | |||
| 133 | return RULE_NONE; | |||
| 134 | } | |||
| 135 | ||||
| 136 | for (i = 0; commands[i].type != RULE_NONE; i++) { | |||
| 137 | if (strncmp(commands[i].lopt, command, len) == 0) | |||
| 138 | return commands[i].type; | |||
| 139 | if (len == 1 && commands[i].sopt == *command) | |||
| 140 | return commands[i].type; | |||
| 141 | } | |||
| 142 | ||||
| 143 | return RULE_NONE; | |||
| 144 | } | |||
| 145 | ||||
| 146 | static void | |||
| 147 | parse_pattern(struct rule *r, char *pattern) | |||
| 148 | { | |||
| 149 | size_t plen; | |||
| 150 | char *p; | |||
| 151 | short nseg = 1; | |||
| 152 | ||||
| 153 | /* | |||
| 154 | * check for / at start and end of pattern both are special and | |||
| 155 | * can bypass full path matching. | |||
| 156 | */ | |||
| 157 | if (*pattern == '/') { | |||
| 158 | pattern++; | |||
| 159 | r->anchored = 1; | |||
| 160 | } | |||
| 161 | plen = strlen(pattern); | |||
| 162 | /* | |||
| 163 | * check for patterns ending in '/' and '/'+'***' and handle them | |||
| 164 | * specially. Because of this and the check above pattern will never | |||
| 165 | * start or end with a '/'. | |||
| 166 | */ | |||
| 167 | if (plen > 1 && pattern[plen - 1] == '/') { | |||
| 168 | r->onlydir = 1; | |||
| 169 | pattern[plen - 1] = '\0'; | |||
| 170 | } | |||
| 171 | if (plen > 4 && strcmp(pattern + plen - 4, "/***") == 0) { | |||
| 172 | r->leadingdir = 1; | |||
| 173 | pattern[plen - 4] = '\0'; | |||
| 174 | } | |||
| 175 | ||||
| 176 | /* count how many segments the pattern has. */ | |||
| 177 | for (p = pattern; *p != '\0'; p++) | |||
| 178 | if (*p == '/') | |||
| 179 | nseg++; | |||
| 180 | r->numseg = nseg; | |||
| 181 | ||||
| 182 | /* check if this pattern only matches against the basename */ | |||
| 183 | if (nseg == 1 && !r->anchored) | |||
| 184 | r->fileonly = 1; | |||
| 185 | ||||
| 186 | if (strpbrk(pattern, "*?[") == NULL((void *)0)) { | |||
| 187 | /* no wildchar matching */ | |||
| 188 | r->nowild = 1; | |||
| 189 | } else { | |||
| 190 | /* requires wildchar matching */ | |||
| 191 | if (strstr(pattern, "**") != NULL((void *)0)) | |||
| 192 | r->numseg = -1; | |||
| 193 | } | |||
| 194 | ||||
| 195 | r->pattern = strdup(pattern); | |||
| 196 | if (r->pattern == NULL((void *)0)) | |||
| 197 | err(ERR_NOMEM22, NULL((void *)0)); | |||
| 198 | } | |||
| 199 | ||||
| 200 | int | |||
| 201 | parse_rule(char *line, enum rule_type def) | |||
| 202 | { | |||
| 203 | enum rule_type type; | |||
| 204 | struct rule *r; | |||
| 205 | char *pattern; | |||
| 206 | size_t len; | |||
| 207 | ||||
| 208 | switch (*line) { | |||
| 209 | case '#': | |||
| 210 | case ';': | |||
| 211 | /* comment */ | |||
| 212 | return 0; | |||
| 213 | case '\0': | |||
| 214 | /* ingore empty lines */ | |||
| 215 | return 0; | |||
| 216 | default: | |||
| 217 | len = strcspn(line, " _"); | |||
| 218 | type = parse_command(line, len); | |||
| 219 | if (type == RULE_NONE) { | |||
| 220 | if (def == RULE_NONE) | |||
| 221 | return -1; | |||
| 222 | type = def; | |||
| 223 | pattern = line; | |||
| 224 | } else | |||
| 225 | pattern = line + len + 1; | |||
| 226 | ||||
| 227 | if (*pattern == '\0' && type != RULE_CLEAR) | |||
| 228 | return -1; | |||
| 229 | if (*pattern != '\0' && type == RULE_CLEAR) | |||
| 230 | return -1; | |||
| 231 | break; | |||
| 232 | } | |||
| 233 | ||||
| 234 | r = get_next_rule(); | |||
| 235 | r->type = type; | |||
| 236 | parse_pattern(r, pattern); | |||
| 237 | ||||
| 238 | return 0; | |||
| 239 | } | |||
| 240 | ||||
| 241 | void | |||
| 242 | parse_file(const char *file, enum rule_type def) | |||
| 243 | { | |||
| 244 | FILE *fp; | |||
| 245 | char *line = NULL((void *)0); | |||
| 246 | size_t linesize = 0, linenum = 0; | |||
| 247 | ssize_t linelen; | |||
| 248 | ||||
| 249 | if ((fp = fopen(file, "r")) == NULL((void *)0)) | |||
| 250 | err(ERR_SYNTAX1, "open: %s", file); | |||
| 251 | ||||
| 252 | while ((linelen = getline(&line, &linesize, fp)) != -1) { | |||
| 253 | linenum++; | |||
| 254 | line[linelen - 1] = '\0'; | |||
| 255 | if (parse_rule(line, def) == -1) | |||
| 256 | errx(ERR_SYNTAX1, "syntax error in %s at entry %zu", | |||
| 257 | file, linenum); | |||
| 258 | } | |||
| 259 | ||||
| 260 | free(line); | |||
| 261 | if (ferror(fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror )(fp))) | |||
| 262 | err(ERR_SYNTAX1, "failed to parse file %s", file); | |||
| 263 | fclose(fp); | |||
| 264 | } | |||
| 265 | ||||
| 266 | static const char * | |||
| 267 | send_command(struct rule *r) | |||
| 268 | { | |||
| 269 | static char buf[16]; | |||
| 270 | char *b = buf; | |||
| 271 | char *ep = buf + sizeof(buf); | |||
| 272 | ||||
| 273 | switch (r->type) { | |||
| 274 | case RULE_EXCLUDE: | |||
| 275 | *b++ = '-'; | |||
| 276 | break; | |||
| 277 | case RULE_INCLUDE: | |||
| 278 | *b++ = '+'; | |||
| 279 | break; | |||
| 280 | case RULE_CLEAR: | |||
| 281 | *b++ = '!'; | |||
| 282 | break; | |||
| 283 | #ifdef NOTYET | |||
| 284 | case RULE_MERGE: | |||
| 285 | *b++ = '.'; | |||
| 286 | break; | |||
| 287 | case RULE_DIR_MERGE: | |||
| 288 | *b++ = ':'; | |||
| 289 | break; | |||
| 290 | case RULE_SHOW: | |||
| 291 | *b++ = 'S'; | |||
| 292 | break; | |||
| 293 | case RULE_HIDE: | |||
| 294 | *b++ = 'H'; | |||
| 295 | break; | |||
| 296 | case RULE_PROTECT: | |||
| 297 | *b++ = 'P'; | |||
| 298 | break; | |||
| 299 | case RULE_RISK: | |||
| 300 | *b++ = 'R'; | |||
| 301 | break; | |||
| 302 | #endif | |||
| 303 | default: | |||
| 304 | err(ERR_SYNTAX1, "unknown rule type %d", r->type); | |||
| 305 | } | |||
| 306 | ||||
| 307 | #ifdef NOTYET | |||
| 308 | for (i = 0; modifiers[i].modifier != 0; i++) { | |||
| 309 | if (rule->modifiers & modifiers[i].modifier) | |||
| 310 | *b++ = modifiers[i].sopt; | |||
| 311 | if (b >= ep - 3) | |||
| 312 | err(ERR_SYNTAX1, "rule modifiers overflow"); | |||
| 313 | } | |||
| 314 | #endif | |||
| 315 | if (b >= ep - 3) | |||
| 316 | err(ERR_SYNTAX1, "rule prefix overflow"); | |||
| 317 | *b++ = ' '; | |||
| 318 | ||||
| 319 | /* include the stripped root '/' for anchored patterns */ | |||
| 320 | if (r->anchored) | |||
| 321 | *b++ = '/'; | |||
| 322 | *b++ = '\0'; | |||
| 323 | return buf; | |||
| 324 | } | |||
| 325 | ||||
| 326 | static const char * | |||
| 327 | postfix_command(struct rule *r) | |||
| 328 | { | |||
| 329 | static char buf[8]; | |||
| 330 | ||||
| 331 | buf[0] = '\0'; | |||
| 332 | if (r->onlydir) | |||
| 333 | strlcpy(buf, "/", sizeof(buf)); | |||
| 334 | if (r->leadingdir) | |||
| 335 | strlcpy(buf, "/***", sizeof(buf)); | |||
| 336 | ||||
| 337 | return buf; | |||
| 338 | } | |||
| 339 | ||||
| 340 | void | |||
| 341 | send_rules(struct sess *sess, int fd) | |||
| 342 | { | |||
| 343 | const char *cmd; | |||
| 344 | const char *postfix; | |||
| 345 | struct rule *r; | |||
| 346 | size_t cmdlen, len, postlen, i; | |||
| 347 | ||||
| 348 | for (i = 0; i < numrules; i++) { | |||
| 349 | r = &rules[i]; | |||
| 350 | cmd = send_command(r); | |||
| 351 | if (cmd == NULL((void *)0)) | |||
| 352 | err(ERR_PROTOCOL2, | |||
| 353 | "rules are incompatible with remote rsync"); | |||
| 354 | postfix = postfix_command(r); | |||
| 355 | cmdlen = strlen(cmd); | |||
| 356 | len = strlen(r->pattern); | |||
| 357 | postlen = strlen(postfix); | |||
| 358 | ||||
| 359 | if (!io_write_int(sess, fd, cmdlen + len + postlen)) | |||
| 360 | err(ERR_SOCK_IO10, "send rules"); | |||
| 361 | if (!io_write_buf(sess, fd, cmd, cmdlen)) | |||
| 362 | err(ERR_SOCK_IO10, "send rules"); | |||
| 363 | if (!io_write_buf(sess, fd, r->pattern, len)) | |||
| 364 | err(ERR_SOCK_IO10, "send rules"); | |||
| 365 | /* include the '/' stripped by onlydir */ | |||
| 366 | if (postlen > 0) | |||
| 367 | if (!io_write_buf(sess, fd, postfix, postlen)) | |||
| 368 | err(ERR_SOCK_IO10, "send rules"); | |||
| 369 | } | |||
| 370 | ||||
| 371 | if (!io_write_int(sess, fd, 0)) | |||
| 372 | err(ERR_SOCK_IO10, "send rules"); | |||
| 373 | } | |||
| 374 | ||||
| 375 | void | |||
| 376 | recv_rules(struct sess *sess, int fd) | |||
| 377 | { | |||
| 378 | char line[8192]; | |||
| 379 | size_t len; | |||
| 380 | ||||
| 381 | do { | |||
| 382 | if (!io_read_size(sess, fd, &len)) | |||
| 383 | err(ERR_SOCK_IO10, "receive rules"); | |||
| 384 | ||||
| 385 | if (len == 0) | |||
| 386 | return; | |||
| 387 | if (len >= sizeof(line) - 1) | |||
| 388 | errx(ERR_SOCK_IO10, "received rule too long"); | |||
| 389 | if (!io_read_buf(sess, fd, line, len)) | |||
| 390 | err(ERR_SOCK_IO10, "receive rules"); | |||
| 391 | line[len] = '\0'; | |||
| 392 | if (parse_rule(line, RULE_NONE) == -1) | |||
| 393 | errx(ERR_PROTOCOL2, "syntax error in received rules"); | |||
| 394 | } while (1); | |||
| 395 | } | |||
| 396 | ||||
| 397 | static inline int | |||
| 398 | rule_matched(struct rule *r) | |||
| 399 | { | |||
| 400 | /* TODO apply negation once modifiers are added */ | |||
| 401 | ||||
| 402 | if (r->type == RULE_EXCLUDE) | |||
| 403 | return -1; | |||
| 404 | else | |||
| 405 | return 1; | |||
| 406 | } | |||
| 407 | ||||
| 408 | int | |||
| 409 | rules_match(const char *path, int isdir) | |||
| 410 | { | |||
| 411 | const char *basename, *p = NULL((void *)0); | |||
| 412 | struct rule *r; | |||
| 413 | size_t i; | |||
| 414 | ||||
| 415 | basename = strrchr(path, '/'); | |||
| 416 | if (basename != NULL((void *)0)) | |||
| ||||
| 417 | basename += 1; | |||
| 418 | else | |||
| 419 | basename = path; | |||
| 420 | ||||
| 421 | for (i = 0; i < numrules; i++) { | |||
| 422 | r = &rules[i]; | |||
| 423 | ||||
| 424 | if (r->onlydir && !isdir) | |||
| 425 | continue; | |||
| 426 | ||||
| 427 | if (r->nowild) { | |||
| 428 | /* fileonly and anchored are mutually exclusive */ | |||
| 429 | if (r->fileonly) { | |||
| 430 | if (strcmp(basename, r->pattern) == 0) | |||
| ||||
| 431 | return rule_matched(r); | |||
| 432 | } else if (r->anchored) { | |||
| 433 | /* | |||
| 434 | * assumes that neither path nor pattern | |||
| 435 | * start with a '/'. | |||
| 436 | */ | |||
| 437 | if (strcmp(path, r->pattern) == 0) | |||
| 438 | return rule_matched(r); | |||
| 439 | } else if (r->leadingdir) { | |||
| 440 | size_t plen = strlen(r->pattern); | |||
| 441 | ||||
| 442 | p = strstr(path, r->pattern); | |||
| 443 | /* | |||
| 444 | * match from start or dir boundary also | |||
| 445 | * match to end or to dir boundary | |||
| 446 | */ | |||
| 447 | if (p != NULL((void *)0) && (p == path || p[-1] == '/') && | |||
| 448 | (p[plen] == '\0' || p[plen] == '/')) | |||
| 449 | return rule_matched(r); | |||
| 450 | } else { | |||
| 451 | size_t len = strlen(path); | |||
| 452 | size_t plen = strlen(r->pattern); | |||
| 453 | ||||
| 454 | if (len >= plen && strcmp(path + len - plen, | |||
| 455 | r->pattern) == 0) { | |||
| 456 | /* match all or start on dir boundary */ | |||
| 457 | if (len == plen || | |||
| 458 | path[len - plen - 1] == '/') | |||
| 459 | return rule_matched(r); | |||
| 460 | } | |||
| 461 | } | |||
| 462 | } else { | |||
| 463 | if (r->fileonly) { | |||
| 464 | p = basename; | |||
| 465 | } else if (r->anchored || r->numseg == -1) { | |||
| 466 | /* full path matching */ | |||
| 467 | p = path; | |||
| 468 | } else { | |||
| 469 | short nseg = 1; | |||
| 470 | ||||
| 471 | /* match against the last numseg elements */ | |||
| 472 | for (p = path; *p != '\0'; p++) | |||
| 473 | if (*p == '/') | |||
| 474 | nseg++; | |||
| 475 | if (nseg < r->numseg) { | |||
| 476 | p = NULL((void *)0); | |||
| 477 | } else { | |||
| 478 | nseg -= r->numseg; | |||
| 479 | for (p = path; *p != '\0' && nseg > 0; | |||
| 480 | p++) { | |||
| 481 | if (*p == '/') | |||
| 482 | nseg--; | |||
| 483 | } | |||
| 484 | } | |||
| 485 | } | |||
| 486 | ||||
| 487 | if (p != NULL((void *)0)) { | |||
| 488 | if (rmatch(r->pattern, p, r->leadingdir) == 0) | |||
| 489 | return rule_matched(r); | |||
| 490 | } | |||
| 491 | } | |||
| 492 | } | |||
| 493 | ||||
| 494 | return 0; | |||
| 495 | } |