1 /*
2 * Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3 * Copyright (C) 2009-2013 Sourcefire, Inc.
4 *
5 * Authors: Tomasz Kojm, aCaB
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #if HAVE_CONFIG_H
23 #include "clamav-config.h"
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #ifdef HAVE_SYS_LIMITS_H
35 #include <sys/limits.h>
36 #endif
37 #ifdef HAVE_SYS_SELECT_H
38 #include <sys/select.h>
39 #endif
40 #ifndef _WIN32
41 #include <sys/socket.h>
42 #include <sys/un.h>
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
45 #include <netdb.h>
46 #include <utime.h>
47 #endif
48 #include <errno.h>
49 #include <dirent.h>
50 #include <fcntl.h>
51
52 #ifdef HAVE_SYS_UIO_H
53 #include <sys/uio.h>
54 #endif
55
56 // libclamav
57 #include "clamav.h"
58 #include "str.h"
59 #include "others.h"
60
61 // shared
62 #include "optparser.h"
63 #include "output.h"
64 #include "misc.h"
65 #include "actions.h"
66 #include "clamdcom.h"
67
68 #include "client.h"
69 #include "proto.h"
70
71 unsigned long int maxstream;
72 #ifndef _WIN32
73 struct sockaddr_un nixsock;
74 #endif
75 extern struct optstruct *clamdopts;
76
77 /* Inits the communication layer
78 * Returns 0 if clamd is local, non zero if clamd is remote */
isremote(const struct optstruct * opts)79 static int isremote(const struct optstruct *opts)
80 {
81 int s, ret;
82 const struct optstruct *opt;
83 char *ipaddr, port[10];
84 struct addrinfo hints, *info, *p;
85 int res;
86
87 UNUSEDPARAM(opts);
88
89 #ifndef _WIN32
90 if ((opt = optget(clamdopts, "LocalSocket"))->enabled) {
91 memset((void *)&nixsock, 0, sizeof(nixsock));
92 nixsock.sun_family = AF_UNIX;
93 strncpy(nixsock.sun_path, opt->strarg, sizeof(nixsock.sun_path));
94 nixsock.sun_path[sizeof(nixsock.sun_path) - 1] = '\0';
95 return 0;
96 }
97 #endif
98 if (!(opt = optget(clamdopts, "TCPSocket"))->enabled)
99 return 0;
100
101 snprintf(port, sizeof(port), "%lld", optget(clamdopts, "TCPSocket")->numarg);
102
103 opt = optget(clamdopts, "TCPAddr");
104 while (opt) {
105 ipaddr = NULL;
106 if (opt->strarg)
107 ipaddr = (!strcmp(opt->strarg, "any") ? NULL : opt->strarg);
108
109 memset(&hints, 0x00, sizeof(struct addrinfo));
110 hints.ai_family = AF_UNSPEC;
111 hints.ai_socktype = SOCK_STREAM;
112 hints.ai_flags = AI_PASSIVE;
113
114 if ((res = getaddrinfo(ipaddr, port, &hints, &info))) {
115 logg("!Can't lookup clamd hostname: %s\n", gai_strerror(res));
116 opt = opt->nextarg;
117 continue;
118 }
119
120 for (p = info; p != NULL; p = p->ai_next) {
121 if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
122 logg("isremote: socket() returning: %s.\n", strerror(errno));
123 continue;
124 }
125
126 switch (p->ai_family) {
127 case AF_INET:
128 ((struct sockaddr_in *)(p->ai_addr))->sin_port = htons(INADDR_ANY);
129 break;
130 case AF_INET6:
131 ((struct sockaddr_in6 *)(p->ai_addr))->sin6_port = htons(INADDR_ANY);
132 break;
133 default:
134 break;
135 }
136
137 ret = bind(s, p->ai_addr, p->ai_addrlen);
138 if (ret) {
139 if (errno == EADDRINUSE) {
140 /*
141 * If we can't bind, then either we're attempting to listen on an IP that isn't
142 * ours or that clamd is already listening on.
143 */
144 closesocket(s);
145 freeaddrinfo(info);
146 return 0;
147 }
148
149 closesocket(s);
150 freeaddrinfo(info);
151 return 1;
152 }
153
154 closesocket(s);
155 }
156
157 freeaddrinfo(info);
158
159 opt = opt->nextarg;
160 }
161
162 return 0;
163 }
164
165 /* pings clamd at the specified interval the number of time specified
166 * return 0 on a succesful connection, 1 upon timeout, -1 on error */
ping_clamd(const struct optstruct * opts)167 int16_t ping_clamd(const struct optstruct *opts)
168 {
169
170 uint64_t attempts = 0;
171 uint64_t interval = 0;
172 char *attempt_str = NULL;
173 char *interval_str = NULL;
174 char *errchk = NULL;
175 uint64_t i = 0;
176 const struct optstruct *opt = NULL;
177 int64_t sockd;
178 struct RCVLN rcv;
179 uint16_t ret = 0;
180
181 if (opts == NULL) {
182 logg("!null parameter was passed\n");
183 ret = -1;
184 goto done;
185 }
186
187 /* ping command takes the form --ping [attempts[:interval]] */
188 if (NULL != (opt = optget(opts, "ping"))) {
189 if (NULL != opt->strarg) {
190 if (NULL == (attempt_str = cli_strdup(opt->strarg))) {
191 logg("!could not allocate memory for string\n");
192 ret = -1;
193 goto done;
194 }
195 interval_str = strchr(attempt_str, ':');
196 if ((NULL != interval_str) && (interval_str[0] != '\0')) {
197 interval_str[0] = '\0';
198 interval_str++;
199 interval = cli_strntoul(interval_str, strlen(interval_str), &errchk, 10);
200 if (interval_str + strlen(interval_str) > errchk) {
201 logg("^interval_str would go past end of buffer\n");
202 ret = -1;
203 goto done;
204 }
205 } else {
206 interval = CLAMDSCAN_DEFAULT_PING_INTERVAL;
207 }
208 attempts = cli_strntoul(attempt_str, strlen(attempt_str), &errchk, 10);
209 if (attempt_str + strlen(attempt_str) > errchk) {
210 logg("^attmept_str would go past end of buffer\n");
211 ret = -1;
212 goto done;
213 }
214 } else {
215 attempts = CLAMDSCAN_DEFAULT_PING_ATTEMPTS;
216 interval = CLAMDSCAN_DEFAULT_PING_INTERVAL;
217 }
218 }
219
220 isremote(opts);
221 do {
222 if ((sockd = dconnect()) >= 0) {
223 recvlninit(&rcv, sockd);
224
225 if (sendln(sockd, "zPING", 5)) {
226 logg("*PING failed...\n");
227 closesocket(sockd);
228 } else {
229 if (!optget(opts, "wait")->enabled) {
230 logg("PONG\n");
231 }
232 ret = 0;
233 goto done;
234 }
235 }
236
237 if (i + 1 < attempts) {
238 if (optget(opts, "wait")->enabled) {
239 if (interval == 1)
240 logg("*Could not connect, will try again in %lu second\n", interval);
241 else
242 logg("*Could not connect, will try again in %lu seconds\n", interval);
243 } else {
244 if (interval == 1)
245 logg("Could not connect, will PING again in %lu second\n", interval);
246 else
247 logg("Could not connect, will PING again in %lu seconds\n", interval);
248 }
249 sleep(interval);
250 }
251 i++;
252 } while (i < attempts);
253
254 /* timed out */
255 ret = 1;
256 if (optget(opts, "wait")->enabled) {
257 logg("Wait timeout exceeded; Could not connect to clamd\n");
258 } else {
259 logg("PING timeout exceeded; No response from clamd\n");
260 }
261
262 done:
263 if (attempt_str) {
264 free(attempt_str);
265 }
266 attempt_str = NULL;
267 interval_str = NULL;
268 errchk = NULL;
269
270 return ret;
271 }
272
273 /* Turns a relative path into an absolute one
274 * Returns a pointer to the path (which must be
275 * freed by the caller) or NULL on error */
makeabs(const char * basepath)276 static char *makeabs(const char *basepath)
277 {
278 int namelen;
279 char *ret;
280
281 if (!(ret = malloc(PATH_MAX + 1))) {
282 logg("^Can't make room for fullpath.\n");
283 return NULL;
284 }
285 if (!cli_is_abspath(basepath)) {
286 if (!getcwd(ret, PATH_MAX)) {
287 logg("^Can't get absolute pathname of current working directory.\n");
288 free(ret);
289 return NULL;
290 }
291 #ifdef _WIN32
292 if (*basepath == '\\') {
293 namelen = 2;
294 basepath++;
295 } else
296 #endif
297 namelen = strlen(ret);
298 snprintf(&ret[namelen], PATH_MAX - namelen, PATHSEP "%s", basepath);
299 } else {
300 strncpy(ret, basepath, PATH_MAX);
301 }
302 ret[PATH_MAX] = '\0';
303 return ret;
304 }
305
306 /* Recursively scans a path with the given scantype
307 * Returns non zero for serious errors, zero otherwise */
client_scan(const char * file,int scantype,int * infected,int * err,int maxlevel,int session,int flags)308 static int client_scan(const char *file, int scantype, int *infected, int *err, int maxlevel, int session, int flags)
309 {
310 int ret;
311 char *real_path = NULL;
312 char *fullpath = NULL;
313
314 /* Convert relative path to fullpath */
315 fullpath = makeabs(file);
316
317 /* Convert fullpath to the real path (evaluating symlinks and . and ..).
318 Doing this early on will ensure that the scan results will appear consistent
319 across regular scans, --fdpass scans, and --stream scans. */
320 if (CL_SUCCESS != cli_realpath(fullpath, &real_path)) {
321 logg("*client_scan: Failed to determine real filename of %s.\n", fullpath);
322 } else {
323 free(fullpath);
324 fullpath = real_path;
325 }
326
327 if (!fullpath)
328 return 0;
329 if (!session)
330 ret = serial_client_scan(fullpath, scantype, infected, err, maxlevel, flags);
331 else
332 ret = parallel_client_scan(fullpath, scantype, infected, err, maxlevel, flags);
333 free(fullpath);
334 return ret;
335 }
336
get_clamd_version(const struct optstruct * opts)337 int get_clamd_version(const struct optstruct *opts)
338 {
339 char *buff;
340 int len, sockd;
341 struct RCVLN rcv;
342
343 isremote(opts);
344 if ((sockd = dconnect()) < 0) return 2;
345 recvlninit(&rcv, sockd);
346
347 if (sendln(sockd, "zVERSION", 9)) {
348 closesocket(sockd);
349 return 2;
350 }
351
352 while ((len = recvln(&rcv, &buff, NULL))) {
353 if (len == -1) {
354 logg("!Error occurred while receiving version information.\n");
355 break;
356 }
357 printf("%s\n", buff);
358 }
359
360 closesocket(sockd);
361 return 0;
362 }
363
reload_clamd_database(const struct optstruct * opts)364 int reload_clamd_database(const struct optstruct *opts)
365 {
366 char *buff;
367 int len, sockd;
368 struct RCVLN rcv;
369
370 isremote(opts);
371 if ((sockd = dconnect()) < 0) return 2;
372 recvlninit(&rcv, sockd);
373
374 if (sendln(sockd, "zRELOAD", 8)) {
375 closesocket(sockd);
376 return 2;
377 }
378
379 if (!(len = recvln(&rcv, &buff, NULL)) || len < 10 || memcmp(buff, "RELOADING", 9)) {
380 logg("!Clamd did not reload the database\n");
381 closesocket(sockd);
382 return 2;
383 }
384 closesocket(sockd);
385 return 0;
386 }
387
client(const struct optstruct * opts,int * infected,int * err)388 int client(const struct optstruct *opts, int *infected, int *err)
389 {
390 int remote, scantype, session = 0, errors = 0, scandash = 0, maxrec, flags = 0;
391 const char *fname;
392
393 if (optget(opts, "wait")->enabled) {
394 int16_t ping_result = ping_clamd(opts);
395 switch (ping_result) {
396 case 0:
397 break;
398 case 1:
399 return (int)CL_ETIMEOUT;
400 default:
401 return (int)CL_ERROR;
402 }
403 }
404
405 scandash = (opts->filename && opts->filename[0] && !strcmp(opts->filename[0], "-") && !optget(opts, "file-list")->enabled && !opts->filename[1]);
406 remote = isremote(opts) | optget(opts, "stream")->enabled;
407 #ifdef HAVE_FD_PASSING
408 if (!remote && optget(clamdopts, "LocalSocket")->enabled && (optget(opts, "fdpass")->enabled || scandash)) {
409 scantype = FILDES;
410 session = optget(opts, "multiscan")->enabled;
411 } else
412 #endif
413 if (remote || scandash) {
414 scantype = STREAM;
415 session = optget(opts, "multiscan")->enabled;
416 } else if (optget(opts, "multiscan")->enabled)
417 scantype = MULTI;
418 else if (optget(opts, "allmatch")->enabled)
419 scantype = ALLMATCH;
420 else
421 scantype = CONT;
422
423 maxrec = optget(clamdopts, "MaxDirectoryRecursion")->numarg;
424 maxstream = optget(clamdopts, "StreamMaxLength")->numarg;
425 if (optget(clamdopts, "FollowDirectorySymlinks")->enabled)
426 flags |= CLI_FTW_FOLLOW_DIR_SYMLINK;
427 if (optget(clamdopts, "FollowFileSymlinks")->enabled)
428 flags |= CLI_FTW_FOLLOW_FILE_SYMLINK;
429 flags |= CLI_FTW_TRIM_SLASHES;
430
431 *infected = 0;
432
433 if (scandash) {
434 int sockd, ret;
435 STATBUF sb;
436 if (FSTAT(0, &sb) < 0) {
437 logg("client.c: fstat failed for file name \"%s\", with %s\n.",
438 opts->filename[0], strerror(errno));
439 return 2;
440 }
441 if ((sb.st_mode & S_IFMT) != S_IFREG) scantype = STREAM;
442 if ((sockd = dconnect()) >= 0 && (ret = dsresult(sockd, scantype, NULL, &ret, NULL)) >= 0)
443 *infected = ret;
444 else
445 errors = 1;
446 if (sockd >= 0) closesocket(sockd);
447 } else if (opts->filename || optget(opts, "file-list")->enabled) {
448 if (opts->filename && optget(opts, "file-list")->enabled)
449 logg("^Only scanning files from --file-list (files passed at cmdline are ignored)\n");
450
451 while ((fname = filelist(opts, NULL))) {
452 if (!strcmp(fname, "-")) {
453 logg("!Scanning from standard input requires \"-\" to be the only file argument\n");
454 continue;
455 }
456 errors += client_scan(fname, scantype, infected, err, maxrec, session, flags);
457 /* this may be too strict
458 if(errors >= 10) {
459 logg("!Too many errors\n");
460 break;
461 }
462 */
463 }
464 } else {
465 errors = client_scan("", scantype, infected, err, maxrec, session, flags);
466 }
467 return *infected ? 1 : (errors ? 2 : 0);
468 }
469