source: trunk/src/sh_socket.c@ 552

Last change on this file since 552 was 524, checked in by katerina, 7 years ago

Fix for ticket #419 (broken SetSocketPassword authentication).

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