source: trunk/src/yulectl.c@ 494

Last change on this file since 494 was 481, checked in by katerina, 9 years ago

Enhancements and fixes for tickets #374, #375, #376, #377, #378, and #379.

File size: 14.8 KB
RevLine 
[1]1/* SAMHAIN file system integrity testing */
2/* Copyright (C) 2003 Rainer Wichmann */
3/* */
4/* This program is free software; you can redistribute it */
5/* and/or modify */
6/* it under the terms of the GNU General Public License as */
7/* published by */
8/* the Free Software Foundation; either version 2 of the License, or */
9/* (at your option) any later version. */
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
19
20#include "config_xor.h"
21
22#include <stddef.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
[481]26#include <ctype.h>
[1]27#include <errno.h>
28
29#include <unistd.h>
30#include <fcntl.h>
31
32#include <sys/socket.h>
33#include <sys/un.h>
34
35#include <signal.h>
36#include <pwd.h>
37
38#if !defined(AF_FILE)
39#define AF_FILE AF_UNIX
40#endif
41
42#define SH_MAXMSG 209
43
[481]44#if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && \
45 !defined(HAVE_STRUCT_CMSGCRED) && !defined(HAVE_STRUCT_FCRED) && \
46 !(defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))
47#define SH_REQ_PASSWORD 1
48#endif
49
[1]50static int sock = -1;
51static char password[15] = "";
52static int verbose = 0;
53
54#ifdef SH_STEALTH
55char * globber(const char * string);
56#define _(string) globber(string)
57#define N_(string) string
58#else
59#define _(string) string
60#define N_(string) string
61#endif
62
63#ifdef SH_STEALTH
64#ifndef SH_MAX_GLOBS
65#define SH_MAX_GLOBS 32
66#endif
67char * globber(const char * str)
68{
69 register int i, j;
70 static int count = -1;
71 static char glob[SH_MAX_GLOBS][128];
72
73 ++count; if (count > (SH_MAX_GLOBS-1) ) count = 0;
74 j = strlen(str);
75 if (j > 127) j = 127;
76
77 for (i = 0; i < j; ++i)
78 {
79 if (str[i] != '\n' && str[i] != '\t')
80 glob[count][i] = str[i] ^ XOR_CODE;
81 else
82 glob[count][i] = str[i];
83 }
84 glob[count][j] = '\0';
85 return glob[count];
86}
87#endif
88
89#define CLIENT _("yulectl")
90
91
[481]92static int
93create_unix_socket ()
[1]94{
95 int sock;
96
97 /* Create the socket. */
98
99 sock = socket (PF_UNIX, SOCK_STREAM, 0);
100 if (sock < 0)
101 {
102 perror (_("ERROR: socket"));
103 return -1;
104 }
105
106 return sock;
107}
108
[481]109static void
[1]110termination_handler (int signum)
111{
112 /* Clean up. */
113 if (signum != 0)
114 {
115 if (verbose)
116 fprintf(stdout, _("# Terminated on signal %d\n"), signum);
117 }
[481]118 if (sock >= 0 )
119 close (sock);
[1]120 return;
121}
122
[481]123static char * safe_copy(char * to, const char * from, size_t size)
124{
125 if (to && from)
126 {
127 strncpy (to, from, size);
128 if (size > 0)
129 to[size-1] = '\0';
130 else
131 *to = '\0';
132 }
133 return to;
134}
135
[1]136
[481]137static int send_to_server (char * serversock, char * message)
[1]138{
139 struct sockaddr_un name;
140 int size;
141 int nbytes;
142
143 /* Initialize the server socket address.
144 */
145 name.sun_family = AF_UNIX;
146 strncpy (name.sun_path, serversock, sizeof(name.sun_path) - 1);
147 size = (offsetof (struct sockaddr_un, sun_path)
148 + strlen (name.sun_path) + 1);
149
150 nbytes = connect(sock, (struct sockaddr *) & name, size);
151 if (nbytes < 0)
152 {
153 perror (_("ERROR: connect"));
154 return -1;
155 }
156
[481]157 /* Send the data.
[1]158 */
159 nbytes = send (sock, message, strlen (message) + 1, 0);
160 if (nbytes < 0)
161 {
162 perror (_("ERROR: send"));
163 return -1;
164 }
165 return 0;
166}
167
168static int getline_from_server (int sock, char * buf, int size)
169{
170 int nbytes = 0;
171 int status = 0;
172 char * p = buf;
173
174 do {
175 status = read (sock, p, 1);
176 if (status <= 0)
177 {
178 buf[nbytes] = '\0';
179 return ((status == 0) ? nbytes : status);
180 }
181 else if (*p == '\0')
182 {
183 return nbytes;
184 }
185 ++nbytes; ++p;
186 } while (nbytes < size);
187 buf[size-1] = '\0';
188 return 0;
189}
190
[481]191static int recv_from_server (char * message)
[1]192{
193 int nbytes = 0;
194 char recvmsg[SH_MAXMSG];
195 int num = 0;
196 int good = -1;
197 char * p;
198
199 if (password[0] == '\0')
[481]200 p = message;
[1]201 else
[481]202 p = &message[strlen(password)+1];
[1]203
[481]204 if (0 == strncmp(p, _("PROBE"), 5) ||
205 0 == strncmp(p, _("LIST"), 4))
[1]206 {
207 do {
208 nbytes = getline_from_server (sock, recvmsg, SH_MAXMSG);
209 if (nbytes < 0)
210 {
211 if (errno == EAGAIN)
[481]212 return 0;
[1]213 else
214 {
215 perror (_("ERROR: recv"));
216 return -1;
217 }
218 }
219 else if (nbytes == 0)
[481]220 return 0;
221
[1]222 if (recvmsg[0] == 'E' && recvmsg[1] == 'N' && recvmsg[2] == 'D')
223 {
224 if (verbose && (num == 0))
[396]225 fprintf (stdout, "%s", _("# There are no pending commands.\n"));
[1]226 return 0;
227 }
228 ++num;
229 fprintf (stdout, _("%03d: %s\n"), num, recvmsg);
230 } while (nbytes >= 0);
231 }
232 else
233 {
234 nbytes = recv (sock, recvmsg, SH_MAXMSG, 0);
235 if (nbytes < 0)
236 {
237 perror (_("ERROR: recv"));
238 return -1;
239 }
240 }
241
242 /* Print a diagnostic message. */
243 if (password[0] == '\0')
[481]244 good = strcmp (message, recvmsg);
[1]245 else
[481]246 good = strcmp (&message[strlen(password)+1], recvmsg);
[1]247
248 if (0 != good)
249 {
[481]250 if (0 == strncmp(recvmsg, _("!E:"), 3))
251 {
252 fputs(recvmsg, stderr);
253 fputc('\n', stderr);
254 }
255 else
256 {
257 fputs (_("ERROR: Bounced message != original message.\n"), stderr);
258 }
[1]259 return -1;
260 }
261 else
262 {
263 if (verbose)
[396]264 fprintf (stdout, "%s", _("# Message received by server.\n"));
[1]265 }
266
267 return 0;
268}
269
[481]270static int check_uuid(const char * in)
[1]271{
[481]272 int i;
273 const char *cp;
274
275 if (!in || strlen(in) != 36)
276 return -1;
277 for (i=0, cp = in; i <= 36; i++,cp++) {
278 if ((i == 8) || (i == 13) || (i == 18) ||
279 (i == 23)) {
280 if (*cp == '-')
281 continue;
282 else
283 return -1;
284 }
285 if (i== 36)
286 if (*cp == 0)
287 continue;
288 if (!isxdigit(*cp))
289 return -1;
290 }
291 return 0;
292}
293
294static int check_command(const char * str)
295{
296 unsigned int i = 0;
297 char * commands[] = { N_("DELTA:"), N_("RELOAD"), N_("STOP"), N_("SCAN"),
298 N_("CANCEL"), N_("LISTALL"), N_("LIST"), N_("PROBE"), NULL };
299
300 while (commands[i])
301 {
302 size_t len = strlen(_(commands[i]));
303
304 if (0 == strncmp(_(commands[i]), str, len))
305 {
306 if (i == 0)
307 {
308 char * p = strchr(str, ':'); ++p;
309 if ( 0 == check_uuid(p) )
310 return 0;
311 }
312 else
313 {
314 if (len == strlen(str))
315 return 0;
316 }
317 }
318 ++i;
319 }
320
321 fprintf (stderr, _("ERROR: invalid command <%s>\n\n"), str);
322 return -1;
323}
324
325static void print_usage_and_exit(char * name, int exit_status)
326{
[1]327 printf(_("\nUsage : %s [-v][-s server_socket] -c command <client_hostname>\n\n"),
328 name);
329
[396]330 printf("%s", _("Purpose : send commands to the server via a socket,\n"));
331 printf("%s", _(" in particular commands that the server would\n"));
332 printf("%s", _(" transfer to the client <client_hostname> when\n"));
333 printf("%s", _(" this client connects to deliver a message.\n\n"));
334 printf("%s", _(" If password is required, it is read from\n"));
335 printf("%s", _(" $HOME/.yulectl_cred or taken from the environment\n"));
336 printf("%s", _(" variable YULECTL_PASSWORD (not recommended).\n\n"));
[1]337
[481]338 printf("%s", _("Commands: RELOAD reload configuration\n"));
339 printf("%s", _(" DELTA:<uuid> load delta database with given uuid\n"));
340 printf("%s", _(" STOP terminate\n"));
341 printf("%s", _(" SCAN initiate file system check\n"));
342 printf("%s", _(" CANCEL cancel pending command(s)\n"));
343 printf("%s", _(" LIST list queued commands\n"));
344 printf("%s", _(" LISTALL list queued and last sent commands\n"));
345 printf("%s", _(" PROBE probe all clients for necessity of reload\n"));
346 exit(exit_status);
[1]347}
348
[117]349char * rtrim(char * str)
350{
351 size_t len;
352
[481]353 if (!str) return str;
[117]354
355 len = strlen(str);
356 while (len > 0)
357 {
358 --len;
359 if (str[len] == '\n' || str[len] == '\r')
360 str[len] = '\0';
361 else
362 break;
363 }
364 return str;
365}
366
[481]367static int get_home(char * home, size_t size)
[1]368{
369 struct passwd * pwent;
370
371 pwent = getpwuid(geteuid());
372 if ((pwent == 0) || (pwent->pw_dir == NULL))
373 {
374 if (verbose)
375 fprintf (stderr, _("WARNING: no home directory for euid %ld\n"),
376 (long) geteuid());
377 if (NULL != getenv(_("HOME")))
378 {
[481]379 safe_copy(home, getenv(_("HOME")), size);
[1]380 }
381 else
382 {
383 fprintf (stderr, _("ERROR: no home directory for euid %ld (tried $HOME and password database).\n"), (long) geteuid());
[481]384 return -1;
[1]385 }
386 }
387 else
388 {
[481]389 safe_copy(home, pwent->pw_dir, size);
[1]390 }
[481]391 return 0;
392}
[1]393
[481]394static int get_passwd(char * message2, size_t size)
395{
396 char home[4096];
397 FILE * fp;
398 char * pw;
399
400 /* 1) Password from environment
401 */
402 pw = getenv(_("YULECTL_PASSWORD"));
403 if (pw && strlen(pw) < 15)
404 {
405 strcpy(password, pw);
406 strcpy(message2, password);
407 return 0;
408 }
409
410 /* 2) Password from $HOME/.yule_cred
411 */
412 if (get_home(home, sizeof(home)) < 0)
413 return -1;
414
[1]415 if ( (strlen(home) + strlen(_("/.yulectl_cred")) + 1) > 4096)
416 {
[396]417 fprintf (stderr, "%s", _("ERROR: path for $HOME is too long.\n"));
[481]418 return -1;
[1]419 }
420 strcat(home, _("/.yulectl_cred"));
421 fp = fopen(home, "r");
[200]422
[481]423#if defined(SH_REQ_PASSWORD)
[1]424 if (fp == NULL)
425 {
[200]426 if (errno == ENOENT) {
427 fprintf (stderr,
428 _("ERROR No password file (%s) exists\n"),
[1]429 home);
[200]430 }
431 else {
432 fprintf (stderr,
433 _("ERROR: Password file (%s) not accessible for euid %ld uid %ld\n"),
[1]434 home, (long)geteuid(), (long)getuid());
[200]435 }
[481]436 return -1;
[1]437 }
[200]438#else
439 if (fp == NULL)
[481]440 return 0;
[200]441#endif
442
[481]443 if (NULL == fgets(message2, size, fp))
[1]444 {
[396]445 fprintf (stderr,
[1]446 _("ERROR: empty or unreadable password file (%s).\n"),
447 home);
[481]448 return -1;
[1]449 }
[117]450
451 (void) rtrim(message2);
452
[22]453 if (strlen(message2) > 14)
[1]454 {
[396]455 fprintf (stderr, "%s",
[1]456 _("ERROR: Password too long (max. 14 characters).\n"));
[481]457 return -1;
[1]458 }
459 strcpy(password, message2);
[22]460 fclose(fp);
[117]461
[481]462 return 0;
463}
[1]464
[481]465static int fixup_message (char * message)
466{
467 char message_fixed[SH_MAXMSG] = { 0 };
468
469 if (get_passwd(message_fixed, sizeof(message_fixed)) < 0)
470 return -1;
471
472 if (strlen(message_fixed) > 0)
473 {
474 strcat(message_fixed, "@");
475
476 strncat(message_fixed, message, SH_MAXMSG - strlen(message_fixed) -1);
477 message_fixed[SH_MAXMSG-1] = '\0';
478 strcpy(message, message_fixed);
479 }
480 return 0;
[1]481}
482
[481]483static int fill_serversock(char * serversock, size_t size)
[1]484{
[481]485 int status;
[1]486
487#ifdef HAVE_VSNPRINTF
[481]488 status = snprintf(serversock, size, _("%s/%s.sock"),
[1]489 DEFAULT_PIDDIR, SH_INSTALL_NAME);
490#else
[481]491 if ((strlen(DEFAULT_PIDDIR) + strlen(SH_INSTALL_NAME) + 1 + 6) > size)
492 status = -1;
[1]493 else
[481]494 status = sprintf (serversock, _("%s/%s.sock"),
495 DEFAULT_PIDDIR, SH_INSTALL_NAME);
496#endif
497
498 if ((status < 0) || (status > (int)(size-1)))
[1]499 {
[481]500 fprintf(stderr, _("ERROR: Path too long (maximum %d): %s/%s.sock\n"),
501 (int) (size-1), DEFAULT_PIDDIR, SH_INSTALL_NAME);
502 return -1;
[1]503 }
[481]504 return 0;
505}
[1]506
[481]507static void checklen(char * command, char * str, size_t maxlen)
508{
509 if (strlen(str) > maxlen)
[1]510 {
[481]511 fprintf(stderr, _("ERROR: String too long (max %d): %s\n\n"),
512 (int) maxlen, str);
513 print_usage_and_exit (command, EXIT_FAILURE);
[1]514 }
[481]515 return;
516}
[1]517
[481]518static void checknull(char * command, char * str)
519{
520 if (str == NULL || str[0] == '\0') {
521 fprintf(stderr, "%s", _("ERROR: option with missing argument\n\n"));
522 print_usage_and_exit(command, EXIT_FAILURE);
523 }
524 return;
525}
526
527int
528main (int argc, char * argv[])
529{
530
531 char message[SH_MAXMSG] = "";
532 char serversock[256];
533 int status;
534 int num = 1;
535 int flag = 0;
536
537 if (fill_serversock(serversock, sizeof(serversock)) < 0)
538 return (EXIT_FAILURE);
539
540
[1]541 while (argc > 1 && argv[num][0] == '-')
542 {
543 switch (argv[num][1])
544 {
545 case 'h':
[481]546 print_usage_and_exit(argv[0], EXIT_SUCCESS);
547 break;
[1]548 case 'v':
549 ++verbose;
550 break;
551 case 's':
552 --argc; ++num;
[481]553 checknull(argv[0], argv[num]);
554 checklen(argv[0], argv[num], sizeof(serversock)-1);
555 safe_copy (serversock, argv[num], sizeof(serversock));
[1]556 break;
557 case 'c':
558 --argc; ++num;
[481]559 checknull(argv[0], argv[num]);
560 checklen(argv[0], argv[num], SH_MAXMSG-1);
561 if (0 != check_command(argv[num]))
562 print_usage_and_exit(argv[0], EXIT_FAILURE);
563 safe_copy(message, argv[num], SH_MAXMSG);
564 strncat(message, ":", SH_MAXMSG-strlen(message)-1);
565 message[SH_MAXMSG-1] = '\0';
566 flag = 1;
[1]567 break;
568 default:
[481]569 fprintf(stderr, _("ERROR: unknown option -%c\n\n"), argv[num][1]);
570 print_usage_and_exit(argv[0], EXIT_FAILURE);
571 break;
[1]572 }
573 --argc; ++num;
574 }
575
576 if (flag == 0) /* no command given */
[481]577 print_usage_and_exit(argv[0], EXIT_FAILURE);
[1]578
579 if (argc > 1)
580 {
[481]581 checklen(argv[0], argv[num], SH_MAXMSG - strlen(message) - 1);
582 strncat (message, argv[num], SH_MAXMSG - strlen(message) - 1);
[1]583 message[SH_MAXMSG-1] = '\0';
584 }
585 else
586 {
[481]587 if (0 == strncmp(message, _("PROBE"), 5) ||
588 0 == strncmp(message, _("LIST"), 4))
[1]589 {
590 strncat (message, _("dummy"), SH_MAXMSG -strlen(message) - 1);
591 message[SH_MAXMSG-1] = '\0';
592 }
593 else
594 {
[396]595 fprintf(stderr, "%s", _("ERROR: this command requires a hostname\n"));
[481]596 print_usage_and_exit(argv[0], EXIT_FAILURE);
[1]597 }
598 }
599
[481]600 if (fixup_message(message) < 0)
601 return (EXIT_FAILURE);
[1]602
603 /* Make the socket.
604 */
[481]605 sock = create_unix_socket ();
[1]606 if (sock < 0)
[481]607 return (EXIT_FAILURE);
[1]608
609 /* Set up termination handler.
610 */
611 signal (SIGINT, termination_handler);
612 signal (SIGHUP, termination_handler);
613 signal (SIGTERM, termination_handler);
614 signal (SIGQUIT, termination_handler);
615
616 /* Send the datagram.
617 */
618 status = send_to_server (serversock, message);
619 if (status < 0)
620 {
[396]621 fprintf(stderr, "%s", _("ERROR: sending command to server failed\n"));
[1]622 (void) termination_handler(0);
623 return (EXIT_FAILURE);
624 }
625
626 /* Wait for a reply.
627 */
[481]628 if (verbose)
[1]629 {
[481]630 if (0 == strncmp(message, "LIST", 4))
[396]631 fprintf(stdout, "%s", _("# Waiting for listing.\n"));
[481]632 else
[396]633 fprintf(stdout, "%s", _("# Waiting for confirmation.\n"));
[1]634 }
[481]635
[1]636 status = recv_from_server (message);
637
638 if (status < 0)
639 {
[481]640 fputs(_("ERROR: unexpected or no reply from server.\n"), stderr);
[1]641 (void) termination_handler(0);
642 return (EXIT_FAILURE);
643 }
644
645 /* Clean up. */
646 (void) termination_handler(0);
647 return (EXIT_SUCCESS);
648}
649
Note: See TracBrowser for help on using the repository browser.