static int cauth_scanfile(const char *fname, int extinfo, struct thrarg *tharg) { struct cb_context context; const char *virname; int ret = 0, fd; context.filename = fname; context.virsize = 0; fd = open(fname, O_RDONLY); if(fd == -1) return -1; if(cl_scandesc_callback(fd, &virname, NULL, tharg->engine, tharg->options, &context) == CL_VIRUS) { if(context.virsize) detstats_add(virname, fname, context.virsize, context.virhash); if(extinfo && context.virsize) logg("ClamAuth: %s: %s(%s:%llu) FOUND\n", fname, virname, context.virhash, context.virsize); else logg("ClamAuth: %s: %s FOUND\n", fname, virname); virusaction(fname, virname, tharg->opts); } close(fd); return ret; }
static int fan_scanfile(int fan_fd, const char *fname, struct fanotify_event_metadata *fmd, int scan, int extinfo, struct thrarg *tharg) { struct fanotify_response res; struct cb_context context; const char *virname; int ret = 0; res.fd = fmd->fd; res.response = FAN_ALLOW; context.filename = fname; context.virsize = 0; if(scan && cl_scandesc_callback(fmd->fd, &virname, NULL, tharg->engine, tharg->options, &context) == CL_VIRUS) { if(context.virsize) detstats_add(virname, fname, context.virsize, context.virhash); if(extinfo && context.virsize) logg("ScanOnAccess: %s: %s(%s:%llu) FOUND\n", fname, virname, context.virhash, context.virsize); else logg("ScanOnAccess: %s: %s FOUND\n", fname, virname); virusaction(fname, virname, tharg->opts); res.response = FAN_DENY; } if(fmd->mask & FAN_ALL_PERM_EVENTS) { ret = write(fan_fd, &res, sizeof(res)); if(ret == -1) logg("!ScanOnAccess: Internal error (can't write to fanotify)\n"); } return ret; }
int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, char term) { int ret, sockfd, acceptd; int tmpd, bread, retval, firsttimeout, timeout, btread; unsigned int port = 0, portscan, min_port, max_port; unsigned long int quota = 0, maxsize = 0; short bound = 0; const char *virname; char buff[FILEBUFF]; char peer_addr[32]; struct cb_context context; struct sockaddr_in server; struct sockaddr_in peer; socklen_t addrlen; char *tmpname; min_port = optget(opts, "StreamMinPort")->numarg; max_port = optget(opts, "StreamMaxPort")->numarg; /* search for a free port to bind to */ port = cli_rndnum(max_port - min_port); bound = 0; for (portscan = 0; portscan < 1000; portscan++) { port = (port - 1) % (max_port - min_port + 1); memset((char *) &server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(min_port + port); server.sin_addr.s_addr = htonl(INADDR_ANY); if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) continue; if(bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) == -1) closesocket(sockfd); else { bound = 1; break; } } port += min_port; timeout = optget(opts, "ReadTimeout")->numarg; firsttimeout = optget(opts, "CommandReadTimeout")->numarg; if(!bound) { logg("!ScanStream: Can't find any free port.\n"); mdprintf(odesc, "Can't find any free port. ERROR%c", term); return -1; } else { listen(sockfd, 1); if(mdprintf(odesc, "PORT %u%c", port, term) <= 0) { logg("!ScanStream: error transmitting port.\n"); closesocket(sockfd); return -1; } } retval = poll_fd(sockfd, firsttimeout, 0); if (!retval || retval == -1) { const char *reason = !retval ? "timeout" : "poll"; mdprintf(odesc, "Accept %s. ERROR%c", reason, term); logg("!ScanStream %u: accept %s.\n", port, reason); closesocket(sockfd); return -1; } addrlen = sizeof(peer); if((acceptd = accept(sockfd, (struct sockaddr *) &peer, &addrlen)) == -1) { closesocket(sockfd); mdprintf(odesc, "accept() ERROR%c", term); logg("!ScanStream %u: accept() failed.\n", port); return -1; } *peer_addr = '\0'; inet_ntop(peer.sin_family, &peer.sin_addr, peer_addr, sizeof(peer_addr)); logg("*Accepted connection from %s on port %u, fd %d\n", peer_addr, port, acceptd); if(cli_gentempfd(optget(opts, "TemporaryDirectory")->strarg, &tmpname, &tmpd)) { shutdown(sockfd, 2); closesocket(sockfd); closesocket(acceptd); mdprintf(odesc, "cli_gentempfd() failed. ERROR%c", term); logg("!ScanStream(%s@%u): Can't create temporary file.\n", peer_addr, port); return -1; } quota = maxsize = optget(opts, "StreamMaxLength")->numarg; while((retval = poll_fd(acceptd, timeout, 0)) == 1) { /* only read up to max */ btread = (maxsize && (quota < sizeof(buff))) ? quota : sizeof(buff); if (!btread) { logg("^ScanStream(%s@%u): Size limit reached (max: %lu)\n", peer_addr, port, maxsize); break; /* Scan what we have */ } bread = recv(acceptd, buff, btread, 0); if(bread <= 0) break; quota -= bread; if(writen(tmpd, buff, bread) != bread) { shutdown(sockfd, 2); closesocket(sockfd); closesocket(acceptd); mdprintf(odesc, "Temporary file -> write ERROR%c", term); logg("!ScanStream(%s@%u): Can't write to temporary file.\n", peer_addr, port); close(tmpd); if(!optget(opts, "LeaveTemporaryFiles")->enabled) unlink(tmpname); free(tmpname); return -1; } } switch(retval) { case 0: /* timeout */ mdprintf(odesc, "read timeout ERROR%c", term); logg("!ScanStream(%s@%u): read timeout.\n", peer_addr, port); break; case -1: mdprintf(odesc, "read poll ERROR%c", term); logg("!ScanStream(%s@%u): read poll failed.\n", peer_addr, port); break; } if(retval == 1) { lseek(tmpd, 0, SEEK_SET); thrmgr_setactivetask(peer_addr, NULL); context.filename = peer_addr; context.virsize = 0; ret = cl_scandesc_callback(tmpd, &virname, scanned, engine, options, &context); thrmgr_setactivetask(NULL, NULL); } else { ret = -1; } close(tmpd); if(!optget(opts, "LeaveTemporaryFiles")->enabled) unlink(tmpname); free(tmpname); closesocket(acceptd); closesocket(sockfd); if(ret == CL_VIRUS) { if(context.virsize) detstats_add(virname, "NOFNAME", context.virsize, context.virhash); if(context.virsize && optget(opts, "ExtendedDetectionInfo")->enabled) { mdprintf(odesc, "stream: %s(%s:%llu) FOUND%c", virname, context.virhash, context.virsize, term); logg("stream(%s@%u): %s(%s:%llu) FOUND\n", peer_addr, port, virname, context.virhash, context.virsize); } else { mdprintf(odesc, "stream: %s FOUND%c", virname, term); logg("stream(%s@%u): %s FOUND\n", peer_addr, port, virname); } virusaction("stream", virname, opts); } else if(ret != CL_CLEAN) { if(retval == 1) { mdprintf(odesc, "stream: %s ERROR%c", cl_strerror(ret), term); logg("stream(%s@%u): %s ERROR\n", peer_addr, port, cl_strerror(ret)); } } else { mdprintf(odesc, "stream: OK%c", term); if(logok) logg("stream(%s@%u): OK\n", peer_addr, port); } return ret; }
int scanfd(const client_conn_t *conn, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int odesc, int stream) { int ret, fd = conn->scanfd; const char *virname; STATBUF statbuf; struct cb_context context; char fdstr[32]; const char*reply_fdstr; if (stream) { struct sockaddr_in sa; socklen_t salen = sizeof(sa); if(getpeername(conn->sd, (struct sockaddr *)&sa, &salen) || salen > sizeof(sa) || sa.sin_family != AF_INET) strncpy(fdstr, "instream(local)", sizeof(fdstr)); else snprintf(fdstr, sizeof(fdstr), "instream(%s@%u)", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port)); reply_fdstr = "stream"; } else { snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd); reply_fdstr = fdstr; } if(FSTAT(fd, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) { logg("%s: Not a regular file. ERROR\n", fdstr); if (conn_reply(conn, reply_fdstr, "Not a regular file", "ERROR") == -1) return CL_ETIMEOUT; return -1; } thrmgr_setactivetask(fdstr, NULL); context.filename = fdstr; context.virsize = 0; ret = cl_scandesc_callback(fd, &virname, scanned, engine, options, &context); thrmgr_setactivetask(NULL, NULL); if (thrmgr_group_need_terminate(conn->group)) { logg("*Client disconnected while scanjob was active\n"); return ret == CL_ETIMEOUT ? ret : CL_BREAK; } if(ret == CL_VIRUS) { if (conn_reply_virus(conn, reply_fdstr, virname) == -1) ret = CL_ETIMEOUT; if(context.virsize) detstats_add(virname, "NOFNAME", context.virsize, context.virhash); if(context.virsize && optget(opts, "ExtendedDetectionInfo")->enabled) logg("%s: %s(%s:%llu) FOUND\n", fdstr, virname, context.virhash, context.virsize); else logg("%s: %s FOUND\n", fdstr, virname); virusaction(reply_fdstr, virname, opts); } else if(ret != CL_CLEAN) { if (conn_reply(conn, reply_fdstr, cl_strerror(ret), "ERROR") == -1) ret = CL_ETIMEOUT; logg("%s: %s ERROR\n", fdstr, cl_strerror(ret)); } else { if (conn_reply_single(conn, reply_fdstr, "OK") == CL_ETIMEOUT) ret = CL_ETIMEOUT; if(logok) logg("%s: OK\n", fdstr); } return ret; }
int scan_callback(STATBUF *sb, char *filename, const char *msg, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) { struct scan_cb_data *scandata = data->data; const char *virname; const char **virpp = &virname; int ret; int type = scandata->type; struct cb_context context; /* detect disconnected socket, * this should NOT detect half-shutdown sockets (SHUT_WR) */ if (send(scandata->conn->sd, &ret, 0, 0) == -1 && errno != EINTR) { logg("$Client disconnected while command was active!\n"); thrmgr_group_terminate(scandata->conn->group); if (reason == visit_file) free(filename); return CL_BREAK; } if (thrmgr_group_need_terminate(scandata->conn->group)) { logg("^Client disconnected while scanjob was active\n"); if (reason == visit_file) free(filename); return CL_BREAK; } scandata->total++; switch (reason) { case error_mem: if (msg) logg("!Memory allocation failed during cli_ftw() on %s\n", msg); else logg("!Memory allocation failed during cli_ftw()\n"); scandata->errors++; return CL_EMEM; case error_stat: conn_reply_errno(scandata->conn, msg, "lstat() failed:"); logg("^lstat() failed on: %s\n", msg); scandata->errors++; return CL_SUCCESS; case warning_skipped_dir: logg("^Directory recursion limit reached, skipping %s\n", msg); return CL_SUCCESS; case warning_skipped_link: logg("$Skipping symlink: %s\n", msg); return CL_SUCCESS; case warning_skipped_special: if (msg == scandata->toplevel_path) conn_reply(scandata->conn, msg, "Not supported file type", "ERROR"); logg("*Not supported file type: %s\n", msg); return CL_SUCCESS; case visit_directory_toplev: return CL_SUCCESS; case visit_file: break; } /* check whether the file is excluded */ #ifdef C_LINUX if(procdev && sb && (sb->st_dev == procdev)) { free(filename); return CL_SUCCESS; } #endif if(sb && sb->st_size == 0) { /* empty file */ if (msg == scandata->toplevel_path) conn_reply_single(scandata->conn, filename, "Empty file"); free(filename); return CL_SUCCESS; } if (type == TYPE_MULTISCAN) { client_conn_t *client_conn = (client_conn_t *) calloc(1, sizeof(struct client_conn_tag)); if(client_conn) { client_conn->scanfd = -1; client_conn->sd = scandata->odesc; client_conn->filename = filename; client_conn->cmdtype = COMMAND_MULTISCANFILE; client_conn->term = scandata->conn->term; client_conn->options = scandata->options; client_conn->opts = scandata->opts; client_conn->group = scandata->group; if(cl_engine_addref(scandata->engine)) { logg("!cl_engine_addref() failed\n"); free(filename); free(client_conn); return CL_EMEM; } else { client_conn->engine = scandata->engine; pthread_mutex_lock(&reload_mutex); client_conn->engine_timestamp = reloaded_time; pthread_mutex_unlock(&reload_mutex); if(!thrmgr_group_dispatch(scandata->thr_pool, scandata->group, client_conn, 1)) { logg("!thread dispatch failed\n"); cl_engine_free(scandata->engine); free(filename); free(client_conn); return CL_EMEM; } } } else { logg("!Can't allocate memory for client_conn\n"); scandata->errors++; free(filename); return CL_EMEM; } return CL_SUCCESS; } if (access(filename, R_OK)) { if (conn_reply(scandata->conn, filename, "Access denied.", "ERROR") == -1) { free(filename); return CL_ETIMEOUT; } logg("*Access denied: %s\n", filename); scandata->errors++; free(filename); return CL_SUCCESS; } thrmgr_setactivetask(filename, NULL); context.filename = filename; context.virsize = 0; ret = cl_scanfile_callback(filename, virpp, &scandata->scanned, scandata->engine, scandata->options, &context); thrmgr_setactivetask(NULL, NULL); if (scandata->options & CL_SCAN_ALLMATCHES) { virpp = (const char **)*virpp; /* temp hack for scanall mode until api augmentation */ virname = virpp[0]; } if (thrmgr_group_need_terminate(scandata->conn->group)) { free(filename); if (ret == CL_VIRUS && scandata->options & CL_SCAN_ALLMATCHES) free((void *)virpp); logg("*Client disconnected while scanjob was active\n"); return ret == CL_ETIMEOUT ? ret : CL_BREAK; } if (ret == CL_VIRUS) { scandata->infected++; if (conn_reply_virus(scandata->conn, filename, virname) == -1) { free(filename); if (scandata->options & CL_SCAN_ALLMATCHES) free((void *)virpp); return CL_ETIMEOUT; } if (scandata->options & CL_SCAN_ALLMATCHES && virpp[1] != NULL) { int i = 1; while (NULL != virpp[i]) if (conn_reply_virus(scandata->conn, filename, virpp[i++]) == -1) { free(filename); free((void *)virpp); return CL_ETIMEOUT; } } if(context.virsize) detstats_add(virname, filename, context.virsize, context.virhash); if(context.virsize && optget(scandata->opts, "ExtendedDetectionInfo")->enabled) logg("~%s: %s(%s:%llu) FOUND\n", filename, virname, context.virhash, context.virsize); else logg("~%s: %s FOUND\n", filename, virname); virusaction(filename, virname, scandata->opts); if (scandata->options & CL_SCAN_ALLMATCHES && virpp[1] != NULL) { int i = 1; while (NULL != virpp[i]) logg("~%s: %s FOUND\n", filename, virpp[i++]); } } else if (ret != CL_CLEAN) { scandata->errors++; if (conn_reply(scandata->conn, filename, cl_strerror(ret), "ERROR") == -1) { free(filename); return CL_ETIMEOUT; } logg("~%s: %s ERROR\n", filename, cl_strerror(ret)); } else if (logok) { logg("~%s: OK\n", filename); } free(filename); if (ret == CL_VIRUS && scandata->options & CL_SCAN_ALLMATCHES) free((void *)virpp); if(ret == CL_EMEM) /* stop scanning */ return ret; if (type == TYPE_SCAN) { /* virus -> break */ return ret; } /* keep scanning always */ return CL_SUCCESS; }
static void *clamukolegacyth(void *arg) { struct thrarg *tharg = (struct thrarg *) arg; sigset_t sigset; const char *virname; struct sigaction act; unsigned long mask = 0; const struct optstruct *pt; short int scan; int sizelimit = 0, extinfo; struct stat sb; struct cb_context context; clamuko_scanning = 0; /* ignore all signals except SIGUSR1 */ sigfillset(&sigset); sigdelset(&sigset, SIGUSR1); /* The behavior of a process is undefined after it ignores a * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */ sigdelset(&sigset, SIGFPE); sigdelset(&sigset, SIGILL); sigdelset(&sigset, SIGSEGV); #ifdef SIGBUS sigdelset(&sigset, SIGBUS); #endif pthread_sigmask(SIG_SETMASK, &sigset, NULL); memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = clamuko_exit; sigfillset(&(act.sa_mask)); sigaction(SIGUSR1, &act, NULL); sigaction(SIGSEGV, &act, NULL); /* register */ if(dazukoRegister("ClamAV", "r+")) { logg("!Clamuko: Can't register with Dazuko\n"); return NULL; } else logg("Clamuko: Correctly registered with Dazuko.\n"); /* access mask */ if(optget(tharg->opts, "ClamukoScanOnOpen")->enabled) { logg("Clamuko: Scan-on-open mode activated.\n"); mask |= DAZUKO_ON_OPEN; } if(optget(tharg->opts, "ClamukoScanOnClose")->enabled) { logg("Clamuko: Scan-on-close mode activated.\n"); mask |= DAZUKO_ON_CLOSE; } if(optget(tharg->opts, "ClamukoScanOnExec")->enabled) { logg("Clamuko: Scan-on-exec mode activated.\n"); mask |= DAZUKO_ON_EXEC; } if(!mask) { logg("!Access mask is not configured properly.\n"); dazukoUnregister(); return NULL; } if(dazukoSetAccessMask(mask)) { logg("!Clamuko: Can't set access mask in Dazuko.\n"); dazukoUnregister(); return NULL; } if((pt = optget(tharg->opts, "ClamukoIncludePath"))->enabled) { while(pt) { if((dazukoAddIncludePath(pt->strarg))) { logg("!Clamuko: Dazuko -> Can't include path %s\n", pt->strarg); dazukoUnregister(); return NULL; } else logg("Clamuko: Included path %s\n", pt->strarg); pt = (struct optstruct *) pt->nextarg; } } else { logg("!Clamuko: please include at least one path.\n"); dazukoUnregister(); return NULL; } if((pt = optget(tharg->opts, "ClamukoExcludePath"))->enabled) { while(pt) { if((dazukoAddExcludePath(pt->strarg))) { logg("!Clamuko: Dazuko -> Can't exclude path %s\n", pt->strarg); dazukoUnregister(); return NULL; } else logg("Clamuko: Excluded path %s\n", pt->strarg); pt = (struct optstruct *) pt->nextarg; } } sizelimit = optget(tharg->opts, "ClamukoMaxFileSize")->numarg; if(sizelimit) logg("Clamuko: Max file size limited to %d bytes.\n", sizelimit); else logg("Clamuko: File size limit disabled.\n"); extinfo = optget(tharg->opts, "ExtendedDetectionInfo")->enabled; while(1) { if(dazukoGetAccess(&acc) == 0) { clamuko_scanning = 1; scan = 1; if(sizelimit) { stat(acc->filename, &sb); if(sb.st_size > sizelimit) { scan = 0; logg("*Clamuko: %s skipped (too big)\n", acc->filename); } } if(clamuko_checkowner(acc->pid, tharg->opts)) { scan = 0; logg("*Clamuko: %s skipped (excluded UID)\n", acc->filename); } context.filename = acc->filename; context.virsize = 0; if(scan && cl_scanfile_callback(acc->filename, &virname, NULL, tharg->engine, tharg->options, &context) == CL_VIRUS) { if(context.virsize) detstats_add(virname, acc->filename, context.virsize, context.virhash); if(extinfo && context.virsize) logg("Clamuko: %s: %s(%s:%llu) FOUND\n", acc->filename, virname, context.virhash, context.virsize); else logg("Clamuko: %s: %s FOUND\n", acc->filename, virname); virusaction(acc->filename, virname, tharg->opts); acc->deny = 1; } else acc->deny = 0; if(dazukoReturnAccess(&acc)) { logg("!Can't return access to Dazuko.\n"); logg("Clamuko stopped.\n"); dazukoUnregister(); clamuko_scanning = 0; return NULL; } clamuko_scanning = 0; } } /* can't be ;) */ return NULL; }