Annotation of src/usr.bin/rsync/rules.c, Revision 1.4
1.4 ! deraadt 1: /* $OpenBSD: rules.c,v 1.3 2021/11/03 08:30:14 claudio Exp $ */
1.3 claudio 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: */
1.1 claudio 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;
1.4 ! deraadt 26: enum rule_type type;
1.1 claudio 27: #ifdef NOTYET
1.4 ! deraadt 28: unsigned int modifiers;
1.1 claudio 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)
115: err(ERR_NOMEM, NULL);
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;
1.4 ! deraadt 129:
1.1 claudio 130: mod = memchr(command, ',', len);
131: if (mod != NULL) {
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:
1.4 ! deraadt 182: /* check if this pattern only matches against the basename */
1.1 claudio 183: if (nseg == 1 && !r->anchored)
184: r->fileonly = 1;
185:
186: if (strpbrk(pattern, "*?[") == NULL) {
187: /* no wildchar matching */
188: r->nowild = 1;
189: } else {
190: /* requires wildchar matching */
191: if (strstr(pattern, "**") != NULL)
192: r->numseg = -1;
193: }
194:
195: r->pattern = strdup(pattern);
196: if (r->pattern == NULL)
197: err(ERR_NOMEM, NULL);
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;
1.4 ! deraadt 224: } else
1.1 claudio 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
1.2 claudio 242: parse_file(const char *file, enum rule_type def)
1.1 claudio 243: {
244: FILE *fp;
245: char *line = NULL;
246: size_t linesize = 0, linenum = 0;
247: ssize_t linelen;
248:
249: if ((fp = fopen(file, "r")) == NULL)
250: err(ERR_SYNTAX, "open: %s", file);
251:
1.2 claudio 252: while ((linelen = getline(&line, &linesize, fp)) != -1) {
1.1 claudio 253: linenum++;
254: line[linelen - 1] = '\0';
255: if (parse_rule(line, def) == -1)
256: errx(ERR_SYNTAX, "syntax error in %s at entry %zu",
257: file, linenum);
258: }
259:
260: free(line);
261: if (ferror(fp))
262: err(ERR_SYNTAX, "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_SYNTAX, "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_SYNTAX, "rule modifiers overflow");
313: }
314: #endif
315: if (b >= ep - 3)
316: err(ERR_SYNTAX, "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)
352: err(ERR_PROTOCOL,
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_IO, "send rules");
361: if (!io_write_buf(sess, fd, cmd, cmdlen))
362: err(ERR_SOCK_IO, "send rules");
363: if (!io_write_buf(sess, fd, r->pattern, len))
364: err(ERR_SOCK_IO, "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_IO, "send rules");
369: }
370:
371: if (!io_write_int(sess, fd, 0))
372: err(ERR_SOCK_IO, "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_IO, "receive rules");
384:
385: if (len == 0)
386: return;
387: if (len >= sizeof(line) - 1)
388: errx(ERR_SOCK_IO, "received rule too long");
389: if (!io_read_buf(sess, fd, line, len))
390: err(ERR_SOCK_IO, "receive rules");
391: line[len] = '\0';
392: if (parse_rule(line, RULE_NONE) == -1)
393: errx(ERR_PROTOCOL, "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;
412: struct rule *r;
413: size_t i;
414:
1.4 ! deraadt 415: basename = strrchr(path, '/');
1.1 claudio 416: if (basename != NULL)
417: basename += 1;
418: else
419: basename = path;
1.4 ! deraadt 420:
1.1 claudio 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 && (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;
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) {
488: if (rmatch(r->pattern, p, r->leadingdir) == 0)
489: return rule_matched(r);
490: }
491: }
492: }
493:
494: return 0;
495: }