source: trunk/src/sh_socket.c@ 583

Last change on this file since 583 was 583, checked in by katerina, 27 hours ago

Fix for ticket #471 (autoreconf throws warnings/errors).

File size: 33.8 KB
Line 
1/* SAMHAIN file system integrity testing */
2/* Copyright (C) 2003,2005 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/* define if you want debug info
23 * #define SH_DEBUG_SOCKET
24 */
25
26#if defined(SH_WITH_SERVER) && defined(__linux__)
27#define _GNU_SOURCE
28#endif
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <stddef.h>
33#include <string.h>
34
35#if HAVE_SYS_TIME_H
36#include <sys/time.h>
37#endif
38#include <time.h>
39
40#include "samhain.h"
41#include "sh_socket.h"
42#include "sh_error.h"
43#include "sh_unix.h"
44#include "sh_calls.h"
45#include "sh_guid.h"
46#include "sh_fifo.h"
47#include "sh_utils.h"
48
49#undef FIL__
50#define FIL__ _("sh_socket.c")
51
52#if defined (SH_WITH_CLIENT)
53
54#include <signal.h>
55
56typedef struct delta_tofetch {
57 char uuid[SH_UUID_BUF];
58 time_t last_time;
59 unsigned int count;
60} SH_DELTA_DB;
61
62static SH_DELTA_DB * parse_entry(SH_DELTA_DB * db, const char * str)
63{
64 long last_time;
65 unsigned int count;
66 char buf[SH_UUID_BUF];
67 int res = sscanf(str, _("%u:%ld:%36s"), &count, &last_time, buf);
68 if (res == 3)
69 {
70 db->count = count;
71 db->last_time = (time_t) last_time;
72 sl_strlcpy(db->uuid, buf, SH_UUID_BUF);
73 return db;
74 }
75 return NULL;
76}
77
78static char * unparse_entry(const SH_DELTA_DB * db, char * str, size_t len)
79{
80 int nbytes = sl_snprintf(str, len, _("%u:%ld:%s"),
81 db->count, (long) db->last_time, db->uuid);
82 if (nbytes < 0 || nbytes >= (int) len)
83 return NULL;
84 return str;
85}
86
87static SH_FIFO xfifo = SH_FIFO_INITIALIZER;
88
89int sh_socket_store_uuid(const char * cmd)
90{
91 char * p = sh_util_strdup(cmd);
92 char * q = strchr(cmd, ':');
93 char entry[SH_BUFSIZE];
94 SH_DELTA_DB db;
95
96 if (!q) { SH_FREE(p); return -1; }
97
98 ++q;
99
100 if (0 != sh_uuid_check(q)) { SH_FREE(p); return -1; }
101
102 db.count = 0;
103 db.last_time = (time_t) 0;
104 sl_strlcpy(db.uuid, q, SH_UUID_BUF);
105 SH_FREE(p);
106
107 if (NULL != unparse_entry(&db, entry, sizeof(entry)))
108 {
109 sh_fifo_push(&xfifo, entry);
110 return 0;
111 }
112 return -1;
113}
114
115static unsigned int try_interval = 60;
116static unsigned int try_max = 2;
117
118int set_delta_retry_interval(const char * str)
119{
120 long val = strtol (str, (char **)NULL, 10);
121
122 if (val < 0 || val > INT_MAX)
123 return -1;
124 try_interval = (unsigned int) val;
125 return 0;
126}
127int set_delta_retry_count(const char * str)
128{
129 long val = strtol (str, (char **)NULL, 10);
130
131 if (val < 0 || val > INT_MAX)
132 return -1;
133 try_max = (unsigned int) val;
134 return 0;
135}
136
137char * sh_socket_get_uuid(int * errflag, unsigned int * count, time_t * last)
138{
139 char * entry = sh_fifo_pop(&xfifo);
140 char * uuid = NULL;
141
142 if (entry)
143 {
144 SH_DELTA_DB db;
145 time_t now;
146
147 if (NULL == parse_entry(&db, entry))
148 {
149 SH_FREE(entry);
150 sh_error_handle(SH_ERR_WARN, FIL__, __LINE__, 0, MSG_E_SUBGEN,
151 _("Bad entry in fifo"),
152 _("sh_socket_get_uuid"));
153 *errflag = -1;
154 return NULL;
155 }
156
157 now = time(NULL);
158
159 if ( (db.count > 0) && ((unsigned long)(now - db.last_time) < try_interval) )
160 {
161 sh_fifo_push_tail(&xfifo, entry);
162 SH_FREE(entry);
163 *errflag = -1;
164 return NULL;
165 }
166
167 SH_FREE(entry);
168 uuid = sh_util_strdup(db.uuid);
169 *count = db.count;
170 *last = db.last_time;
171 }
172
173 *errflag = 0;
174 return uuid;
175}
176
177int sh_socket_return_uuid (const char * uuid, unsigned int count, time_t last)
178{
179 (void) last;
180
181 if (count < try_max)
182 {
183 char entry[SH_BUFSIZE];
184 SH_DELTA_DB db;
185 time_t now = time(NULL);
186
187 db.count = count + 1;
188 db.last_time = now;
189 sl_strlcpy(db.uuid, uuid, SH_UUID_BUF);
190
191 if (NULL != unparse_entry(&db, entry, sizeof(entry)))
192 return sh_fifo_push_tail(&xfifo, entry); /* >0 for success */
193 }
194 return -1;
195}
196
197void sh_socket_server_cmd(const char * srvcmd)
198{
199 SL_ENTER(_("sh_tools_server_cmd"));
200
201 if ((srvcmd == NULL) || (srvcmd[0] == '\0') || (sl_strlen(srvcmd) < 4))
202 {
203 SL_RET0(_("sh_socket_server_cmd"));
204 }
205
206 if (0 == strncmp(srvcmd, _("STOP"), 4))
207 {
208 TPT((0, FIL__, __LINE__, _("msg=<stop command from server>\n")));
209#ifdef SIGQUIT
210 raise(SIGQUIT);
211#else
212 sig_terminate = 1;
213 ++sig_raised;
214#endif
215 }
216
217 else if (0 == strncmp(srvcmd, _("RELOAD"), 6))
218 {
219 TPT((0, FIL__, __LINE__, _("msg=<reload command from server>\n")));
220#ifdef SIGHUP
221 raise(SIGHUP);
222#else
223 sig_config_read_again = 1;
224 ++sig_raised;
225#endif
226 }
227
228 else if (0 == strncmp(srvcmd, _("DELTA:"), 6))
229 {
230 TPT((0, FIL__, __LINE__, _("msg=<delta load command from server>\n")));
231
232 if (sh_socket_store_uuid(srvcmd) == 0)
233 {
234 ++sh_load_delta_flag;
235 ++sig_raised;
236 }
237 }
238
239 else if (0 == strncmp(srvcmd, _("SCAN"), 4))
240 {
241 TPT((0, FIL__, __LINE__, _("msg=<scan command from server>\n")));
242 if (sh.flag.isdaemon == S_TRUE)
243 {
244#ifdef SIGTTOU
245 raise(SIGTTOU);
246#else
247 sig_force_check = 1;
248 ++sig_raised;
249#endif
250 }
251 else
252 {
253 sig_force_check = 1;
254 ++sig_raised;
255 }
256 }
257
258 /* Unknown command
259 */
260 else
261 {
262 sh_error_handle(SH_ERR_WARN, FIL__, __LINE__, 0, MSG_E_SUBGEN,
263 srvcmd,
264 _("sh_socket_server_cmd"));
265 }
266 SL_RET0(_("sh_socket_server_cmd"));
267}
268#endif /* #if defined (SH_WITH_CLIENT) */
269
270#if defined(SH_WITH_SERVER)
271#include <errno.h>
272
273#include <sys/types.h>
274#include <sys/stat.h>
275
276#include <unistd.h>
277#include <fcntl.h>
278
279#include <time.h>
280
281#include <sys/socket.h>
282#include <sys/un.h>
283
284
285#ifdef HAVE_SYS_UIO_H
286#include <sys/uio.h>
287#endif
288#if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED)
289#if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
290#include <sys/param.h>
291#include <sys/ucred.h>
292#endif
293#endif
294
295
296int pf_unix_fd = -1;
297static char * sh_sockname = NULL;
298static char sh_sockpass_real[SOCKPASS_MAX+1];
299
300struct socket_cmd {
301 char cmd[SH_MAXMSGLEN];
302 char clt[SH_MAXMSGLEN];
303 char cti[81];
304 struct socket_cmd * next;
305};
306
307#if !defined(O_NONBLOCK)
308#if defined(O_NDELAY)
309#define O_NONBLOCK O_NDELAY
310#else
311#define O_NONBLOCK 0
312#endif
313#endif
314
315#if !defined(AF_FILE)
316#define AF_FILE AF_UNIX
317#endif
318
319static struct socket_cmd * cmdlist = NULL;
320static struct socket_cmd * runlist = NULL;
321
322static int sh_socket_flaguse = S_FALSE;
323static int sh_socket_flaguid = 0;
324
325#include "sh_utils.h"
326
327/* The reload list stores information about
328 * reloads confirmed by clients (startup and/or
329 * runtime cinfiguration reloaded).
330 */
331struct reload_cmd {
332 char clt[SH_MAXMSGLEN];
333 time_t cti;
334 struct reload_cmd * next;
335};
336static struct reload_cmd * reloadlist = NULL;
337
338void sh_socket_add2reload (const char * clt)
339{
340 struct reload_cmd * new = reloadlist;
341
342 while (new)
343 {
344 if (0 == sl_strcmp(new->clt, clt))
345 {
346#ifdef SH_DEBUG_SOCKET
347 fprintf(stderr, "add2reload: time reset for %s\n", clt);
348#endif
349 sl_strlcpy (new->clt, clt, SH_MAXMSGLEN);
350 new->cti = time(NULL);
351 return;
352 }
353 new = new->next;
354 }
355
356 new = SH_ALLOC(sizeof(struct reload_cmd));
357#ifdef SH_DEBUG_SOCKET
358 fprintf(stderr, "add2reload: time set for %s\n", clt);
359#endif
360 sl_strlcpy (new->clt, clt, SH_MAXMSGLEN);
361 new->cti = time(NULL);
362
363 new->next = reloadlist;
364 reloadlist = new;
365
366 return;
367}
368
369#include "zAVLTree.h"
370#include "sh_html.h"
371#include "sh_tools.h"
372static void sh_socket_add2list (struct socket_cmd * in);
373
374static void sh_socket_probe4reload (void)
375{
376 struct reload_cmd * new;
377 struct socket_cmd cmd;
378
379 zAVLCursor avlcursor;
380 client_t * item;
381 extern zAVLTree * all_clients;
382
383 char * file;
384 unsigned long dummy;
385 struct stat buf;
386
387 for (item = (client_t *) zAVLFirst(&avlcursor, all_clients); item;
388 item = (client_t *) zAVLNext(&avlcursor))
389 {
390 if (item->status_now != CLT_INACTIVE)
391 {
392 int flag = 0;
393
394 file = get_client_conf_file (item->hostname, &dummy);
395
396 if (0 == stat (file, &buf))
397 {
398 new = reloadlist;
399 while (new)
400 {
401 if (0 == sl_strcmp(new->clt, item->hostname))
402 {
403 flag = 1; /* Client is in list already */
404
405 if (buf.st_mtime > new->cti)
406 {
407 /* reload */
408 sl_strlcpy(cmd.cmd, _("RELOAD"), SH_MAXMSGLEN);
409 sl_strlcpy(cmd.clt, item->hostname, SH_MAXMSGLEN);
410 sh_socket_add2list (&cmd);
411 }
412 break;
413 }
414 new = new->next;
415 }
416
417 if (flag == 0)
418 {
419 /* client is active, but start message has been missed; reload
420 */
421 sl_strlcpy(cmd.cmd, _("RELOAD"), SH_MAXMSGLEN);
422 sl_strlcpy(cmd.clt, item->hostname, SH_MAXMSGLEN);
423 sh_socket_add2list (&cmd);
424
425 /* Add the client to the reload list and set
426 * time to 0, since we don't know the startup time.
427 */
428 sh_socket_add2reload (item->hostname);
429 new = reloadlist;
430 while (new)
431 {
432 if (0 == sl_strcmp(new->clt, item->hostname))
433 {
434 new->cti = 0;
435 break;
436 }
437 new = new->next;
438 }
439 }
440 } /* if stat(file).. */
441 } /* if !CLT_INACTIVE */
442 } /* loop over clients */
443 return;
444}
445
446char * sh_get_sockpass (void)
447{
448 size_t j = 0;
449
450 while (skey->sh_sockpass[2*j] != '\0' && j < sizeof(sh_sockpass_real))
451 {
452 sh_sockpass_real[j] = skey->sh_sockpass[2*j];
453 ++j;
454 }
455 sh_sockpass_real[j] = '\0';
456
457 return sh_sockpass_real;
458}
459
460void sh_set_sockpass (void)
461{
462 int j;
463 for (j = 0; j < 15; ++j)
464 {
465 sh_sockpass_real[j] = '\0';
466 }
467}
468
469int sh_socket_use (const char * c)
470{
471 return sh_util_flagval(c, &sh_socket_flaguse);
472}
473
474int sh_socket_remove ()
475{
476 int retval = 0;
477#ifdef S_ISSOCK
478 struct stat sbuf;
479#endif
480
481 SL_ENTER(_("sh_socket_remove"));
482
483 if (NULL == sh_sockname)
484 {
485 SL_RETURN((retval),_("sh_socket_remove"));
486 }
487
488 if (0 != tf_trust_check (DEFAULT_PIDDIR, SL_YESPRIV))
489 {
490 SL_RETURN((-1),_("sh_socket_remove"));
491 }
492
493 if ( (retry_lstat(FIL__, __LINE__, sh_sockname, &sbuf) == 0) &&
494 (sbuf.st_uid == getuid()))
495 {
496#ifdef S_ISSOCK
497 if (S_ISSOCK (sbuf.st_mode))
498 {
499 retval = retry_aud_unlink (FIL__, __LINE__, sh_sockname);
500 }
501#else
502 retval = retry_aud_unlink (FIL__, __LINE__, sh_sockname);
503#endif
504 }
505 SL_RETURN((retval),_("sh_socket_remove"));
506}
507
508#if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && !defined(HAVE_STRUCT_CMSGCRED) && !defined(HAVE_STRUCT_FCRED) && !(defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))
509
510#define NEED_PASSWORD_AUTH
511#endif
512
513int sh_socket_uid (const char * c)
514{
515 uid_t val = (uid_t) strtol (c, (char **)NULL, 10);
516 sh_socket_flaguid = val;
517#if defined(NEED_PASSWORD_AUTH)
518 sh_error_handle(SH_ERR_WARN, FIL__, __LINE__, errno, MSG_E_SUBGEN,
519 _("Config option SetSocketAllowUID not supported, use SetSocketPassword"),
520 _("sh_socket_uid"));
521#endif
522 return 0;
523}
524
525int sh_socket_password (const char * c)
526{
527#if defined(NEED_PASSWORD_AUTH)
528 int j, i;
529
530#define LCG(n) ((69069 * n) & 0xffffffffUL)
531
532 i = sl_strlen(c);
533 if (i > SOCKPASS_MAX) {
534 return -1;
535 }
536 for (j = 0; j < (2*SOCKPASS_MAX+1); ++j)
537 {
538 skey->sh_sockpass[j] = '\0';
539 }
540 for (j = 0; j < i; ++j)
541 {
542 skey->sh_sockpass[2*j] = c[j];
543 skey->sh_sockpass[(2*j)+1] = (LCG(c[j]) % 256);
544 }
545 return 0;
546#else
547 sh_error_handle(SH_ERR_WARN, FIL__, __LINE__, errno, MSG_E_SUBGEN,
548 _("Config option SetSocketPassword not supported, use SetSocketAllowUID"),
549 _("sh_socket_password"));
550 (void) c;
551 return 0;
552#endif
553}
554
555
556int sh_socket_open_int ()
557{
558 struct sockaddr_un name;
559 size_t size;
560 int flags;
561#if defined(SO_PASSCRED)
562 socklen_t optval = 1;
563#endif
564 struct stat buf;
565 char errbuf[SH_ERRBUF_SIZE];
566
567 SL_ENTER(_("sh_socket_open_int"));
568
569 if (sh_socket_flaguse == S_FALSE)
570 {
571 SL_RETURN(0, _("sh_socket_open_int"));
572 }
573
574 if (sh_sockname == NULL)
575 {
576 size = sl_strlen(DEFAULT_PIDDIR) + 1 + sl_strlen(SH_INSTALL_NAME) + 6;
577 sh_sockname = SH_ALLOC(size); /* compile-time constant */
578 sl_strlcpy(sh_sockname, DEFAULT_PIDDIR, size);
579 sl_strlcat(sh_sockname, "/", size);
580 sl_strlcat(sh_sockname, SH_INSTALL_NAME, size);
581 sl_strlcat(sh_sockname, _(".sock"), size);
582 }
583
584 if (0 != sh_unix_check_piddir (sh_sockname))
585 {
586 SH_FREE(sh_sockname);
587 SL_RETURN((-1),_("sh_socket_open_int"));
588 }
589
590 pf_unix_fd = socket (PF_UNIX, SOCK_STREAM, 0);
591 if ((pf_unix_fd) < 0)
592 {
593 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
594 sh_error_message (errno, errbuf, sizeof(errbuf)),
595 _("sh_socket_open_int: socket"));
596 SL_RETURN( (-1), _("sh_socket_open_int"));
597 }
598
599 if (sizeof(name.sun_path) < (1 + sl_strlen(sh_sockname)))
600 {
601 sl_close_fd(FIL__, __LINE__, pf_unix_fd); pf_unix_fd = -1;
602 sh_error_handle ((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN,
603 _("PID dir path too long"),
604 _("sh_socket_open_int"));
605 SL_RETURN( (-1), _("sh_socket_open_int"));
606 }
607
608 name.sun_family = AF_FILE;
609 sl_strlcpy (name.sun_path, sh_sockname, sizeof(name.sun_path));
610
611 size = (offsetof (struct sockaddr_un, sun_path)
612 + strlen (name.sun_path) + 1);
613
614 flags = retry_lstat (FIL__, __LINE__, sh_sockname, &buf);
615
616 if (flags == 0)
617 {
618 sh_error_handle(SH_ERR_INFO, FIL__, __LINE__, -1, MSG_E_SUBGEN,
619 _("Socket exists, trying to unlink it"),
620 _("sh_socket_open_int"));
621 if (sh_socket_remove() < 0)
622 {
623 sl_close_fd(FIL__, __LINE__, pf_unix_fd); pf_unix_fd = -1;
624 sh_error_handle ((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN,
625 _("Unlink of socket failed, maybe path not trusted"),
626 _("sh_socket_open_int"));
627 SL_RETURN( (-1), _("sh_socket_open_int"));
628 }
629 }
630
631 if (bind ((pf_unix_fd), (struct sockaddr *) &name, size) < 0)
632 {
633 sl_close_fd(FIL__, __LINE__, pf_unix_fd); pf_unix_fd = -1;
634 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
635 sh_error_message (errno, errbuf, sizeof(errbuf)),
636 _("sh_socket_open_int: bind"));
637 SL_RETURN( (-1), _("sh_socket_open_int"));
638 }
639
640#ifdef SO_PASSCRED
641 if (0 != setsockopt(pf_unix_fd, SOL_SOCKET, SO_PASSCRED,
642 &optval, sizeof(optval)))
643 {
644 sl_close_fd(FIL__, __LINE__, pf_unix_fd); pf_unix_fd = -1;
645 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
646 sh_error_message (errno, errbuf, sizeof(errbuf)),
647 _("sh_socket_open_int: setsockopt"));
648 SL_RETURN( (-1), _("sh_socket_open_int"));
649 }
650#endif
651
652 flags = fcntl((pf_unix_fd), F_GETFL);
653 if (flags < 0)
654 {
655 sl_close_fd(FIL__, __LINE__, pf_unix_fd); pf_unix_fd = -1;
656 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
657 sh_error_message (errno, errbuf, sizeof(errbuf)),
658 _("sh_socket_open_int: fcntl1"));
659 SL_RETURN( (-1), _("sh_socket_open_int"));
660 }
661
662 flags = fcntl((pf_unix_fd), F_SETFL, flags|O_NONBLOCK);
663 if (flags < 0)
664 {
665 sl_close_fd(FIL__, __LINE__, pf_unix_fd); pf_unix_fd = -1;
666 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
667 sh_error_message (errno, errbuf, sizeof(errbuf)),
668 _("sh_socket_open_int: fcntl2"));
669 SL_RETURN( (-1), _("sh_socket_open_int"));
670 }
671
672 if (0 != listen(pf_unix_fd, 5))
673 {
674 sl_close_fd(FIL__, __LINE__, pf_unix_fd); pf_unix_fd = -1;
675 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
676 sh_error_message (errno, errbuf, sizeof(errbuf)),
677 _("sh_socket_open_int: listen"));
678 SL_RETURN( (-1), _("sh_socket_open_int"));
679 }
680 SL_RETURN( (0), _("sh_socket_open_int"));
681}
682
683
684/*
685 * Parts of the socket authentication code is copied from PostgreSQL:
686 *
687 * PostgreSQL Database Management System
688 * (formerly known as Postgres, then as Postgres95)
689 *
690 * Portions Copyright (c) 1996-2001, The PostgreSQL Global Development Group
691 *
692 * Portions Copyright (c) 1994, The Regents of the University of California
693 *
694 * Permission to use, copy, modify, and distribute this software and its
695 * documentation for any purpose, without fee, and without a written agreement
696 * is hereby granted, provided that the above copyright notice and this
697 * paragraph and the following two paragraphs appear in all copies.
698 *
699 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
700 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
701 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
702 * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
703 * POSSIBILITY OF SUCH DAMAGE.
704 *
705 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
706 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
707 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
708 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
709 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
710 */
711
712static int receive_message(int talkfd, struct msghdr * msg, size_t message_size)
713{
714 unsigned int retry = 0;
715 int nbytes;
716 char * message = msg->msg_iov->iov_base;
717 char errbuf[SH_ERRBUF_SIZE];
718
719 do {
720 nbytes = recvmsg (talkfd, msg, 0);
721 if ((nbytes < 0) && (errno != EAGAIN))
722 {
723 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
724 sh_error_message (errno, errbuf, sizeof(errbuf)),
725 _("sh_socket_read: recvmsg"));
726 sl_close_fd(FIL__, __LINE__, talkfd);
727 return -1;
728 }
729 else if (nbytes < 0)
730 {
731 ++retry;
732 retry_msleep(0, 10);
733 }
734 } while ((nbytes < 0) && (retry < 3));
735
736 /* msg.msg_iov.iov_base, filled by recvmsg
737 */
738 message[message_size-1] = '\0';
739
740 if (nbytes < 0)
741 {
742 if (errno == EAGAIN)
743 {
744 /* no data */
745 sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, errno, MSG_E_SUBGEN,
746 sh_error_message (errno, errbuf, sizeof(errbuf)),
747 _("sh_socket_read: recvfrom"));
748 sl_close_fd(FIL__, __LINE__, talkfd);
749 return 0;
750 }
751 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
752 sh_error_message (errno, errbuf, sizeof(errbuf)),
753 _("sh_socket_read: recvfrom"));
754 sl_close_fd(FIL__, __LINE__, talkfd);
755 return -1;
756 }
757 return 0;
758}
759
760#if defined(HAVE_GETPEEREID)
761
762static int get_peer_uid(int talkfd)
763{
764 uid_t peer_uid;
765 gid_t peer_gid;
766 char errbuf[SH_ERRBUF_SIZE];
767
768 if (0 != getpeereid(talkfd, &peer_uid, &peer_gid))
769 {
770 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
771 sh_error_message (errno, errbuf, sizeof(errbuf)),
772 _("sh_socket_read: getpeereid"));
773 sl_close_fd(FIL__, __LINE__, talkfd);
774 return -1;
775 }
776 return peer_uid;
777}
778
779#elif defined(SO_PEERCRED)
780
781static int get_peer_uid(int talkfd)
782{
783 char errbuf[SH_ERRBUF_SIZE];
784 struct ucred cr;
785#ifdef HAVE_SOCKLEN_T
786 socklen_t cl = sizeof(cr);
787#else
788 int cl = sizeof(cr);
789#endif
790
791 if (0 != getsockopt(talkfd, SOL_SOCKET, SO_PEERCRED, &cr, &cl))
792 {
793 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
794 sh_error_message (errno, errbuf, sizeof(errbuf)),
795 _("sh_socket_read: getsockopt"));
796 sl_close_fd(FIL__, __LINE__, talkfd);
797 return -1;
798 }
799 return cr.uid;
800}
801
802#endif
803
804#if defined(NEED_PASSWORD_AUTH)
805char * check_password(char * message, size_t msglen, int * client_uid, int talkfd)
806{
807 char * cmd = NULL;
808 char * eopw = NULL;
809 char * goodpassword = NULL;
810
811 goodpassword = sh_get_sockpass();
812 eopw = strchr(message, '@');
813 if (eopw)
814 *eopw = '\0';
815 /*
816 * message is null-terminated and >> goodpassword
817 */
818 if (0 == strcmp(goodpassword, message) &&
819 strlen(goodpassword) < (msglen/2))
820 {
821 *client_uid = sh_socket_flaguid;
822 cmd = &message[strlen(goodpassword)+1];
823 sh_set_sockpass();
824 }
825 else
826 {
827 sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
828 _("Bad password"),
829 _("sh_socket_read"));
830 sh_set_sockpass();
831 sl_close_fd(FIL__, __LINE__, talkfd);
832 return NULL;
833 }
834 return cmd;
835}
836#endif
837
838static int list_all (int talkfd, char * cmd);
839static int process_message(int talkfd, char * cmd, struct socket_cmd * srvcmd);
840
841static
842int sh_socket_read (struct socket_cmd * srvcmd)
843{
844 char message[SH_MAXMSG];
845 struct sockaddr_un name;
846 ACCEPT_TYPE_ARG3 size = sizeof(name);
847 int talkfd;
848 char * cmd = NULL;
849 int client_uid = -1;
850 char errbuf[SH_ERRBUF_SIZE];
851 struct msghdr msg;
852 struct iovec iov;
853 int status;
854
855 if (pf_unix_fd < 0)
856 return 0;
857
858 iov.iov_base = (char *) &message;
859 iov.iov_len = sizeof(message);
860
861 memset (&msg, 0, sizeof (msg));
862 msg.msg_iov = &iov;
863 msg.msg_iovlen = 1;
864
865 /* the socket is non-blocking
866 * 'name' is the address of the sender socket
867 */
868 do {
869 talkfd = accept(pf_unix_fd, (struct sockaddr *) &name, &size);
870 } while (talkfd < 0 && errno == EINTR);
871
872 if ((talkfd < 0) && (errno == EAGAIN))
873 {
874 return 0;
875 }
876 else if (talkfd < 0)
877 {
878 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
879 sh_error_message (errno, errbuf, sizeof(errbuf)),
880 _("sh_socket_read: accept"));
881 return -1;
882 }
883
884 if (receive_message(talkfd, &msg, sizeof(message)) < 0)
885 return -1;
886
887 /* Authenticate request by peer uid or password.
888 */
889#if defined(HAVE_GETPEEREID)
890 client_uid = get_peer_uid(talkfd);
891 cmd = message;
892
893#elif defined(SO_PEERCRED)
894 client_uid = get_peer_uid(talkfd);
895 cmd = message;
896
897#elif defined(NEED_PASSWORD_AUTH)
898 cmd = check_password(message, sizeof(message), &client_uid, talkfd);
899
900#else
901 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
902 _("Socket credentials not supported on this OS"),
903 _("sh_socket_read"));
904 sl_close_fd(FIL__, __LINE__, talkfd);
905 return -1;
906#endif
907
908 if (client_uid != sh_socket_flaguid)
909 {
910 sh_error_handle((-1), FIL__, __LINE__, client_uid, MSG_E_SUBGEN,
911 _("client does not have required uid"),
912 _("sh_socket_read: getsockopt"));
913 sl_close_fd(FIL__, __LINE__, talkfd);
914 return -1;
915 }
916
917 status = process_message(talkfd, cmd, srvcmd);
918
919 sl_close_fd(FIL__, __LINE__, talkfd);
920 return status;
921}
922
923static int check_valid_command(const char * str)
924{
925 unsigned int i = 0;
926 char * commands[] = { N_("DELTA"), N_("RELOAD"), N_("STOP"), N_("SCAN"),
927 N_("CANCEL"), N_("LISTALL"), N_("LIST"), N_("PROBE"), NULL };
928
929 while (commands[i])
930 {
931 if (0 == strcmp(_(commands[i]), str))
932 {
933 return 0;
934 }
935 ++i;
936 }
937 return -1;
938}
939
940static int send_reply (int fd, char * msg)
941{
942 int nbytes = send (fd, msg, strlen(msg) + 1, 0);
943
944 if (nbytes < 0)
945 {
946 char errbuf[SH_ERRBUF_SIZE];
947 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
948 sh_error_message (errno, errbuf, sizeof(errbuf)),
949 _("send_reply"));
950 return -1;
951 }
952
953 return nbytes;
954}
955
956static int process_message(int talkfd, char * cmd, struct socket_cmd * srvcmd)
957{
958 int nbytes;
959 char error_type[SH_ERRBUF_SIZE] = { '\0' };
960 char * clt = (cmd) ? strchr(cmd, ':') : NULL;
961
962 if (clt && 0 == strncmp(cmd, _("DELTA:"), 6))
963 {
964 /* DELTA:uuid:hostname
965 */
966 char * uuid = clt;
967
968 *uuid = '\0'; ++uuid;
969 clt = strchr(uuid, ':');
970 if (clt) { *clt = '\0'; ++clt; }
971
972 if (sh_uuid_check(uuid) < 0)
973 {
974 sl_strlcpy(error_type, _("!E:uuid-format:"), sizeof(error_type));
975 sl_strlcat(error_type, uuid, sizeof(error_type));
976 clt = NULL;
977 }
978
979 --uuid; *uuid = ':';
980 }
981 else if (clt && *clt == ':')
982 {
983 *clt = '\0'; ++clt;
984 if (check_valid_command(cmd) < 0)
985 {
986 sl_strlcpy(error_type, _("!E:cmd-invalid:"), sizeof(error_type));
987 sl_strlcat(error_type, cmd, sizeof(error_type));
988 clt = NULL;
989 }
990 }
991
992 if (clt != NULL)
993 {
994 if (sl_strlen(cmd) >= SH_MAXMSGLEN)
995 {
996 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
997 _("Bad message format: command too long"),
998 _("sh_socket_read"));
999 sl_strlcpy(error_type, _("!E:cmd-toolong"), sizeof(error_type));
1000 send_reply(talkfd, error_type);
1001 return -1;
1002 }
1003 else if (sl_strlen(clt) >= SH_MAXMSGLEN)
1004 {
1005 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
1006 _("Bad message format: hostname too long"),
1007 _("sh_socket_read"));
1008 sl_strlcpy(error_type, _("!E:hostname-toolong"), sizeof(error_type));
1009 send_reply(talkfd, error_type);
1010 return -1;
1011 }
1012
1013 if (0 == strncmp(cmd, _("LIST"), 4))
1014 return list_all(talkfd, cmd);
1015 else if (0 == strncmp(cmd, _("PROBE"), 4))
1016 {
1017 sh_socket_probe4reload();
1018 sl_strlcpy(cmd, _("LIST"), 5);
1019 return list_all(talkfd, cmd);
1020 }
1021
1022 sl_strlcpy (srvcmd->cmd, cmd, SH_MAXMSGLEN);
1023 sl_strlcpy (srvcmd->clt, clt, SH_MAXMSGLEN);
1024 --clt; *clt = ':';
1025 }
1026 else
1027 {
1028 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
1029 _("Bad message format"),
1030 _("sh_socket_read"));
1031 if (error_type[0] == '\0')
1032 sl_strlcpy(error_type, _("!E:message-format"), sizeof(error_type));
1033 send_reply(talkfd, error_type);
1034 return -1;
1035 }
1036
1037 /* Bounce the message back to the sender.
1038 */
1039 nbytes = send_reply(talkfd, cmd);
1040
1041 return nbytes;
1042}
1043
1044static int list_all (int talkfd, char * cmd)
1045{
1046 int nbytes;
1047 struct socket_cmd * list_cmd;
1048 char message[SH_MAXMSG];
1049 char errbuf[SH_ERRBUF_SIZE];
1050
1051 if (0 == strncmp(cmd, _("LISTALL"), 7))
1052 {
1053 list_cmd = runlist;
1054 while (list_cmd)
1055 {
1056 sl_snprintf(message, sizeof(message), _("SENT %42s %32s %s"),
1057 list_cmd->cmd, list_cmd->clt, list_cmd->cti);
1058
1059 nbytes = send (talkfd, message, sl_strlen(message) + 1, 0);
1060 if (nbytes < 0)
1061 {
1062 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
1063 sh_error_message (errno, errbuf, sizeof(errbuf)),
1064 _("sh_socket_read: sendto"));
1065 return -1;
1066 }
1067 list_cmd = list_cmd->next;
1068 }
1069 }
1070
1071 list_cmd = cmdlist;
1072 while (list_cmd)
1073 {
1074 sl_snprintf(message, sizeof(message), _(">>>> %42s %32s %s"),
1075 list_cmd->cmd, list_cmd->clt, list_cmd->cti);
1076
1077 nbytes = send (talkfd, message, sl_strlen(message) + 1, 0);
1078 if (nbytes < 0)
1079 {
1080 sh_error_handle((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
1081 sh_error_message (errno, errbuf, sizeof(errbuf)),
1082 _("sh_socket_read: sendto"));
1083 return -1;
1084 }
1085 list_cmd = list_cmd->next;
1086 }
1087
1088 send (talkfd, _("END"), 4, 0);
1089 return 0;
1090}
1091
1092static void sh_socket_add2list (struct socket_cmd * in)
1093{
1094 struct socket_cmd * new = cmdlist;
1095 struct socket_cmd * last = cmdlist;
1096
1097 while (new)
1098 {
1099 /* Only skip identical commands.
1100 */
1101 if (0 == sl_strcmp(new->clt, in->clt) &&
1102 0 == sl_strcmp(new->cmd, in->cmd))
1103 {
1104 (void) sh_unix_time(0, new->cti, sizeof(new->cti));
1105 return;
1106 }
1107 new = new->next;
1108 }
1109
1110 new = SH_ALLOC(sizeof(struct socket_cmd));
1111 sl_strlcpy (new->cmd, in->cmd, sizeof(new->cmd));
1112 sl_strlcpy (new->clt, in->clt, sizeof(new->clt));
1113 (void) sh_unix_time(0, new->cti, sizeof(new->cti));
1114 new->next = NULL;
1115
1116 if (last)
1117 {
1118 while (last->next) { last = last->next; }
1119 last->next = new;
1120 }
1121 else
1122 {
1123 cmdlist = new;
1124 }
1125 return;
1126}
1127
1128static void sh_socket_add2run (struct socket_cmd * in)
1129{
1130 struct socket_cmd * new = runlist;
1131 struct socket_cmd * last = runlist;
1132
1133 while (new)
1134 {
1135 /* Only skip identical commands. First 5 will
1136 * make all 'DELTA' identical.
1137 */
1138 if (0 == sl_strcmp(new->clt, in->clt) &&
1139 0 == sl_strncmp(new->cmd, in->cmd, 5))
1140 {
1141 sl_strlcpy (new->cmd, in->cmd, sizeof(new->cmd));
1142 (void) sh_unix_time(0, new->cti, sizeof(new->cti));
1143 return;
1144 }
1145 new = new->next;
1146 }
1147
1148 new = SH_ALLOC(sizeof(struct socket_cmd));
1149 sl_strlcpy (new->cmd, in->cmd, sizeof(new->cmd));
1150 sl_strlcpy (new->clt, in->clt, sizeof(new->clt));
1151#ifdef SH_DEBUG_SOCKET
1152 fprintf(stderr, "add2run: time set for %s\n", new->clt);
1153#endif
1154 (void) sh_unix_time(0, new->cti, sizeof(new->cti));
1155 new->next = NULL;
1156
1157 if (last)
1158 {
1159 while (last->next) { last = last->next; }
1160 last->next = new;
1161 }
1162 else
1163 {
1164 runlist = new;
1165 }
1166 return;
1167}
1168
1169
1170
1171static void sh_socket_rm2list (const char * client_name, int remove_all)
1172{
1173 struct socket_cmd * old = cmdlist;
1174 struct socket_cmd * new = cmdlist;
1175
1176 while (new)
1177 {
1178 if (0 == sl_strcmp(new->clt, client_name))
1179 {
1180 if ((new == cmdlist) && (new->next == NULL))
1181 {
1182 /* There is only one entry */
1183 cmdlist = NULL;
1184 SH_FREE(new);
1185 return;
1186 }
1187 else if (new == cmdlist)
1188 {
1189 /* first entry: new = old = cmdlist */
1190 cmdlist = new->next;
1191 SH_FREE(new);
1192 if (remove_all == S_FALSE)
1193 return;
1194 old = cmdlist;
1195 new = cmdlist;
1196 continue;
1197 }
1198 else
1199 {
1200 old->next = new->next;
1201 SH_FREE(new);
1202 if (remove_all == S_FALSE)
1203 return;
1204 new = old;
1205 }
1206 }
1207 old = new;
1208 new = new->next;
1209 }
1210 return;
1211}
1212
1213/* poll the socket to gather input
1214 */
1215int sh_socket_poll()
1216{
1217 struct socket_cmd cmd;
1218 char cancel_cmd[SH_MAXMSGLEN];
1219
1220 /* struct pollfd sh_poll = { pf_unix_fd, POLLIN, 0 }; */
1221
1222 if (pf_unix_fd < 0)
1223 return 0;
1224
1225 sl_strlcpy(cancel_cmd, _("CANCEL"), sizeof(cancel_cmd));
1226
1227 while (sh_socket_read (&cmd) > 0)
1228 {
1229 if (0 == sl_strcmp(cmd.cmd, cancel_cmd))
1230 sh_socket_rm2list (cmd.clt, S_TRUE);
1231 else
1232 sh_socket_add2list (&cmd);
1233 }
1234 return 0;
1235}
1236
1237/* return the command associated with client_name
1238 and remove the corresponding entry
1239 */
1240char * sh_socket_check(const char * client_name)
1241{
1242 struct socket_cmd * new = cmdlist;
1243 static char out[SH_MAXMSGLEN];
1244
1245 while (new)
1246 {
1247 if (0 == sl_strcmp(new->clt, client_name))
1248 {
1249 sl_strlcpy(out, new->cmd, sizeof(out));
1250 sh_socket_add2run (new);
1251 sh_socket_rm2list (client_name, S_FALSE);
1252 return out;
1253 }
1254 new = new->next;
1255 }
1256 return NULL;
1257}
1258/* #if defined (SH_WITH_SERVER)
1259 */
1260#endif
1261
1262
1263#ifdef SH_CUTEST
1264#include "CuTest.h"
1265
1266void Test_cmdlist (CuTest *tc) {
1267
1268#if defined (SH_WITH_SERVER)
1269 struct socket_cmd cmd;
1270 char * p;
1271
1272 sl_strlcpy(cmd.clt, "one", sizeof(cmd.clt));
1273 sl_strlcpy(cmd.cmd, "RELOAD", sizeof(cmd.cmd));
1274
1275 sh_socket_add2list (&cmd);
1276 p = sh_socket_check("one");
1277 CuAssertPtrNotNull(tc, p);
1278 CuAssertStrEquals(tc, "RELOAD", p);
1279
1280 p = sh_socket_check("one");
1281 CuAssertPtrEquals(tc, NULL, p);
1282
1283 sh_socket_add2list (&cmd);
1284 sl_strlcpy(cmd.cmd, "STOP", sizeof(cmd.cmd));
1285 sh_socket_add2list (&cmd);
1286
1287 sl_strlcpy(cmd.clt, "two", sizeof(cmd.clt));
1288 sl_strlcpy(cmd.cmd, "STOP", sizeof(cmd.cmd));
1289 sh_socket_add2list (&cmd);
1290 sl_strlcpy(cmd.clt, "three", sizeof(cmd.clt));
1291 sl_strlcpy(cmd.cmd, "STOP", sizeof(cmd.cmd));
1292 sh_socket_add2list (&cmd);
1293
1294 sl_strlcpy(cmd.clt, "one", sizeof(cmd.clt));
1295 sl_strlcpy(cmd.cmd, "DELTA", sizeof(cmd.cmd));
1296 sh_socket_add2list (&cmd);
1297
1298 p = sh_socket_check("one");
1299 CuAssertPtrNotNull(tc, p);
1300 CuAssertStrEquals(tc, "RELOAD", p);
1301
1302 sl_strlcpy(cmd.clt, "two", sizeof(cmd.clt));
1303 sl_strlcpy(cmd.cmd, "RELOAD", sizeof(cmd.cmd));
1304 sh_socket_add2list (&cmd);
1305 sl_strlcpy(cmd.clt, "three", sizeof(cmd.clt));
1306 sl_strlcpy(cmd.cmd, "RELOAD", sizeof(cmd.cmd));
1307 sh_socket_add2list (&cmd);
1308
1309 p = sh_socket_check("one");
1310 CuAssertPtrNotNull(tc, p);
1311 CuAssertStrEquals(tc, "STOP", p);
1312 p = sh_socket_check("one");
1313 CuAssertPtrNotNull(tc, p);
1314 CuAssertStrEquals(tc, "DELTA", p);
1315 p = sh_socket_check("one");
1316 CuAssertPtrEquals(tc, NULL, p);
1317
1318 /* Test removal in correct order */
1319 sl_strlcpy(cmd.clt, "one", sizeof(cmd.clt));
1320 sl_strlcpy(cmd.cmd, "RELOAD", sizeof(cmd.cmd));
1321 sh_socket_add2list (&cmd);
1322 sl_strlcpy(cmd.cmd, "STOP", sizeof(cmd.cmd));
1323 sh_socket_add2list (&cmd);
1324 sl_strlcpy(cmd.cmd, "DELTA", sizeof(cmd.cmd));
1325 sh_socket_add2list (&cmd);
1326 sl_strlcpy(cmd.cmd, "FOOBAR", sizeof(cmd.cmd));
1327 sh_socket_add2list (&cmd);
1328
1329 sh_socket_rm2list ("one", S_FALSE);
1330
1331 p = sh_socket_check("one");
1332 CuAssertPtrNotNull(tc, p);
1333 CuAssertStrEquals(tc, "STOP", p);
1334
1335 sh_socket_rm2list ("one", S_FALSE);
1336
1337 p = sh_socket_check("one");
1338 CuAssertPtrNotNull(tc, p);
1339 CuAssertStrEquals(tc, "FOOBAR", p);
1340
1341 p = sh_socket_check("one");
1342 CuAssertPtrEquals(tc, NULL, p);
1343
1344 sl_strlcpy(cmd.clt, "one", sizeof(cmd.clt));
1345 sl_strlcpy(cmd.cmd, "RELOAD", sizeof(cmd.cmd));
1346 sh_socket_add2list (&cmd);
1347 sl_strlcpy(cmd.cmd, "STOP", sizeof(cmd.cmd));
1348 sh_socket_add2list (&cmd);
1349
1350 sl_strlcpy(cmd.clt, "two", sizeof(cmd.clt));
1351 sl_strlcpy(cmd.cmd, "RELOAD", sizeof(cmd.cmd));
1352 sh_socket_add2list (&cmd);
1353 sl_strlcpy(cmd.clt, "three", sizeof(cmd.clt));
1354 sl_strlcpy(cmd.cmd, "RELOAD", sizeof(cmd.cmd));
1355 sh_socket_add2list (&cmd);
1356
1357 sl_strlcpy(cmd.clt, "one", sizeof(cmd.clt));
1358 sl_strlcpy(cmd.cmd, "DELTA", sizeof(cmd.cmd));
1359 sh_socket_add2list (&cmd);
1360 sl_strlcpy(cmd.cmd, "FOOBAR", sizeof(cmd.cmd));
1361 sh_socket_add2list (&cmd);
1362
1363 sh_socket_rm2list ("one", S_TRUE);
1364 p = sh_socket_check("one");
1365 CuAssertPtrEquals(tc, NULL, p);
1366
1367 p = sh_socket_check("two");
1368 CuAssertPtrNotNull(tc, p);
1369 CuAssertStrEquals(tc, "STOP", p);
1370 p = sh_socket_check("two");
1371 CuAssertPtrNotNull(tc, p);
1372 CuAssertStrEquals(tc, "RELOAD", p);
1373 p = sh_socket_check("two");
1374 CuAssertPtrEquals(tc, NULL, p);
1375
1376 p = sh_socket_check("three");
1377 CuAssertPtrNotNull(tc, p);
1378 CuAssertStrEquals(tc, "STOP", p);
1379 p = sh_socket_check("three");
1380 CuAssertPtrNotNull(tc, p);
1381 CuAssertStrEquals(tc, "RELOAD", p);
1382 p = sh_socket_check("three");
1383 CuAssertPtrEquals(tc, NULL, p);
1384
1385 p = sh_socket_check("four");
1386 CuAssertPtrEquals(tc, NULL, p);
1387#else
1388 (void) tc;
1389#endif
1390}
1391
1392#endif /* #ifdef SH_CUTEST */
1393
1394
1395
Note: See TracBrowser for help on using the repository browser.