/* * Copyright (C) 2015-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * * Authors: Mickey Sola * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #if HAVE_CONFIG_H #include "clamav-config.h" #endif #if defined(FANOTIFY) #include #include #include #include #include #include #include #include #include #include #include #include "onaccess_fan.h" #include "onaccess_hash.h" #include "onaccess_ddd.h" #include "libclamav/clamav.h" #include "libclamav/scanners.h" #include "libclamav/str.h" #include "shared/optparser.h" #include "shared/output.h" #include "server.h" #include "others.h" #include "scanner.h" #include "priv_fts.h" static struct onas_bucket *onas_bucket_init(); static void onas_free_bucket(struct onas_bucket *bckt); static int onas_bucket_insert(struct onas_bucket *bckt, struct onas_element *elem); static int onas_bucket_remove(struct onas_bucket *bckt, struct onas_element *elem); static int onas_add_hashnode_child(struct onas_hnode *node, const char* dirname); static struct onas_lnode *onas_listnode_init(void); static struct onas_hnode *onas_hashnode_init(void); static inline uint32_t onas_hshift(uint32_t hash) { hash = ~hash; hash += (hash << 15); hash ^= (hash >> 12); hash += (hash << 2); hash ^= (hash >> 4); hash += (hash << 3); hash += (hash << 11); hash ^= (hash >> 16); return hash; } static inline int onas_hash(const char* key, size_t keylen, uint32_t size) { uint32_t hash = 1; uint32_t i; for (i = 0; i < keylen; i++) { hash += key[i]; hash = onas_hshift(hash); } return hash & (size - 1); } int onas_ht_init(struct onas_ht **ht, uint32_t size) { if (size == 0 || (size & (~size + 1)) != size) return CL_EARG; *ht = (struct onas_ht *) cli_malloc(sizeof(struct onas_ht)); if (!(*ht)) return CL_EMEM; **ht = (struct onas_ht) { .htable = NULL, .size = size, .nbckts = 0, }; if (!((*ht)->htable = (struct onas_bucket **) cli_calloc(size, sizeof(struct onas_bucket *)))) { onas_free_ht(*ht); return CL_EMEM; } return CL_SUCCESS; } void onas_free_ht(struct onas_ht *ht) { if (!ht || ht->size == 0) return; if (!ht->htable) { free(ht); return; } uint32_t i = 0; for (i = 0; i < ht->size; i++) { onas_free_bucket(ht->htable[i]); ht->htable[i] = NULL; } free(ht->htable); ht->htable = NULL; free(ht); return; } static struct onas_bucket *onas_bucket_init() { struct onas_bucket *bckt = (struct onas_bucket*) cli_malloc(sizeof(struct onas_bucket)); if (!bckt) return NULL; *bckt = (struct onas_bucket) { .size = 0, .head = NULL, .tail = NULL }; return bckt; } static void onas_free_bucket(struct onas_bucket *bckt) { if (!bckt) return; uint32_t i = 0; struct onas_element *curr = NULL; for (i = 0; i < bckt->size; i++) { curr = bckt->head; bckt->head = curr->next; onas_free_element(curr); curr = NULL; } free(bckt); return; } struct onas_element *onas_element_init(struct onas_hnode *value, const char *key, size_t klen) { struct onas_element *elem = (struct onas_element *) cli_malloc(sizeof(struct onas_element)); if (!elem) return NULL; *elem = (struct onas_element) { .key = key, .klen = klen, .data = value, .next = NULL, .prev = NULL }; return elem; } void onas_free_element(struct onas_element *elem) { if (!elem) return; onas_free_hashnode(elem->data); elem->prev = NULL; elem->next = NULL; free(elem); return; } int onas_ht_insert(struct onas_ht *ht, struct onas_element *elem) { if (!ht || !elem || !elem->key) return CL_ENULLARG; int idx = onas_hash(elem->key, elem->klen, ht->size); struct onas_bucket *bckt = ht->htable[idx]; int ret = 0; uint32_t bsize = 0; if (bckt == NULL) { ht->htable[idx] = onas_bucket_init(); bckt = ht->htable[idx]; } bsize = bckt->size; ret = onas_bucket_insert(bckt, elem); if (ret == CL_SUCCESS) if (bsize < bckt->size) ht->nbckts++; return ret; } static int onas_bucket_insert(struct onas_bucket *bckt, struct onas_element *elem) { if (!bckt || !elem) return CL_ENULLARG; if (bckt->size == 0) { bckt->head = elem; bckt->tail = elem; elem->prev = NULL; elem->next = NULL; bckt->size++; } else { struct onas_element *btail = bckt->tail; btail->next = elem; elem->prev = btail; elem->next = NULL; bckt->tail = elem; bckt->size++; } return CL_SUCCESS; } /* Checks if key exists and optionally stores address to the element corresponding to the key within elem */ int onas_ht_get(struct onas_ht *ht, const char *key, size_t klen, struct onas_element **elem) { if (elem) *elem = NULL; if (!ht || !key || klen <= 0) return CL_ENULLARG; struct onas_bucket *bckt = ht->htable[onas_hash(key, klen, ht->size)]; if (!bckt || bckt->size == 0) return CL_EARG; struct onas_element *curr = bckt->head; while (curr && strcmp(curr->key, key)) { curr = curr->next; } if (!curr) return CL_EARG; if (elem) *elem = curr; return CL_SUCCESS; } /* Removes the element corresponding to key from the hashtable and optionally returns a pointer to the removed element. */ int onas_ht_remove(struct onas_ht *ht, const char* key, size_t klen, struct onas_element **relem) { if (!ht || !key || klen <= 0) return CL_ENULLARG; struct onas_bucket *bckt = ht->htable[onas_hash(key, klen, ht->size)]; if (!bckt) return CL_EARG; struct onas_element *elem = NULL; onas_ht_get(ht, key, klen, &elem); if (!elem) return CL_EARG; int ret = onas_bucket_remove(bckt, elem); if (relem) *relem = elem; return ret; } static int onas_bucket_remove(struct onas_bucket *bckt, struct onas_element *elem) { if (!bckt || !elem) return CL_ENULLARG; struct onas_element *curr = bckt->head; while (curr && curr != elem) { curr = curr->next; } if (!curr) return CL_EARG; if (bckt->head == elem) { bckt->head = elem->next; if (bckt->head) bckt->head->prev = NULL; elem->next = NULL; } else if (bckt->tail == elem) { bckt->tail = elem->prev; if (bckt->tail) bckt->tail->next = NULL; elem->prev = NULL; } else { struct onas_element *tmp = NULL; tmp = elem->prev; if (tmp) { tmp->next = elem->next; tmp = elem->next; tmp->prev = elem->prev; } elem->prev = NULL; elem->next = NULL; } bckt->size--; return CL_SUCCESS; } /* Dealing with hash nodes and list nodes */ /* Function to initialize hashnode. */ static struct onas_hnode *onas_hashnode_init(void) { struct onas_hnode *hnode = NULL; if(!(hnode = (struct onas_hnode *) cli_malloc(sizeof(struct onas_hnode)))) { return NULL; } *hnode = (struct onas_hnode) { .pathlen = 0, .pathname = NULL, .prnt_pathlen = 0, .prnt_pathname = NULL, .childhead = NULL, .childtail = NULL, .wd = 0, .watched = 0 }; if (!(hnode->childhead = (struct onas_lnode *) onas_listnode_init())) { onas_free_hashnode(hnode); return NULL; } if (!(hnode->childtail = (struct onas_lnode *) onas_listnode_init())) { onas_free_hashnode(hnode); return NULL; } hnode->childhead->next = (struct onas_lnode *) hnode->childtail; hnode->childtail->prev = (struct onas_lnode *) hnode->childhead; return hnode; } /* Function to initialize listnode. */ static struct onas_lnode *onas_listnode_init(void) { struct onas_lnode *lnode = NULL; if(!(lnode = (struct onas_lnode *) cli_malloc(sizeof(struct onas_lnode)))) { return NULL; } *lnode = (struct onas_lnode) { .dirname = NULL, .next = NULL, .prev = NULL }; return lnode; } /* Function to free hashnode. */ void onas_free_hashnode(struct onas_hnode *hnode) { if (!hnode) return; onas_free_dirlist(hnode->childhead); hnode->childhead = NULL; free(hnode->pathname); hnode->pathname = NULL; free(hnode->prnt_pathname); hnode->prnt_pathname = NULL; free(hnode); return; } /* Function to free list of listnodes. */ void onas_free_dirlist(struct onas_lnode *head) { if (!head) return; struct onas_lnode *curr = head; struct onas_lnode *tmp = curr; while(curr) { tmp = curr->next; onas_free_listnode(curr); curr = tmp; } return; } /* Function to free a listnode. */ void onas_free_listnode(struct onas_lnode *lnode) { if (!lnode) return; lnode->next = NULL; lnode->prev = NULL; free(lnode->dirname); lnode->dirname = NULL; free(lnode); return; } static int onas_add_hashnode_child(struct onas_hnode *node, const char* dirname) { if (!node || !dirname) return CL_ENULLARG; struct onas_lnode *child = onas_listnode_init(); if (!child) return CL_EMEM; size_t n = strlen(dirname); child->dirname = cli_strndup(dirname, n); onas_add_listnode(node->childtail, child); return CL_SUCCESS; } /* Function to add a dir_listnode to a list */ int onas_add_listnode(struct onas_lnode *tail, struct onas_lnode *node) { if (!tail || !node) return CL_ENULLARG; struct onas_lnode *tmp = tail->prev; tmp->next = node; node->prev = tail->prev; node->next = tail; tail->prev = node; return CL_SUCCESS; } /* Function to remove a listnode based on dirname. */ int onas_rm_listnode(struct onas_lnode *head, const char *dirname) { if (!dirname || !head) return CL_ENULLARG; struct onas_lnode *curr = head; size_t n = strlen(dirname); while ((curr = curr->next)) { if (!strncmp(curr->dirname, dirname, n)) { struct onas_lnode *tmp = curr->prev; tmp->next = curr->next; tmp = curr->next; tmp->prev = curr->prev; onas_free_listnode(curr); return CL_SUCCESS; } } return -1; } /*** Dealing with parent/child relationships in the table. ***/ /* Determines parent and returns a copy based on full pathname. */ inline static char *onas_get_parent(const char *pathname, size_t len) { if (!pathname || len <= 1) return NULL; int idx = len - 2; char *ret = NULL; while(idx >= 0 && pathname[idx] != '/') { idx--; } if (idx == 0) { idx++; } ret = cli_strndup(pathname, idx); if (!ret) { errno = ENOMEM; return NULL; } return ret; } /* Gets the index at which the name of directory begins from the full pathname. */ inline static int onas_get_dirname_idx(const char *pathname, size_t len) { if (!pathname || len <= 1) return -1; int idx = len - 2; while(idx >= 0 && pathname[idx] != '/') { idx--; } if (pathname[idx] == '/') return idx + 1; return idx; } /* Emancipates the specified child from the specified parent. */ int onas_ht_rm_child(struct onas_ht *ht, const char *prntpath, size_t prntlen, const char *childpath, size_t childlen) { if (!ht || !prntpath || prntlen <= 0 || !childpath || childlen <= 1) return CL_ENULLARG; struct onas_element *elem = NULL; struct onas_hnode *hnode = NULL; int idx = onas_get_dirname_idx(childpath, childlen); int ret = 0; if(idx <= 0) return CL_SUCCESS; if(onas_ht_get(ht, prntpath, prntlen, &elem) != CL_SUCCESS) return CL_EARG; hnode = elem->data; if ((ret = onas_rm_listnode(hnode->childhead, &(childpath[idx])))) return CL_EARG; return CL_SUCCESS; } /* The specified parent adds the specified child to its list. */ int onas_ht_add_child(struct onas_ht *ht, const char *prntpath, size_t prntlen, const char *childpath, size_t childlen) { if (!ht || !prntpath || prntlen <= 0 || !childpath || childlen <= 1) return CL_ENULLARG; struct onas_element *elem = NULL; struct onas_hnode *hnode = NULL; int idx = onas_get_dirname_idx(childpath, childlen); if(idx <= 0) return CL_SUCCESS; if(onas_ht_get(ht, prntpath, prntlen, &elem)) return CL_EARG; hnode = elem->data; return onas_add_hashnode_child(hnode, &(childpath[idx])); } /*** Dealing with hierarchy changes. ***/ /* Adds the hierarchy under pathname to the tree and allocates all necessary memory. */ int onas_ht_add_hierarchy(struct onas_ht *ht, const char *pathname) { if (!ht || !pathname) return CL_ENULLARG; FTS *ftsp = NULL; int ftspopts = FTS_PHYSICAL | FTS_XDEV; FTSENT *curr = NULL; FTSENT *childlist = NULL; size_t len = strlen(pathname); char *prnt = onas_get_parent(pathname, len); if (prnt) onas_ht_add_child(ht, prnt, strlen(prnt), pathname, len); free(prnt); char * const pathargv[] = { (char*) pathname, NULL }; if (!(ftsp = _priv_fts_open(pathargv, ftspopts, NULL))) { logg("!ScanOnAccess: Could not open '%s'\n", pathname); return CL_EARG; } while((curr = _priv_fts_read(ftsp))) { struct onas_hnode *hnode = NULL; /* May want to handle other options in the future. */ switch (curr->fts_info) { case FTS_D: hnode = onas_hashnode_init(); if (!hnode) return CL_EMEM; hnode->pathlen = curr->fts_pathlen; hnode->pathname = cli_strndup(curr->fts_path, hnode->pathlen); hnode->prnt_pathname = onas_get_parent(hnode->pathname, hnode->pathlen); if (hnode->prnt_pathname) hnode->prnt_pathlen = strlen(hnode->prnt_pathname); else hnode->prnt_pathlen = 0; break; default: continue; } if((childlist = _priv_fts_children(ftsp, 0))) { do { if (childlist->fts_info == FTS_D) { if(CL_EMEM == onas_add_hashnode_child(hnode, childlist->fts_name)) return CL_EMEM; } } while ((childlist = childlist->fts_link)); } struct onas_element *elem = onas_element_init(hnode, hnode->pathname, hnode->pathlen); if (!elem) return CL_EMEM; if (onas_ht_insert(ht, elem)) return -1; } _priv_fts_close(ftsp); return CL_SUCCESS; } /* Removes the underlying hierarchy from the tree and frees all associated memory. */ int onas_ht_rm_hierarchy(struct onas_ht *ht, const char* pathname, size_t len, int level) { if (!ht || !pathname || len <= 0) return CL_ENULLARG; struct onas_hnode *hnode = NULL; struct onas_element *elem = NULL; char *prntname = NULL; size_t prntlen = 0; if(onas_ht_get(ht, pathname, len, &elem)) return CL_EARG; hnode = elem->data; struct onas_lnode *curr = hnode->childhead; if(level == 0) { if(!(prntname = onas_get_parent(pathname, len))) return CL_EARG; prntlen = strlen(prntname); if(onas_ht_rm_child(ht, prntname, prntlen, pathname, len)) return CL_EARG; free(prntname); } while (curr->next != hnode->childtail) { curr = curr->next; size_t size = len + strlen(curr->dirname) + 2; char *child_path = (char *) cli_malloc(size); if (child_path == NULL) return CL_EMEM; if (hnode->pathname[len-1] == '/') snprintf(child_path, size, "%s%s", hnode->pathname, curr->dirname); else snprintf(child_path, size, "%s/%s", hnode->pathname, curr->dirname); onas_ht_rm_hierarchy(ht, child_path, size, level + 1); free(child_path); } onas_ht_remove(ht, pathname, len, NULL); onas_free_element(elem); return CL_SUCCESS; } #endif