tcp.c 10.4 KB
Newer Older
venaas's avatar
venaas committed
1
/*
2
 * Copyright (C) 2008-2009 Stig Venaas <venaas@uninett.no>
venaas's avatar
venaas committed
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
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 */

#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#ifdef SYS_SOLARIS9
#include <fcntl.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <ctype.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <regex.h>
#include <pthread.h>
#include "radsecproxy.h"
28
#include "hostport.h"
venaas's avatar
venaas committed
29

30 31 32
#ifdef RADPROT_TCP
#include "debug.h"
#include "util.h"
venaas's avatar
venaas committed
33 34
static void setprotoopts(struct commonprotoopts *opts);
static char **getlistenerargs();
venaas's avatar
venaas committed
35 36 37 38
void *tcplistener(void *arg);
int tcpconnect(struct server *server, struct timeval *when, int timeout, char * text);
void *tcpclientrd(void *arg);
int clientradputtcp(struct server *server, unsigned char *rad);
venaas's avatar
venaas committed
39
void tcpsetsrcres();
venaas's avatar
venaas committed
40 41 42 43 44 45 46 47 48 49 50

static const struct protodefs protodefs = {
    "tcp",
    NULL, /* secretdefault */
    SOCK_STREAM, /* socktype */
    "1812", /* portdefault */
    0, /* retrycountdefault */
    0, /* retrycountmax */
    REQUEST_RETRY_INTERVAL * REQUEST_RETRY_COUNT, /* retryintervaldefault */
    60, /* retryintervalmax */
    DUPLICATE_INTERVAL, /* duplicateintervaldefault */
venaas's avatar
venaas committed
51 52
    setprotoopts, /* setprotoopts */
    getlistenerargs, /* getlistenerargs */
venaas's avatar
venaas committed
53 54 55 56 57 58 59 60 61
    tcplistener, /* listener */
    tcpconnect, /* connecter */
    tcpclientrd, /* clientconnreader */
    clientradputtcp, /* clientradput */
    NULL, /* addclient */
    NULL, /* addserverextra */
    tcpsetsrcres, /* setsrcres */
    NULL /* initextra */
};
venaas's avatar
venaas committed
62

venaas's avatar
venaas committed
63
static struct addrinfo *srcres = NULL;
venaas's avatar
venaas committed
64
static uint8_t handle;
venaas's avatar
venaas committed
65
static struct commonprotoopts *protoopts = NULL;
venaas's avatar
venaas committed
66 67 68 69
const struct protodefs *tcpinit(uint8_t h) {
    handle = h;
    return &protodefs;
}
venaas's avatar
venaas committed
70

venaas's avatar
venaas committed
71 72 73 74 75 76 77 78 79
static void setprotoopts(struct commonprotoopts *opts) {
    protoopts = opts;
}

static char **getlistenerargs() {
    return protoopts ? protoopts->listenargs : NULL;
}

void tcpsetsrcres() {
venaas's avatar
venaas committed
80
    if (!srcres)
81 82 83
	srcres =
            resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL,
                                   AF_UNSPEC, NULL, protodefs.socktype);
venaas's avatar
venaas committed
84
}
85

venaas's avatar
venaas committed
86 87 88
int tcpconnect(struct server *server, struct timeval *when, int timeout, char *text) {
    struct timeval now;
    time_t elapsed;
89

venaas's avatar
venaas committed
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
    debug(DBG_DBG, "tcpconnect: called from %s", text);
    pthread_mutex_lock(&server->lock);
    if (when && memcmp(&server->lastconnecttry, when, sizeof(struct timeval))) {
	/* already reconnected, nothing to do */
	debug(DBG_DBG, "tcpconnect(%s): seems already reconnected", text);
	pthread_mutex_unlock(&server->lock);
	return 1;
    }

    for (;;) {
	gettimeofday(&now, NULL);
	elapsed = now.tv_sec - server->lastconnecttry.tv_sec;
	if (timeout && server->lastconnecttry.tv_sec && elapsed > timeout) {
	    debug(DBG_DBG, "tcpconnect: timeout");
	    if (server->sock >= 0)
		close(server->sock);
	    pthread_mutex_unlock(&server->lock);
	    return 0;
	}
	if (server->connectionok) {
	    server->connectionok = 0;
	    sleep(2);
	} else if (elapsed < 1)
	    sleep(2);
	else if (elapsed < 60) {
	    debug(DBG_INFO, "tcpconnect: sleeping %lds", elapsed);
	    sleep(elapsed);
	} else if (elapsed < 100000) {
	    debug(DBG_INFO, "tcpconnect: sleeping %ds", 60);
	    sleep(60);
	} else
	    server->lastconnecttry.tv_sec = now.tv_sec;  /* no sleep at startup */
122

venaas's avatar
venaas committed
123 124
	if (server->sock >= 0)
	    close(server->sock);
125
	if ((server->sock = connecttcphostlist(server->conf->hostports, srcres)) >= 0)
venaas's avatar
venaas committed
126 127
	    break;
    }
128
    server->connectionok = 1;
venaas's avatar
venaas committed
129 130 131 132 133 134 135 136 137 138 139
    gettimeofday(&server->lastconnecttry, NULL);
    pthread_mutex_unlock(&server->lock);
    return 1;
}

/* timeout in seconds, 0 means no timeout (blocking), returns when num bytes have been read, or timeout */
/* returns 0 on timeout, -1 on error and num if ok */
int tcpreadtimeout(int s, unsigned char *buf, int num, int timeout) {
    int ndesc, cnt, len;
    fd_set readfds, writefds;
    struct timeval timer;
140

venaas's avatar
venaas committed
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
    if (s < 0)
	return -1;
    /* make socket non-blocking? */
    for (len = 0; len < num; len += cnt) {
	FD_ZERO(&readfds);
	FD_SET(s, &readfds);
	writefds = readfds;
	if (timeout) {
	    timer.tv_sec = timeout;
	    timer.tv_usec = 0;
	}
	ndesc = select(s + 1, &readfds, &writefds, NULL, timeout ? &timer : NULL);
	if (ndesc < 1)
	    return ndesc;

	cnt = read(s, buf + len, num - len);
	if (cnt <= 0)
	    return -1;
    }
    return num;
}

/* timeout in seconds, 0 means no timeout (blocking) */
unsigned char *radtcpget(int s, int timeout) {
    int cnt, len;
    unsigned char buf[4], *rad;

    for (;;) {
	cnt = tcpreadtimeout(s, buf, 4, timeout);
	if (cnt < 1) {
	    debug(DBG_DBG, cnt ? "radtcpget: connection lost" : "radtcpget: timeout");
	    return NULL;
	}

	len = RADLEN(buf);
176 177 178 179
	if (len < 4) {
	    debug(DBG_ERR, "radtcpget: length too small");
	    continue;
	}
venaas's avatar
venaas committed
180 181 182 183 184 185
	rad = malloc(len);
	if (!rad) {
	    debug(DBG_ERR, "radtcpget: malloc failed");
	    continue;
	}
	memcpy(rad, buf, 4);
186

venaas's avatar
venaas committed
187 188 189 190 191 192
	cnt = tcpreadtimeout(s, rad + 4, len - 4, timeout);
	if (cnt < 1) {
	    debug(DBG_DBG, cnt ? "radtcpget: connection lost" : "radtcpget: timeout");
	    free(rad);
	    return NULL;
	}
193

venaas's avatar
venaas committed
194 195
	if (len >= 20)
	    break;
196

venaas's avatar
venaas committed
197 198 199
	free(rad);
	debug(DBG_WARN, "radtcpget: packet smaller than minimum radius size");
    }
200

venaas's avatar
venaas committed
201 202 203 204 205 206 207 208
    debug(DBG_DBG, "radtcpget: got %d bytes", len);
    return rad;
}

int clientradputtcp(struct server *server, unsigned char *rad) {
    int cnt;
    size_t len;
    struct clsrvconf *conf = server->conf;
209 210 211

    if (!server->connectionok)
	return 0;
venaas's avatar
venaas committed
212
    len = RADLEN(rad);
213
    if ((cnt = write(server->sock, rad, len)) <= 0) {
venaas's avatar
venaas committed
214
	debug(DBG_ERR, "clientradputtcp: write error");
215
	return 0;
venaas's avatar
venaas committed
216
    }
217
    debug(DBG_DBG, "clientradputtcp: Sent %d bytes, Radius packet of length %d to TCP peer %s", cnt, len, conf->name);
venaas's avatar
venaas committed
218 219 220 221 222 223 224
    return 1;
}

void *tcpclientrd(void *arg) {
    struct server *server = (struct server *)arg;
    unsigned char *buf;
    struct timeval lastconnecttry;
225

venaas's avatar
venaas committed
226 227 228 229 230 231 232 233 234
    for (;;) {
	/* yes, lastconnecttry is really necessary */
	lastconnecttry = server->lastconnecttry;
	buf = radtcpget(server->sock, 0);
	if (!buf) {
	    tcpconnect(server, &lastconnecttry, 0, "tcpclientrd");
	    continue;
	}

venaas's avatar
venaas committed
235
	replyh(server, buf);
venaas's avatar
venaas committed
236 237 238 239 240 241 242 243
    }
    server->clientrdgone = 1;
    return NULL;
}

void *tcpserverwr(void *arg) {
    int cnt;
    struct client *client = (struct client *)arg;
244
    struct gqueue *replyq;
venaas's avatar
venaas committed
245
    struct request *reply;
246

247
    debug(DBG_DBG, "tcpserverwr: starting for %s", addr2string(client->addr));
venaas's avatar
venaas committed
248 249 250 251
    replyq = client->replyq;
    for (;;) {
	pthread_mutex_lock(&replyq->mutex);
	while (!list_first(replyq->entries)) {
252
	    if (client->sock >= 0) {
venaas's avatar
venaas committed
253 254 255 256 257 258 259 260 261 262 263
		debug(DBG_DBG, "tcpserverwr: waiting for signal");
		pthread_cond_wait(&replyq->cond, &replyq->mutex);
		debug(DBG_DBG, "tcpserverwr: got signal");
	    }
	    if (client->sock < 0) {
		/* s might have changed while waiting */
		pthread_mutex_unlock(&replyq->mutex);
		debug(DBG_DBG, "tcpserverwr: exiting as requested");
		pthread_exit(NULL);
	    }
	}
venaas's avatar
venaas committed
264
	reply = (struct request *)list_shift(replyq->entries);
venaas's avatar
venaas committed
265
	pthread_mutex_unlock(&replyq->mutex);
venaas's avatar
venaas committed
266
	cnt = write(client->sock, reply->replybuf, RADLEN(reply->replybuf));
venaas's avatar
venaas committed
267
	if (cnt > 0)
268 269
	    debug(DBG_DBG, "tcpserverwr: sent %d bytes, Radius packet of length %d to %s",
		  cnt, RADLEN(reply->replybuf), addr2string(client->addr));
venaas's avatar
venaas committed
270
	else
271
	    debug(DBG_ERR, "tcpserverwr: write error for %s", addr2string(client->addr));
venaas's avatar
venaas committed
272
	freerq(reply);
venaas's avatar
venaas committed
273 274 275 276
    }
}

void tcpserverrd(struct client *client) {
277 278
    struct request *rq;
    uint8_t *buf;
venaas's avatar
venaas committed
279
    pthread_t tcpserverwrth;
280

281
    debug(DBG_DBG, "tcpserverrd: starting for %s", addr2string(client->addr));
282

283
    if (pthread_create(&tcpserverwrth, &pthread_attr, tcpserverwr, (void *)client)) {
venaas's avatar
venaas committed
284 285 286 287 288
	debug(DBG_ERR, "tcpserverrd: pthread_create failed");
	return;
    }

    for (;;) {
289 290
	buf = radtcpget(client->sock, 0);
	if (!buf) {
291
	    debug(DBG_ERR, "tcpserverrd: connection from %s lost", addr2string(client->addr));
venaas's avatar
venaas committed
292 293
	    break;
	}
294
	debug(DBG_DBG, "tcpserverrd: got Radius message from %s", addr2string(client->addr));
295 296 297 298 299 300 301 302
	rq = newrequest();
	if (!rq) {
	    free(buf);
	    continue;
	}
	rq->buf = buf;
	rq->from = client;
	if (!radsrv(rq)) {
303
	    debug(DBG_ERR, "tcpserverrd: message authentication/validation failed, closing connection from %s", addr2string(client->addr));
venaas's avatar
venaas committed
304 305 306 307 308 309 310 311 312 313 314
	    break;
	}
    }

    /* stop writer by setting s to -1 and give signal in case waiting for data */
    client->sock = -1;
    pthread_mutex_lock(&client->replyq->mutex);
    pthread_cond_signal(&client->replyq->cond);
    pthread_mutex_unlock(&client->replyq->mutex);
    debug(DBG_DBG, "tcpserverrd: waiting for writer to end");
    pthread_join(tcpserverwrth, NULL);
315
    debug(DBG_DBG, "tcpserverrd: reader for %s exiting", addr2string(client->addr));
venaas's avatar
venaas committed
316 317 318 319
}
void *tcpservernew(void *arg) {
    int s;
    struct sockaddr_storage from;
venaas's avatar
venaas committed
320
    socklen_t fromlen = sizeof(from);
venaas's avatar
venaas committed
321 322 323 324 325 326 327 328
    struct clsrvconf *conf;
    struct client *client;

    s = *(int *)arg;
    if (getpeername(s, (struct sockaddr *)&from, &fromlen)) {
	debug(DBG_DBG, "tcpservernew: getpeername failed, exiting");
	goto exit;
    }
329
    debug(DBG_WARN, "tcpservernew: incoming TCP connection from %s", addr2string((struct sockaddr *)&from));
venaas's avatar
venaas committed
330

venaas's avatar
venaas committed
331
    conf = find_clconf(handle, (struct sockaddr *)&from, NULL);
venaas's avatar
venaas committed
332
    if (conf) {
333
	client = addclient(conf, 1);
venaas's avatar
venaas committed
334 335
	if (client) {
	    client->sock = s;
336
	    client->addr = addr_copy((struct sockaddr *)&from);
venaas's avatar
venaas committed
337 338 339 340 341 342 343
	    tcpserverrd(client);
	    removeclient(client);
	} else
	    debug(DBG_WARN, "tcpservernew: failed to create new client instance");
    } else
	debug(DBG_WARN, "tcpservernew: ignoring request, no matching TCP client");

344
exit:
venaas's avatar
venaas committed
345 346 347 348 349 350 351 352 353
    shutdown(s, SHUT_RDWR);
    close(s);
    pthread_exit(NULL);
}

void *tcplistener(void *arg) {
    pthread_t tcpserverth;
    int s, *sp = (int *)arg;
    struct sockaddr_storage from;
venaas's avatar
venaas committed
354
    socklen_t fromlen = sizeof(from);
venaas's avatar
venaas committed
355 356 357 358 359 360 361 362 363

    listen(*sp, 0);

    for (;;) {
	s = accept(*sp, (struct sockaddr *)&from, &fromlen);
	if (s < 0) {
	    debug(DBG_WARN, "accept failed");
	    continue;
	}
364
	if (pthread_create(&tcpserverth, &pthread_attr, tcpservernew, (void *)&s)) {
venaas's avatar
venaas committed
365 366 367 368 369 370 371 372 373 374
	    debug(DBG_ERR, "tcplistener: pthread_create failed");
	    shutdown(s, SHUT_RDWR);
	    close(s);
	    continue;
	}
	pthread_detach(tcpserverth);
    }
    free(sp);
    return NULL;
}
375 376 377 378 379
#else
const struct protodefs *tcpinit(uint8_t h) {
    return NULL;
}
#endif
380 381 382 383

/* Local Variables: */
/* c-file-style: "stroustrup" */
/* End: */