/* * cado: execute a command in a capability ambient * Copyright (C) 2016 Renzo Davoli and Davide Berardi, University of Bologna * * This file is part of cado. * * Cado is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see . * */ #include #include #include #include #include #include #include #include #include /* this structure stores the output of the "parsing" of a scado line. a line has (at most) three colon ":" separated fields. all field and end pointers are addresses within the string "line". the i-th field begins at field[i] and terminates just before end[i] */ struct scado_line { char *line; char *field[3]; char *end[3]; }; /* parse a scado line */ static struct scado_line scado_parse(char *line) { struct scado_line out={line, {NULL, NULL, NULL}, {NULL, NULL, NULL}}; char *scan=line; int i; for (i=0; i<3; i++) { while (isspace(*scan)) scan++; out.field[i] = scan; if (*scan == '#' || *scan == 0) goto end; while (*scan != ':') { if (scan[0] == '\\' && scan[1] != 0) scan++; if (*scan == '#' || *scan == 0) goto end; scan++; } out.end[i] = scan; scan++; } return out; end: out.end[i] = scan; return out; } /* true if the current line matches path, false otherwise. scado_path_match manages the escape chars '\' in the scado line without any copy */ static int scado_path_match(const char *path, struct scado_line *line) { char *lpath; for (lpath = line->field[0]; *path && *lpath; path++, lpath++) { if (lpath[0] == '\\' && lpath[1] != 0) lpath++; if (*lpath != *path) break; } return (*path == 0 && (*lpath == 0 || isspace(*lpath) || *lpath == ':')); } /* get the capability set from a scado file line */ static int scado_get_caps(struct scado_line *line, uint64_t *capset) { if (line->field[1]) { size_t caplen = line->end[1] - line->field[1]; char caps[caplen+1]; strncpy(caps,line->field[1],caplen); caps[caplen]=0; return capset_from_namelist(caps, capset); } else return -1; } /* scado_check_digest_syntax returns: -1 if the digest field has syntax errors, 0 if it is an ampty field (but the field exists!) 1 if the line has just two fields (one ':') OR there is a well-formed hex digest */ static int scado_check_digest_syntax(struct scado_line *line) { if (line->field[2]) { int i; size_t digestlen = line->end[2] - line->field[2]; char digest[digestlen+1]; strncpy(digest,line->field[2],digestlen); digest[digestlen]=0; if (*digest == 0) return 0; if (strlen(digest) < DIGESTSTRLEN) return -1; for (i = 0; i < DIGESTSTRLEN && isxdigit(digest[i]); i++) ; if (i < DIGESTSTRLEN) return -1; for (; digest[i] != 0; i++) if (!isspace(digest[i])) return -1; return 1; } else return 1; } /* true if the field exists and it is not empty */ static inline int fieldok(struct scado_line *line, int i) { return line->field[i] && line->end[i] != line->field[i]; } /* some syntax sanity checks */ static int scado_syntax_check(char *path, int lineno, struct scado_line *line) { uint64_t capset; int rv=0; if (line->end[2] && line->end[2][0] == ':') { fprintf(stderr,"%s line %d: extra trailing chars\n", path, lineno); rv = -1; } if (fieldok(line, 0) && line->field[0][0] != '/') { fprintf(stderr,"%s line %d: pathname is not absolute\n", path, lineno); rv = -1; } if (fieldok(line, 0) && !fieldok(line, 1)) { fprintf(stderr,"%s line %d: missing capability set\n", path, lineno); rv = -1; } if (fieldok(line, 1) && scado_get_caps(line, &capset) < 0) { fprintf(stderr,"%s line %d: wrong capability syntax\n", path, lineno); rv = -1; } if (fieldok(line, 2) && scado_check_digest_syntax(line) < 0) { fprintf(stderr,"%s line %d: wrong digest syntax\n", path, lineno); rv = -1; } return rv; } /* clean the path (parsing escape chars) in place */ static void cleanpath(char *path) { char *out=path; while(*path != 0 && !isspace(*path)) { if (path[0] == '\\' && path[1] != 0) path++; *out++ = *path++; } *out=0; } /* update a line */ /* inpath == NULL -> add the digest it if is missing inpath == "" -> update the digest in any case inpath == "/....." (a valid path) -> update the digest if the path of this line matches */ static void scado_update_line(FILE *fout, struct scado_line *line, char *inpath) { if (line->field[2] != NULL) { /* there is the digest field */ size_t pathlen = line->end[0] - line->field[0]; char path[pathlen+1]; /* create a temporary copy of the path on the stack and clean it*/ strncpy(path,line->field[0],pathlen); path[pathlen]=0; cleanpath(path); int digestok=scado_check_digest_syntax(line); if (digestok < 0) // do not update in case of syntax error fprintf(fout, "%s\n", line->line); else if (digestok == 0) { //missing digest -> fill in! char digest[DIGESTSTRLEN + 1]; int len = line->field[2] - line->line; if (compute_digest(path, digest) < 0) fprintf(fout, "%s\n", line->line); else if (line->end[2] != 0) fprintf(fout,"%*.*s%s %s\n",len,len,line->line,digest,line->end[2]); else fprintf(fout,"%*.*s%s\n",len,len,line->line,digest); } else if (inpath && (*inpath == 0 || strcmp(path, inpath) == 0)) { /* if ALL or this path */ char digest[DIGESTSTRLEN + 1]; int len = line->field[2] - line->line; if (compute_digest(path, digest) < 0) fprintf(fout, "%s\n", line->line); else fprintf(fout,"%*.*s%s%s\n",len,len,line->line,digest,line->field[2]+DIGESTSTRLEN); } else fprintf(fout, "%s\n", line->line); } else fprintf(fout, "%s\n", line->line); } /* copy the file from inpath to outpath, updating the digest where required: path == NULL -> add missing digests path == "" -> all */ void scado_copy_update(char *inpath, char *outpath, char *path) { FILE *fin = NULL; mode_t oldmask; FILE *fout; char *line; size_t len=0; int lineno=0; ssize_t n; int fdin; struct stat l_stats; /* Symlinks check */ if ((fdin = open(inpath, O_RDONLY | O_NOFOLLOW)) < 0) { return; } if (fstat(fdin, &l_stats) || l_stats.st_mode == S_IFLNK) { return; } fin = fdopen(fdin, "r"); if (fin == NULL) return; oldmask = umask(077); fout = fopen(outpath, "w"); umask(oldmask); if (fout == NULL) { fclose(fin); return; } while ((n = getline(&line, &len, fin)) > 0) { struct scado_line scado_line; line[n-1] = 0; scado_line = scado_parse(line); lineno++; scado_syntax_check(outpath, lineno, &scado_line); scado_update_line(fout, &scado_line, path); } fclose(fin); fclose(fout); free(line); } /* scan the scado file whose pathname is inpath seeking for the line matching with 'path' scado_path_getinfo returns: -1 in case of error, 0 if the line is missing, 1 if the line has been found */ /* if scado_path_getinfo succeeds, pcapset and digest are set to the set of permitted capabilities and the hash digest, respectively. */ int scado_path_getinfo(char *inpath, const char *path, uint64_t *pcapset, char *digest) { FILE *fin = fopen(inpath, "r"); char *line; size_t len=0; ssize_t n; int lineno=0; int rv = 0; if (fin == NULL) return -1; while ((n = getline(&line, &len, fin)) > 0) { struct scado_line scado_line; line[n-1] = 0; scado_line = scado_parse(line); lineno++; if (scado_path_match(path, &scado_line)) { if (scado_syntax_check(inpath, lineno, &scado_line) == 0) { if (scado_get_caps(&scado_line, pcapset) >= 0) { if (scado_line.field[2]) { int i; for (i = 0; i < DIGESTSTRLEN ; i++) digest[i] = tolower(scado_line.field[2][i]); digest[i] = 0; rv = 1; } else { digest[0] = 0; rv = 1; } } } break; } } fclose(fin); free(line); return rv; } #if 0 int main() { char *line; size_t len=0; struct scado_line s; int lineno=0; while (1) { ssize_t n=getline(&line, &len, stdin); line[n-1]=0; lineno++; s=scado_parse(line); printf("L %s\n",s.line); printf("P %s|%s\n",s.field[0],s.end[0]); printf("C %s|%s\n",s.field[1],s.end[1]); printf("H %s|%s\n",s.field[2],s.end[2]); uint64_t cap=0; printf("%d ",scado_get_caps(&s,&cap)); printf("%lx\n",cap); scado_syntax_check(lineno, &s); printf("NULL->");fflush(stdout); scado_update_line(1, &s, NULL); printf("ALL ->");fflush(stdout); scado_update_line(1, &s, ""); printf("bash ->");fflush(stdout); scado_update_line(1, &s, "/bin/bash"); } } #endif #if 0 int main(int argc, char *argv[]) { scado_update(argv[1], argv[2], argv[3]); } #endif #if 0 int main(int argc, char *argv[]) { char digest[DIGESTSTRLEN + 1]; uint64_t capset; if (scado_path_getinfo(argv[1], argv[2], &capset, digest)) { printf("%lx %s\n",capset,digest); } } #endif