You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
11 KiB
402 lines
11 KiB
/*
|
|
* Copyright (c) 1983 Regents of the University of California, (c) 1998 David Faure.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or
|
|
* without modification, are permitted provided that the following
|
|
* conditions are met:
|
|
*
|
|
* - Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer.
|
|
*
|
|
* - Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials
|
|
* provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* (BSD License, from tdelibs/doc/common/bsd-license.html)
|
|
*/
|
|
|
|
char copyright[] =
|
|
"@(#) Copyright (c) 1983 Regents of the University of California.\n"
|
|
"All rights reserved.\n";
|
|
|
|
/*
|
|
* From: @(#)talkd.c 5.8 (Berkeley) 2/26/91
|
|
*/
|
|
|
|
#include "includ.h"
|
|
|
|
/*
|
|
* talkd - internet talk daemon
|
|
* loops waiting for and processing requests until idle for a while,
|
|
* then exits.
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <signal.h>
|
|
#include <syslog.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
/*#include <stdio.h>*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <netdb.h>
|
|
|
|
#include <sys/utsname.h>
|
|
|
|
#include "proto.h"
|
|
#include "process.h"
|
|
#include "readconf.h"
|
|
#include "defs.h"
|
|
#include "threads.h"
|
|
#include "table.h"
|
|
#include "machines/answmach.h"
|
|
#include "machines/talkconn.h"
|
|
|
|
#include <kinstance.h>
|
|
KTalkdTable * ktable;
|
|
|
|
#define TIMEOUT 20
|
|
/* TIMEOUT was 30, but has been reduced to remove the
|
|
zombie process (answering machine) as soon as possible */
|
|
#define MAXIDLE 120
|
|
/* #define MAXIDLE 30 for debugging purposes */
|
|
|
|
#define NB_MAX_CHILD 20 /* Max number of answering / forwarding machines at a time */
|
|
|
|
#if !defined(MAXHOSTNAMELEN)
|
|
#define MAXHOSTNAMELEN 64
|
|
#endif
|
|
/*char ourhostname[MAXHOSTNAMELEN];*/
|
|
|
|
static time_t lastmsgtime;
|
|
|
|
static void
|
|
timeout(int ignore)
|
|
{
|
|
(void)ignore;
|
|
|
|
int ok_exit = ack_process();
|
|
if (ok_exit && (time(0) - lastmsgtime >= MAXIDLE)) {
|
|
delete ktable;
|
|
_exit(0);
|
|
}
|
|
|
|
signal(SIGALRM, timeout);
|
|
alarm(TIMEOUT);
|
|
}
|
|
|
|
/*
|
|
* Returns true if the address belongs to the local host. If it's
|
|
* not a loopback address, try binding to it.
|
|
*/
|
|
static int
|
|
is_local_address(uint32_t addr)
|
|
{
|
|
struct sockaddr_in sn;
|
|
int sock, ret;
|
|
if (addr == htonl(INADDR_LOOPBACK)) {
|
|
return 1;
|
|
}
|
|
|
|
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (sock<0) {
|
|
syslog(LOG_WARNING, "socket: %s", strerror(errno));
|
|
return 0;
|
|
}
|
|
memset(&sn, 0, sizeof(sn));
|
|
sn.sin_family = AF_INET;
|
|
sn.sin_port = htons(0);
|
|
sn.sin_addr.s_addr = addr;
|
|
ret = bind(sock, (struct sockaddr *)&sn, sizeof(sn));
|
|
close(sock);
|
|
return ret==0;
|
|
}
|
|
|
|
static void
|
|
send_packet(CTL_RESPONSE *response, struct sockaddr_in *sn, int quirk)
|
|
{
|
|
char buf[2*sizeof(CTL_RESPONSE)];
|
|
size_t sz = sizeof(CTL_RESPONSE);
|
|
int cc, err=0;
|
|
|
|
memcpy(buf, response, sz);
|
|
if (quirk) {
|
|
sz = irrationalize_reply(buf, sizeof(buf), quirk);
|
|
}
|
|
while (sz > 0) {
|
|
cc = sendto(1, buf, sz, 0, (struct sockaddr *)sn, sizeof(*sn));
|
|
if (cc<0) {
|
|
syslog(LOG_WARNING, "sendto: %s", strerror(errno));
|
|
if (err) return;
|
|
err = 1;
|
|
}
|
|
else sz -= cc;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Issue an error packet. Should not assume anything other than the
|
|
* header part (the uint8_t's) of mp is valid, and assume as little
|
|
* as possible about that, since it might have been a packet we
|
|
* couldn't dequirk.
|
|
*/
|
|
static void
|
|
send_reject_packet(CTL_MSG *mp, struct sockaddr_in *sn, int code, int quirk)
|
|
{
|
|
CTL_RESPONSE rsp;
|
|
memset(&rsp, 0, sizeof(rsp));
|
|
rsp.vers = TALK_VERSION;
|
|
rsp.type = mp->type;
|
|
rsp.answer = code;
|
|
send_packet(&rsp, sn, quirk);
|
|
}
|
|
|
|
static void
|
|
do_one_packet(void)
|
|
{
|
|
char inbuf[2*sizeof(CTL_MSG)];
|
|
int quirk = 0;
|
|
CTL_RESPONSE response;
|
|
CTL_MSG *mp;
|
|
char theirhost[MAXHOSTNAMELEN];
|
|
const char *theirip;
|
|
|
|
struct hostent *hp;
|
|
struct sockaddr_in sn;
|
|
int cc, i, ok;
|
|
socklen_t addrlen;
|
|
int ret_value = PROC_RETQ_OK; /* return value from process_request */
|
|
|
|
addrlen = sizeof(sn);
|
|
cc = recvfrom(0, inbuf, sizeof(inbuf), 0,
|
|
(struct sockaddr *)&sn, &addrlen);
|
|
if (cc<0) {
|
|
if (errno==EINTR || errno==EAGAIN) {
|
|
return;
|
|
}
|
|
syslog(LOG_WARNING, "recvfrom: %s", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This should be set on any input, even trash, because even
|
|
* trash input will cause us to be restarted if we exit.
|
|
*/
|
|
lastmsgtime = time(NULL);
|
|
|
|
if (addrlen!=sizeof(sn)) {
|
|
syslog(LOG_WARNING, "recvfrom: bogus address length");
|
|
return;
|
|
}
|
|
if (sn.sin_family!=AF_INET) {
|
|
syslog(LOG_WARNING, "recvfrom: bogus address family");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If we get here we have an address we can reply to, although
|
|
* it may not be good for much. If possible, reply to it, because
|
|
* if we just drop the packet the remote talk client will keep
|
|
* throwing junk at us.
|
|
*/
|
|
theirip = inet_ntoa(sn.sin_addr);
|
|
mp = (CTL_MSG *)inbuf;
|
|
|
|
/*
|
|
* Check they're not being weenies.
|
|
* We should look into using libwrap here so hosts.deny works.
|
|
* Wrapping talkd with tcpd isn't very useful.
|
|
*/
|
|
hp = gethostbyaddr((char *)&sn.sin_addr, sizeof(struct in_addr),
|
|
AF_INET);
|
|
if (hp == NULL) {
|
|
syslog(LOG_WARNING, "%s: bad dns", theirip);
|
|
send_reject_packet(mp, &sn, MACHINE_UNKNOWN, 0);
|
|
return;
|
|
}
|
|
strncpy(theirhost, hp->h_name, sizeof(theirhost));
|
|
theirhost[sizeof(theirhost)-1] = 0;
|
|
|
|
hp = gethostbyname(theirhost);
|
|
if (hp == NULL) {
|
|
syslog(LOG_WARNING, "%s: bad dns", theirip);
|
|
send_reject_packet(mp, &sn, MACHINE_UNKNOWN, 0);
|
|
return;
|
|
}
|
|
|
|
for (i=ok=0; hp->h_addr_list[i] && !ok; i++) {
|
|
if (!memcmp(hp->h_addr_list[i], &sn.sin_addr,
|
|
sizeof(sn.sin_addr))) ok = 1;
|
|
}
|
|
if (!ok) {
|
|
syslog(LOG_WARNING, "%s: bad dns", theirip);
|
|
send_reject_packet(mp, &sn, MACHINE_UNKNOWN, 0);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Try to straighten out bad packets.
|
|
*/
|
|
quirk = rationalize_packet(inbuf, cc, sizeof(inbuf), &sn);
|
|
if (quirk<0) {
|
|
print_broken_packet(inbuf, cc, &sn);
|
|
syslog(LOG_WARNING, "%s (%s): unintelligible packet",
|
|
theirhost, theirip);
|
|
send_reject_packet(mp, &sn, UNKNOWN_REQUEST, 0);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Make sure we know what we're getting into.
|
|
*/
|
|
if (mp->vers!=TALK_VERSION) {
|
|
syslog(LOG_WARNING, "%s (%s): bad protocol version %d",
|
|
theirhost, theirip, mp->vers);
|
|
send_reject_packet(mp, &sn, BADVERSION, 0);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* LEAVE_INVITE messages should only come from localhost.
|
|
* Of course, old talk clients send from our hostname's IP
|
|
* rather than localhost, complicating the issue...
|
|
*/
|
|
if (mp->type==LEAVE_INVITE && !is_local_address(sn.sin_addr.s_addr)) {
|
|
syslog(LOG_WARNING, "%s (%s) sent invite packet",
|
|
theirhost, theirip);
|
|
send_reject_packet(mp, &sn, MACHINE_UNKNOWN, quirk);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Junk the reply address they reported for themselves. Write
|
|
* the real one over it because announce.c gets it from there.
|
|
*/
|
|
mp->ctl_addr.ta_family = AF_INET;
|
|
mp->ctl_addr.ta_port = sn.sin_port;
|
|
mp->ctl_addr.ta_addr = sn.sin_addr.s_addr;
|
|
|
|
/*
|
|
* Since invite messages only come from localhost, and nothing
|
|
* but invite messages use the TCP address, force it to be our
|
|
* address.
|
|
*
|
|
* Actually, if it's a local address, leave it alone. talk has
|
|
* to play games to figure out the right interface address to
|
|
* use, and we don't want to get into that - they can work it
|
|
* out, but we can't since we don't know who they're trying to
|
|
* talk to.
|
|
*
|
|
* If it's not a local address, someone's trying to play games
|
|
* with us. Rather than trying to pick a local address to use,
|
|
* reject the packet.
|
|
*/
|
|
if (mp->type==LEAVE_INVITE) {
|
|
mp->addr.ta_family = AF_INET;
|
|
if (!is_local_address(mp->addr.ta_addr)) {
|
|
syslog(LOG_WARNING,
|
|
"invite packet had bad return address");
|
|
send_reject_packet(mp, &sn, BADADDR, quirk);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
/* non-invite packets don't use this field */
|
|
memset(&mp->addr, 0, sizeof(mp->addr));
|
|
}
|
|
|
|
ret_value = process_request(mp, &response, theirhost);
|
|
|
|
if (ret_value != PROC_RETQ_FORWMACH)
|
|
{
|
|
/* can block here, is this what I want? */
|
|
send_packet(&response, &sn, quirk);
|
|
}
|
|
|
|
if (Options.answmach && (ret_value>=PROC_RETQ_MIN_A))
|
|
{
|
|
ktalk_debug("Launch answer machine, mode %d.", ret_value);
|
|
AnswMachine::launchAnswMach(*mp, ret_value);
|
|
new_process();
|
|
}
|
|
}
|
|
|
|
/* the following copies defaults values to 'Options.*' variables so that
|
|
* configuration file can overwrite them */
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct sockaddr_in sn;
|
|
socklen_t sz = sizeof(sn);
|
|
int do_debug=0, do_badpackets=0, ch;
|
|
|
|
new TDEInstance("ktalkd"); // for KConfig and friends
|
|
ktable = new KTalkdTable();
|
|
|
|
/* make sure we're a daemon */
|
|
if (getsockname(0, (struct sockaddr *)&sn, &sz)) {
|
|
const char *msg = strerror(errno);
|
|
write(2, msg, strlen(msg));
|
|
exit(1);
|
|
}
|
|
|
|
openlog(*argv, LOG_PID, LOG_DAEMON);
|
|
|
|
/* get local machine name */
|
|
// using 'uname' instead of 'gethostname', as suggested by Stephan Kulow
|
|
struct utsname buf;
|
|
if (uname (&buf) == -1) {
|
|
syslog (LOG_ERR, "Unable to get name of local host : %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
strcpy(Options.hostname, buf.nodename);
|
|
|
|
/* if (gethostname(Options.hostname, sizeof (Options.hostname) - 1) < 0) {
|
|
syslog(LOG_ERR, "gethostname: %m");
|
|
exit(1);
|
|
}*/
|
|
if (chdir(_PATH_DEV) < 0) {
|
|
syslog(LOG_ERR, "chdir: %s: %s", _PATH_DEV, strerror(errno));
|
|
exit(1);
|
|
}
|
|
while ((ch = getopt(argc, argv, "dp"))!=-1) {
|
|
switch (ch) {
|
|
case 'd':
|
|
Options.debug_mode = 1;
|
|
do_debug=1; break;
|
|
case 'p': do_badpackets=1; break;
|
|
}
|
|
}
|
|
set_debug(do_debug, do_badpackets);
|
|
|
|
TalkConnection::init(); /* global initialization */
|
|
process_config_file(); /* read configuration */
|
|
|
|
signal(SIGALRM, timeout);
|
|
alarm(TIMEOUT);
|
|
for (;;) {
|
|
do_one_packet();
|
|
}
|
|
/* return 0; <--- unreachable because of the above loop */
|
|
}
|