summaryrefslogtreecommitdiffstats
path: root/ktalkd/ktalkd/talkd.cpp
blob: 0f2f27a08bec26ffc3b459e8bd1ec2401303f85c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
/*
 * 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 kdelibs/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_REQ_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_REQ_FORWMACH)
        {
            /* can block here, is this what I want? */
            send_packet(&response, &sn, quirk);
        }

        if (Options.answmach && (ret_value>=PROC_REQ_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 KInstance("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 */
}