/* * - User Access Control * * Copyright 2009-2017 by AVM GmbH * * This software contains free software; you can redistribute it and/or modify it under the terms * of the GNU General Public License ("License") as published by the Free Software Foundation * (version 2 of the License). This software 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 copy of the License you received along with this software for more details. * */ #ifdef HAVE_CONFIG_H # include #endif /* memory debugging * #include "config.h" */ #include #include #include #include #include #include /* isspace () */ #include #include #include #include #include #include #include #include #include "avm_acl.h" // #include "extern.h" #define ACL_BOX_USER_ID_COMPATMODE (100) #define ACL_BOX_USER_ID_SKIP_AUTH_FROM_HOMENET (101) #define ACL_USER_ID_OFFSET (1000) #define USERS_ACL_FILE "/var/tmp/users.acl" #if defined(COMPILE_HOSTTOOLS) static void _fLog(char *fmt, ...) { FILE *fp = fopen("/dev/console", "w"); if (!fp) fp = fopen("/var/tmp/acl.debug", "a"); // NOTE that you have to "chmod 777 /var/tmp" before if (fp) { va_list ap; fprintf(fp, "acl(%d): ", getpid()); va_start(ap, fmt); vfprintf(fp, fmt, ap); va_end(ap); fprintf(fp, "\n"); fclose(fp); } } #define Log(x) _fLog x #undef AVM_ACL_COMMON_REAL_PREFIX #define AVM_ACL_COMMON_REAL_PREFIX "/var/tmp" #else #define Log(x) #endif struct avm_acl_context { char *username; unsigned uid; struct acl_directory *acl; char *real_root; size_t real_root_len; unsigned int access_from_internet:1; unsigned int logged_in:1; unsigned int reconfig:1; unsigned int is_samba:1; }; static struct avm_acl_context glob = { NULL, 0, NULL, NULL, 0, 0, 0, 0, 0, }; struct acl_directory { struct acl_directory *next; char *real_path; /* the user always has read access in this path and all files/folders below */ int real_path_len; /* calc and store the len once to be faster */ short write_access; int common_path_len; // number of chars in real_path for which another acl_directory exists }; static struct avm_acl_context* avm_acl_create_context(enum avm_acl_mode mode); static struct avm_acl_context* avm_acl_login(struct avm_acl_context *ctx, const char *username, int access_from_internet); struct avm_acl_context* avm_acl_login_uid(struct avm_acl_context *ctx, unsigned uid, int access_from_internet); static int avm_acl_set_mode(struct avm_acl_context *ctx, enum avm_acl_mode mode); static void acl_unset_user_context(struct avm_acl_context *ctx); static void avm_acl_free_context(struct avm_acl_context **ppctx); static int FixUserContext(struct avm_acl_context *ctx) { if (!ctx->reconfig) return 0; ctx->reconfig = 0; if (!ctx->acl) return -1; // reconfig with updated users.acl file if (ctx->username) { acl_set_user_context(ctx->username, ctx->access_from_internet); } else { acl_set_user_context_uid(ctx->uid, ctx->access_from_internet); } if (!ctx->acl) return -1; return 0; } /* ------------------------------------------------------------------------ */ // Nutzung macht nur Sinn, wenn der Interne Speicher fuer Netzwerkzugriff nicht aktiviert ist. static int is_mountpoint(const char *path) { const char *p; int len = strlen(path); if (len < strlen(AVM_ACL_COMMON_REAL_PREFIX)) { return 0; } if (path != strstr(path, AVM_ACL_COMMON_REAL_PREFIX)) return 0; p = path + strlen(AVM_ACL_COMMON_REAL_PREFIX); if (*p != '/') return 0; p++; if (*p == '\0') return 0; // kein / mehr oder nur am ende while(*p != '\0') { if (*p == '/') { if (*(p+1) != '\0') return 0; } p++; } return 1; } static int InternalMemoryActiveForNetwork(int reset) { static int known = 0; static int active; if (reset) { // just trigger reload known = 0; return 0; } if (!known) { char buf[PATH_MAX]; known = 1; if (0 == realpath("/var/InternerSpeicher", buf)) { active = 0; } else { // as a "trick" we use the "old" /var/mountpoint_excluded_for_network if (0 == access("/var/mountpoint_excluded_for_network", R_OK)) { active = 0; } active = 1; } } return active; } /** * Block access to listed files. * Only needed for files in one of the directories: * /var/InternerSpeicher * /var/media/ftp (AVM_ACL_COMMON_REAL_PREFIX) * * Other locations of bpjm.data do not need to be blocked here, because the directories are out of access scope. */ static const char *blockedpaths[] = { "/var/InternerSpeicher/FRITZ/bpjm.data", AVM_ACL_COMMON_REAL_PREFIX"/FRITZ/bpjm.data", "/var/InternerSpeicher/FRITZ/firmware_update.image", AVM_ACL_COMMON_REAL_PREFIX"/FRITZ/firmware_update.image", "/var/InternerSpeicher/FRITZ/firmware_update.image.active", AVM_ACL_COMMON_REAL_PREFIX"/FRITZ/firmware_update.image.active", "/var/InternerSpeicher/FRITZ/firmware_update.version", AVM_ACL_COMMON_REAL_PREFIX"/FRITZ/firmware_update.version" }; #define DIM(array) (sizeof(array)/sizeof((array)[0])) static bool IsBlockedPath(const char *path) { unsigned int u; for (u = 0; u < DIM(blockedpaths); u++) { char buf[PATH_MAX]; errno = 0; if (0 != realpath(blockedpaths[u], buf)) { Log(("strcmp(%s, %s)", path, buf)); if (!strcmp(path, buf)) { return true; } } else { Log(("realpath(%s) failed, %s.", blockedpaths[u], strerror(errno))); } } return false; } static int IsHiddenLostAndFound(const char *path) { static char *internal_lost_and_found = (char *)-1; static int internal_lost_and_found_len; if ((char *)-1 == internal_lost_and_found) { char buf[PATH_MAX]; if (0 == realpath("/var/InternerSpeicher", buf)) { internal_lost_and_found = 0; return 0; } else { internal_lost_and_found_len = strlen(buf) + strlen("/lost+found"); internal_lost_and_found = (char*)malloc(internal_lost_and_found_len + 1); if (!internal_lost_and_found) return 0; snprintf(internal_lost_and_found, internal_lost_and_found_len + 1, "%s/lost+found", buf); } } if (0 == internal_lost_and_found) return 0; if (path != strstr(path, internal_lost_and_found)) return 0; if (path[internal_lost_and_found_len] != '/' && path[internal_lost_and_found_len] != '\0') return 0; return 1; } /* ------------------------------------------------------------------------ */ static int avm_acl_is_allowed_path_check(struct avm_acl_context *ctx, const char *path_, int *pwrite_access) { if (pwrite_access) { *pwrite_access = 0; } if (!ctx) { return 0; } if (!path_) { return 0; } struct acl_directory *a = ctx->acl; char buf[PATH_MAX]; char *path; path = strdup(path_); if (!path) { return 0; } Log(("acl_is_allowed_path_check %s.", path)); if (ctx->is_samba && (!InternalMemoryActiveForNetwork(0) || !ctx->logged_in) && !strcmp(path, AVM_ACL_COMMON_REAL_PREFIX)) { // our path of $IPC service if (pwrite_access) { *pwrite_access = 0; } free(path); Log(("acl_is_allowed_path - Read-Only for $IPC")); return 1; } if (!ctx->logged_in) { free(path); Log(("acl_is_allowed_path - not logged in")); return 0; } if (*path == '\0') { /* realpath() with empty string would fail */ if (0 == getcwd(buf, sizeof(buf))) { free(path); Log(("acl_is_allowed_real_path - getcwd failed")); return 0; } buf[sizeof(buf)-1] = '\0'; } else { if (buf != realpath(path, buf)) { int len, buf_remaining; // Check if parent exists // and construct realpath char *p = &path[strlen(path)]; while (p > path && *p != '/') p--; if (*p != '/') { Log(("realpath failed and path=%s has no / p=%s.", path, p)); if (buf != realpath(".", buf)) { // parent does not exists free(path); Log(("realpath of current dir failed")); return 0; } len = strlen(buf); if (0 == len) { free(path); return 0; } buf_remaining = sizeof(buf) - len - 1; if (buf[len-1] != '/') { if (buf_remaining < 1) { free(path); return 0; } strncat(buf, "/", 1); buf_remaining--; } len = strlen(path); if (buf_remaining < len) { free(path); return 0; } strncat(buf, path, len); } else { if (!strcmp(p+1, "..")) { free(path); return 0; } *p = '\0'; if (buf != realpath(path, buf)) { // parent does not exists Log(("realpath of parent=%s failed, buf=%s.", path, buf)); *p = '/'; free(path); return 0; } len = strlen(buf); if (0 == len) { free(path); return 0; } buf_remaining = sizeof(buf) - len - 1; *p = '/'; if (buf[len-1] == '/') { len = strlen(p+1); if (buf_remaining < len) { free(path); return 0; } strncat(buf, p+1, len); } else { len = strlen(p); if (buf_remaining < len) { free(path); return 0; } strncat(buf, p, len); } } Log(("acl_is_real_path - constructed realpath %s.", buf)); } } Log(("checking access to %s.", buf)); if (IsHiddenLostAndFound(buf)) { Log(("acl_is_allowed_path - path %s - not allowed cause its in lost+found in internal flash", buf)); free(path); return 0; } if (IsBlockedPath(buf)) { Log(("path %s is blocked", path)); free(path); return 0; } if (FixUserContext(ctx)) { Log(("acl_is_allowed_path - path %s - not allowed cause FixUserContext failed", buf)); free(path); return 0; } while(a) { if (buf == strstr(buf, a->real_path)) { char c = buf[a->real_path_len]; if (c == '\0' || c == '/') break; // found } a = a->next; } if (a) { if (pwrite_access) { if (InternalMemoryActiveForNetwork(0)) { // /var/media/ftp is the internal memory // and all mountpoints are within the internal memory *pwrite_access = a->write_access ? 1 : 0; } else { // im smbd gibts sonst Probleme mit loeschen/umbennenn // direkt unterhalb des mountpoints if (!ctx->is_samba && is_mountpoint(buf)) { *pwrite_access = 0; } else { *pwrite_access = a->write_access ? 1 : 0; } } } } else { // check for path component to a shared "partition home" // eg. the following paths are configured: // /var/media/ftp/STICK/home/sub1/sub2/sub3 // /var/media/ftp/STICK/home/sub4/sub5 // --> now we also have to allow readonly access to exactly // /var/media/ftp/STICK/home // /var/media/ftp/STICK/home/sub1 // /var/media/ftp/STICK/home/sub1/sub2 // /var/media/ftp/STICK/home/sub4 // otherwise the user would not be able to reach // /var/media/ftp/STICK/home/sub1/sub2/sub3 or // /var/media/ftp/STICK/home/sub4/sub5 // from his "partition root" /var/media/ftp/STICK/home int len = strlen(buf); for (a = ctx->acl; a; a = a->next) { char c; // Log(("checking intermediate path %s against allowed %s.", buf, a->real_path)); if (0 == a->common_path_len) continue; if (len < a->common_path_len) continue; // ist unterhalb der "partition root" c = buf[a->common_path_len]; if (c != '\0' && c != '/') continue; assert(strlen(buf) >= a->common_path_len); if (memcmp(a->real_path, buf, a->common_path_len)) continue; if (a->real_path != strstr(a->real_path, buf)) continue; c = a->real_path[len]; if (c == '/') { Log(("intermediate path %s IS ALLOWED against allowed %s.", buf, a->real_path)); break; // found } } /* Bei Freigabe eines Unterverzeichnisses wird dennoch als einziger! Samba-Share * immer /var/media/ftp freigegeben. * Ein User muss daher auf alle Verzeichnisse auf dem Weg zu seinem user-spezifischen Real-Root * mit Read-Only-Rechten zugreifen duerfen. * Beispiel: Freigabe es Verzeichnisses /Musik/Pop/neu fuer den User * --> user-spezifischer Real-Root ist /var/media/ftp/Musik/Pop/neu * --> Leserecht auch auf folgende Verzeichnisse noetig: * /var/media/ftp * /var/media/ftp/Musik * /var/media/ftp/Pop * (aber auf /var/media/ftp/Musik/Pop/neu ggf. auch Schreibrechte!) */ if (!a && ctx->is_samba && len >= strlen(AVM_ACL_COMMON_REAL_PREFIX)) { for (a = ctx->acl; a; a = a->next) { int rl; char *p; // Log(("samba: checking for parent path %s against allowed %s.", buf, a->real_path)); p = strrchr(a->real_path, '/'); // remove last dir if (!p) continue; rl = p - a->real_path; if (len > rl) continue; // buf ist auf gleichem Level oder subdir von a->real_path if (a->real_path[len] != '/') continue; if (!memcmp(buf, a->real_path, len)) { Log(("samba: parent path %s IS ALLOWED (read only) against allowed %s.", buf, a->real_path)); break; } } } if (a) { if (pwrite_access) { *pwrite_access = 0; } } } if (!a) { Log(("acl_is_allowed_path - path %s - not in any allow path for this user ", buf)); free(path); return 0; } Log(("access to %s allowed write_access=%d ret=1", buf, pwrite_access ? *pwrite_access : 42)); free(path); return 1; } int avm_acl_is_allowed_path(enum avm_acl_mode mode, const char *username, const char *path_, int access_from_internet, int *pwrite_access) { struct avm_acl_context *ctx = NULL; int ret = 0; ctx = avm_acl_create_context(mode); if (!ctx) { ret = 0; goto end; } ctx = avm_acl_login(ctx, username, access_from_internet); if (!ctx) { ret = 0; goto end; } ret = avm_acl_is_allowed_path_check(ctx, path_, pwrite_access); end: avm_acl_free_context(&ctx); return ret; } int avm_acl_is_allowed_path_uid(enum avm_acl_mode mode, unsigned uid, const char *path_, int access_from_internet, int *pwrite_access) { struct avm_acl_context *ctx = NULL; int ret = 0; ctx = avm_acl_create_context(mode); if (!ctx) { ret = 0; goto end; } ctx = avm_acl_login_uid(ctx, uid, access_from_internet); if (!ctx) { ret = 0; goto end; } ret = avm_acl_is_allowed_path_check(ctx, path_, pwrite_access); end: avm_acl_free_context(&ctx); return ret; } int acl_is_allowed_path(const char *path_, int *pwrite_access) { return avm_acl_is_allowed_path_check(&glob, path_, pwrite_access); } /* ------------------------------------------------------------------------ */ static void add_virt_path(struct avm_acl_context *ctx, char *path, int write_access) { struct acl_directory **pp; struct acl_directory *a; struct acl_directory *a2; Log(("add_virt_path %s write_access:%d", path, write_access)); if (path[0] != '/') return; if (path[1] == '\0') path = ""; a = calloc(1, sizeof(struct acl_directory)); if (!a) return; a->real_path = malloc(strlen(AVM_ACL_COMMON_REAL_PREFIX) + strlen(path) + 1); if (!a->real_path) { free(a); return; } strcpy(a->real_path, AVM_ACL_COMMON_REAL_PREFIX); strcat(a->real_path, path); a->real_path_len = strlen(a->real_path); a->write_access = write_access; // calc common_path_len -> bis zum letzten gemeinsamen Verzeichnis vom Pfad-Anfang for (a2 = ctx->acl; a2; a2 = a2->next) { int len; char *p1 = a->real_path; char *p2 = a2->real_path; while(*p1 == *p2 && *p1 != '\0') { p1++; p2++; } if ((*p1 == '\0' || *p1 == '/') && (*p2 == '\0' || *p2 == '/')) { } else { p1--; while(*p1 != '/') p1--; } // match bis einschliesslich p1-1 len = p1 - a->real_path; if (len > a->common_path_len) { Log(("common_path_len of new %s set to %u", a->real_path, len)); a->common_path_len = len; } if (len > a2->common_path_len) { Log(("common_path_len of existing %s set to %u", a2->real_path, len)); a2->common_path_len = len; } } pp = &ctx->acl; while(*pp) { pp = &((*pp)->next); } *pp = a; } /* ----------------------------------------------------------------------- */ enum eToken { TOKEN_EOF, TOKEN_STRING, TOKEN_USER, TOKEN_ID, TOKEN_DIR, TOKEN_ACCESS, TOKEN_LOCAL_READ, TOKEN_LOCAL_WRITE, TOKEN_INTERNET_READ, TOKEN_INTERNET_WRITE, TOKEN_INTERNET_SECURED, TOKEN_APP, TOKEN_APP_USERID, TOKEN_APP_NAS_RIGHTS, TOKEN_APP_INTERNET_RIGHTS }; static struct defined_token { char *string; enum eToken token; } tokens[] = { { "user", TOKEN_USER }, { "id", TOKEN_ID }, { "dir", TOKEN_DIR }, { "access", TOKEN_ACCESS }, { "read", TOKEN_LOCAL_READ }, { "write", TOKEN_LOCAL_WRITE }, { "internet-read", TOKEN_INTERNET_READ }, { "internet-write", TOKEN_INTERNET_WRITE }, { "internet-secured", TOKEN_INTERNET_SECURED }, { "app", TOKEN_APP }, { "userid", TOKEN_APP_USERID }, { "nas-rights", TOKEN_APP_NAS_RIGHTS }, { "internet-rights", TOKEN_APP_INTERNET_RIGHTS }, { 0, TOKEN_EOF } }; struct UserACLReader { FILE *fp; char *string; // current token size_t string_len; size_t string_buf_size; }; static int avmacl_atoi(const char *s) { int v = 0; char *endptr = NULL; errno = 0; v = strtol(s, &endptr, 10); if (errno != 0) { v = 0; } return v; } static void add_char(struct UserACLReader *r, int c) { if (!r->string) return; if (r->string_len >= r->string_buf_size-2) return; r->string[r->string_len] = c; r->string_len++; } static char *GetString(struct UserACLReader *r) { return r->string; } static enum eToken UserACLFile_GetToken(struct UserACLReader *r) { int c; short bQuoted; short bEscaped = 0; if (!r->fp) return TOKEN_EOF; do { c = fgetc(r->fp); if (c == EOF) { fclose(r->fp); r->fp = 0; return TOKEN_EOF; } } while(isspace(c)); if (!r->string) { r->string_buf_size = 256; r->string = (char *)malloc(r->string_buf_size); if (!r->string) return TOKEN_EOF; } r->string_len = 0; if (c == '\"') { bQuoted = 1; } else { bQuoted = 0; add_char(r, c); } do { c = fgetc(r->fp); if (c == EOF) break; if (bQuoted) { // end at " if (bEscaped) { bEscaped = 0; add_char(r, c); } else { if (c == '\\') bEscaped = 1; else if (c == '\"') break; // string end else add_char(r, c); } } else { // end at white space if (isspace(c)) break; // stringend add_char(r, c); } } while(1); r->string[r->string_len++] = '\0'; if (!bQuoted) { struct defined_token *dt; for (dt = &tokens[0]; dt->string; dt++) { if (!strcmp(r->string, dt->string)) return dt->token; } } return TOKEN_STRING; } static struct UserACLReader *UserACLFile_ReadOpen(void) { struct UserACLReader *r = (struct UserACLReader *)calloc(1, sizeof(struct UserACLReader)); if (!r) return 0; r->fp = fopen(USERS_ACL_FILE, "rt"); if (!r->fp) { free(r); return 0; } return r; } static void UserACLFile_ReadClose(struct UserACLReader *r) { if (!r) return; free(r->string); if (r->fp) fclose(r->fp); free(r); } /* user "Udo G" id 70 dir "/x" access write */ static int ReadUser(struct avm_acl_context *ctx, struct UserACLReader *r, int access_from_internet, int allow_write_access) { enum eToken t; short ungot_token = 0; do { short read_access = 0; // effective read right to this path short write_access = 0; // effective write right to this path char *path; if (!ungot_token) t = UserACLFile_GetToken(r); ungot_token = 0; if (t != TOKEN_DIR) break; t = UserACLFile_GetToken(r); if (t != TOKEN_STRING) break; path = strdup(GetString(r)); t = UserACLFile_GetToken(r); // if (!access_from_internet) read_access = 1; if (t == TOKEN_ACCESS) { short end = 0; do { t = UserACLFile_GetToken(r); switch(t) { case TOKEN_LOCAL_READ: if (!access_from_internet) read_access = 1; break; case TOKEN_LOCAL_WRITE: if (!access_from_internet) { write_access = 1; read_access = 1; } break; case TOKEN_INTERNET_READ: if (access_from_internet) read_access = 1; break; case TOKEN_INTERNET_WRITE: if (access_from_internet) { write_access = 1; read_access = 1; } break; default: ungot_token = 1; end = 1; } } while(!end); } else { ungot_token = 1; } if (read_access && path && path[0] == '/') { add_virt_path(ctx, path, (allow_write_access && write_access) ? 1 : 0); } free(path); } while(1); UserACLFile_ReadClose(r); return 0; } static int ReadUsersACLFileByID(struct avm_acl_context *ctx, unsigned id, int access_from_internet, int allow_write_access) { enum eToken t; struct UserACLReader *r = UserACLFile_ReadOpen(); if (!r) return -1; t = UserACLFile_GetToken(r); while(t != TOKEN_EOF) { if (t == TOKEN_USER) { do { t = UserACLFile_GetToken(r); } while(t == TOKEN_STRING); if (t == TOKEN_ID) { t = UserACLFile_GetToken(r); if (t == TOKEN_STRING) { if (avmacl_atoi(GetString(r)) == id) break; } } } else if (t == TOKEN_APP) { /* app by id not supported here */ } t = UserACLFile_GetToken(r); } if (t == TOKEN_EOF) goto fail; return ReadUser(ctx, r, access_from_internet, allow_write_access); fail: UserACLFile_ReadClose(r); return -1; } static int ReadUsersACLFileByFriendlyName(struct avm_acl_context *ctx, const char *username, int access_from_internet) { enum eToken t; int appid = 0; int app_userid; int app_internet_rights; int app_write_access; struct UserACLReader *r = UserACLFile_ReadOpen(); if (!r) return -1; t = UserACLFile_GetToken(r); while(t != TOKEN_EOF) { if (t == TOKEN_USER) { short bFound = 0; do { t = UserACLFile_GetToken(r); if (!bFound && t == TOKEN_STRING && !strcasecmp(GetString(r), username)) bFound = 1; } while (t == TOKEN_STRING); if (bFound) { if (t != TOKEN_ID) goto fail; t = UserACLFile_GetToken(r); if (t != TOKEN_STRING) goto fail; break; } } else if (t == TOKEN_APP) { short bFound = 0; do { t = UserACLFile_GetToken(r); if (!bFound && t == TOKEN_STRING && !strcasecmp(GetString(r), username)) bFound = 1; } while (t == TOKEN_STRING); if (bFound) { if (t != TOKEN_ID) goto fail; t = UserACLFile_GetToken(r); if (t != TOKEN_STRING) goto fail; appid = avmacl_atoi(GetString(r)); if (appid <= 0) goto fail; t = UserACLFile_GetToken(r); if (t != TOKEN_APP_USERID) goto fail; t = UserACLFile_GetToken(r); if (t != TOKEN_STRING) goto fail; app_userid = avmacl_atoi(GetString(r)); if (app_userid <= 0) goto fail; t = UserACLFile_GetToken(r); if (t != TOKEN_APP_NAS_RIGHTS) goto fail; t = UserACLFile_GetToken(r); switch(t) { case TOKEN_LOCAL_READ: app_write_access = 0; break; // read-only case TOKEN_LOCAL_WRITE: app_write_access = 1; break; // read-write default: goto fail; } t = UserACLFile_GetToken(r); if (t != TOKEN_APP_INTERNET_RIGHTS) goto fail; t = UserACLFile_GetToken(r); if (t != TOKEN_STRING) goto fail; app_internet_rights = avmacl_atoi(GetString(r)); if (app_internet_rights < 0) goto fail; break; } } t = UserACLFile_GetToken(r); } if (t == TOKEN_EOF) goto fail; if (0 != appid) { int ret; UserACLFile_ReadClose(r); if (access_from_internet && 0 == app_internet_rights) return 0; if (app_internet_rights && (app_userid == ACL_BOX_USER_ID_COMPATMODE || app_userid == ACL_BOX_USER_ID_SKIP_AUTH_FROM_HOMENET)) { access_from_internet = 0; // this way internet rights are added to the app } ret = ReadUsersACLFileByID(ctx, app_userid, access_from_internet, app_write_access); return ret; } else { return ReadUser(ctx, r, access_from_internet, 1/*allow write access*/); } fail: UserACLFile_ReadClose(r); return -1; } /* ----------------------------------------------------------------------- */ static void acl_unset_user_context(struct avm_acl_context *ctx) { if (!ctx) { /* TODO */ return; } while (ctx->acl) { struct acl_directory *a = ctx->acl; ctx->acl = a->next; free(a->real_path); free(a); } free(ctx->real_root); ctx->real_root = NULL; ctx->real_root_len = 0; ctx->logged_in = 0; /* keep ctx->is_samba */ } static int calc_real_root(struct avm_acl_context *ctx) { int root_path_len; struct acl_directory *root = ctx->acl; Log(("calc_real_root")); // calc real root (what is virtual /) for this user // the real root is the longest common parent path of all real directories the user can access (at least read) // ACHTUNG: TODO Das Home-Dir in /etc/passwd muss fuer Local Access als auch fuer Access aus dem Internet gelten!!!!!!! // bzw der ftp muss das homedir hieraus berechnen und nicht aus /etc/passwd lesen! if (!root) return -1; while(root->next) root = root->next; // note that longer path with common parent are first in list, so we go to the last root_path_len = root->real_path_len; do { // check obs gemeinsamer parent ist struct acl_directory *a = ctx->acl; char *p; if (root_path_len == 1) break; // "/" passt immer while(a != root) { char c; if (a->real_path_len < root_path_len || memcmp(a->real_path, root->real_path, root_path_len)) break; // passt nicht c = a->real_path[root_path_len]; if (c != '/' && c != '\0') break; // passt nicht a = a->next; } if (a == root) break; // passt! // shorten root - go up one dir if (root_path_len == strlen(AVM_ACL_COMMON_REAL_PREFIX)) break; // cant shorten more! p = root->real_path + root_path_len - 1; while(*p != '/' && p > root->real_path) p--; if (p == root->real_path) { root_path_len = 1; // '/' - should not happen break; } root_path_len = p - root->real_path; } while(1); ctx->real_root_len = root_path_len; ctx->real_root = malloc(root_path_len+1); if (!ctx->real_root) return -1; memcpy(ctx->real_root, root->real_path, root_path_len); ctx->real_root[root_path_len] = '\0'; Log(("calc_real_root: real_root is %s.", ctx->real_root)); return 0; } /** * Cleanup context after usage. Needed to free all internal resources. * * @param ppctx */ static void avm_acl_free_context(struct avm_acl_context **ppctx) { if (ppctx && *ppctx) { acl_unset_user_context((*ppctx)); free((*ppctx)->username); free(*ppctx); *ppctx = NULL; } } static int avm_acl_reset_context(struct avm_acl_context *ctx, const char *username, int access_from_internet) { char *old = NULL; if (!ctx) { return -1; } acl_unset_user_context(ctx); if (username) { if (ReadUsersACLFileByFriendlyName(ctx, username, access_from_internet)) { // no access at all goto err; } // parameter username may be the same pointer as ctx->username old = ctx->username; ctx->username = strdup(username); free(old); if (!ctx->username) { goto err; } ctx->uid = 0; ctx->logged_in = 1; } else { ctx->uid = 0; ctx->logged_in = 0; } ctx->access_from_internet = access_from_internet ? 1 : 0; return 0; err: return -1; } static int avm_acl_reset_context_uid(struct avm_acl_context *ctx, unsigned uid, int access_from_internet) { if (!ctx) { return -1; } if (uid <= ACL_USER_ID_OFFSET) { return -1; } acl_unset_user_context(ctx); if (ReadUsersACLFileByID(ctx, uid - ACL_USER_ID_OFFSET, access_from_internet, 1/*allow write access*/)) { // no access at all goto err; } free(ctx->username); ctx->username = NULL; ctx->uid = uid; ctx->access_from_internet = access_from_internet ? 1 : 0; ctx->logged_in = 1; /* keep ctx->is_samba */ return 0; err: return -1; } /** * Create a new context by user name. * * @param mode access mode needed to apply special handling for SMB access * * @return context or NULL on failure. */ static struct avm_acl_context* avm_acl_create_context(enum avm_acl_mode mode) { struct avm_acl_context *ctx = NULL; ctx = (struct avm_acl_context*)calloc(1, sizeof(struct avm_acl_context)); if (!ctx) { goto err; } if (0 != avm_acl_set_mode(ctx, mode)) { goto err; } return ctx; err: avm_acl_free_context(&ctx); return NULL; } /** * Change the status of the context by logging in with an user name. * On failure NULL is returned and the context was freed. * * @param ctx context * @param username username * @param access_from_internet LAN access if 0 else WAN access * * @return context or NULL on failure. */ static struct avm_acl_context* avm_acl_login(struct avm_acl_context *ctx, const char *username, int access_from_internet) { if (!ctx) { return NULL; } if (avm_acl_reset_context(ctx, username, access_from_internet)) { goto err; } return ctx; err: avm_acl_free_context(&ctx); return ctx; } int acl_set_user_context(char *username, int access_from_internet) { Log(("acl_set_user_context username %s access_from_internet:%d", username ? username : "", access_from_internet)); Log(("sizeof(off_t)=%u sizeof(struct stat)=%u _FILE_OFFSET_BITS=%u", sizeof(off_t), sizeof(struct stat), _FILE_OFFSET_BITS)); if (avm_acl_reset_context(&glob, username, access_from_internet)) { // no access at all goto bad; } #if 0 // TEST // laengere pfade mit gleichen Parent-Pfaden vorher anfuegen! // add_virt_path(&glob, "/JetFlash-TS8GJF160-01/readonly", 0/* READ ONLY */); // add_virt_path(&glob, "/JetFlash-TS8GJF160-01", 1/* write access to everything */); #endif return 0; bad: acl_unset_user_context(&glob); Log(("acl_set_user_context failed")); return -1; } /** * Change the status of the context by logging in with an user ID. * On failure NULL is returned and the context was freed. * * @param ctx context * @param uid user ID * @param access_from_internet LAN access if 0 else WAN access * * @return context or NULL on failure. */ struct avm_acl_context* avm_acl_login_uid(struct avm_acl_context *ctx, unsigned uid, int access_from_internet) { if (uid <= ACL_USER_ID_OFFSET) { goto err; } if (avm_acl_reset_context_uid(ctx, uid, access_from_internet)) { // no access at all goto err; } return ctx; err: avm_acl_free_context(&ctx); return ctx; } int acl_set_user_context_uid(unsigned uid, int access_from_internet) { Log(("acl_set_user_context_uid uid:%u access_from_internet:%d", uid, access_from_internet)); acl_unset_user_context(&glob); if (uid <= ACL_USER_ID_OFFSET) return -1; if (avm_acl_reset_context_uid(&glob, uid, access_from_internet)) { // no access at all goto bad; } return 0; bad: acl_unset_user_context(&glob); Log(("acl_set_user_context_uid failed")); return -1; } int acl_set_full_access_user_context(void) { Log(("acl_set_full_user_context()")); acl_unset_user_context(&glob); add_virt_path(&glob, "/", 1/* write access to everything */); glob.logged_in = 1; return 0; } /** * Return real root directory path. * * @param ctx context * * @return path or NULL on failure */ static char *avm_acl_get_common_real_dir(struct avm_acl_context *ctx) { if (!ctx) { return NULL; } if (!ctx->acl) { return NULL; } if (!ctx->real_root) { if (calc_real_root(ctx)) { return NULL; // failed } } return ctx->real_root; } char *acl_get_user_common_real_dir(void) { Log(("acl_get_user_common_real_dir")); return avm_acl_get_common_real_dir(&glob); } static struct acl_string_list *avm_acl_get_all_real_paths_with_read_access(struct avm_acl_context *ctx) { struct acl_string_list *head = 0; struct acl_string_list *tail = 0; if (!ctx) { return NULL; } struct acl_directory *a; for (a = ctx->acl; a && a->real_path; a = a->next) { struct acl_string_list *sl = (struct acl_string_list *)malloc(sizeof(struct acl_string_list)); if (!sl) break; sl->s = strdup(a->real_path); if (!sl->s) { free(sl); break; } sl->next = 0; if (!head) { head = sl; } else { tail->next = sl; } tail = sl; } return head; } struct acl_string_list *acl_get_user_all_real_paths_with_read_access(void) { struct acl_string_list *head = NULL; head = avm_acl_get_all_real_paths_with_read_access(&glob); return head; } struct acl_string_list *avm_acl_user_get_all_real_paths_with_read_access_uid(enum avm_acl_mode mode, unsigned uid, int access_from_internet) { struct avm_acl_context *ctx = NULL; struct acl_string_list *list = NULL; ctx = avm_acl_create_context(mode); if (!ctx) { goto end; } ctx = avm_acl_login_uid(ctx, uid, access_from_internet); if (!ctx) { goto end; } list = avm_acl_get_all_real_paths_with_read_access(ctx); end: avm_acl_free_context(&ctx); return list; } struct acl_string_list *avm_acl_user_get_all_real_paths_with_read_access(enum avm_acl_mode mode, const char *username, int access_from_internet) { struct avm_acl_context *ctx = NULL; struct acl_string_list *list = NULL; ctx = avm_acl_create_context(mode); if (!ctx) { goto end; } ctx = avm_acl_login(ctx, username, access_from_internet); if (!ctx) { goto end; } list = avm_acl_get_all_real_paths_with_read_access(ctx); end: avm_acl_free_context(&ctx); return list; } void avm_acl_string_list_free(struct acl_string_list **pp) { struct acl_string_list *p; while (*pp) { p = *pp; *pp = p->next; free(p->s); free(p); } } /** * Switch context mode. * Used to enable SMB mode on already created context. * * @param ctx * @param mode * * @return 0 on success */ static int avm_acl_set_mode(struct avm_acl_context *ctx, enum avm_acl_mode mode) { if (!ctx) { return -1; } switch (mode) { case avm_acl_mode_unknown: goto err; case avm_acl_mode_smb: ctx->is_samba = 1; break; case avm_acl_mode_ftp: case avm_acl_mode_http: ctx->is_samba = 0; break; } return 0; err: return -1; } void acl_set_samba_mode(void) { Log(("acl_set_samba_mode")); avm_acl_set_mode(&glob, avm_acl_mode_smb); } void acl_reconfig(void) { InternalMemoryActiveForNetwork(1/*reset*/); glob.reconfig = 1; } int acl_force_secured_internet_access(void) { enum eToken t; int secured = 0; struct UserACLReader *r = UserACLFile_ReadOpen(); if (!r) return 0; t = UserACLFile_GetToken(r); while(t != TOKEN_EOF && t != TOKEN_USER) { if (t == TOKEN_INTERNET_SECURED) { secured = 1; break; } t = UserACLFile_GetToken(r); } UserACLFile_ReadClose(r); return secured; }