Retire MINIX rarpd(8)
Change-Id: I2d7b7edbb8fa6000ba368d156cf947e7691cf454
This commit is contained in:
parent
9cf227216d
commit
34a8cf369f
@ -482,7 +482,7 @@
|
||||
./usr/bin/ps minix-base obsolete
|
||||
./usr/bin/pwhash minix-base
|
||||
./usr/bin/ramdisk minix-base
|
||||
./usr/bin/rarpd minix-base
|
||||
./usr/bin/rarpd minix-base obsolete
|
||||
./usr/bin/rawspeed minix-base
|
||||
./usr/bin/rcp minix-base obsolete
|
||||
./usr/bin/readlink minix-base
|
||||
|
@ -383,7 +383,7 @@
|
||||
./usr/libdata/debug/usr/bin/pwhash.debug minix-debug debug
|
||||
./usr/libdata/debug/usr/bin/ramdisk.debug minix-debug debug
|
||||
./usr/libdata/debug/usr/bin/ranlib.debug minix-debug debug
|
||||
./usr/libdata/debug/usr/bin/rarpd.debug minix-debug debug
|
||||
./usr/libdata/debug/usr/bin/rarpd.debug minix-debug debug,obsolete
|
||||
./usr/libdata/debug/usr/bin/rawspeed.debug minix-debug debug
|
||||
./usr/libdata/debug/usr/bin/readelf.debug minix-debug debug
|
||||
./usr/libdata/debug/usr/bin/recwave.debug minix-debug debug
|
||||
|
@ -3277,7 +3277,7 @@
|
||||
./usr/man/man5/dhcp.conf.5 minix-man obsolete
|
||||
./usr/man/man5/dir.5 minix-man obsolete
|
||||
./usr/man/man5/editrc.5 minix-man
|
||||
./usr/man/man5/ethers.5 minix-man
|
||||
./usr/man/man5/ethers.5 minix-man obsolete
|
||||
./usr/man/man5/fstab.5 minix-man
|
||||
./usr/man/man5/ftpchroot.5 minix-man
|
||||
./usr/man/man5/ftpd.conf.5 minix-man
|
||||
@ -3432,7 +3432,7 @@
|
||||
./usr/man/man8/printroot.8 minix-man
|
||||
./usr/man/man8/pwd_mkdb.8 minix-man
|
||||
./usr/man/man8/pwdauth.8 minix-man obsolete
|
||||
./usr/man/man8/rarpd.8 minix-man
|
||||
./usr/man/man8/rarpd.8 minix-man obsolete
|
||||
./usr/man/man8/rawspeed.8 minix-man
|
||||
./usr/man/man8/rc.8 minix-man
|
||||
./usr/man/man8/rc.d.8 minix-man
|
||||
|
@ -18,7 +18,7 @@ SUBDIR= at backup \
|
||||
minix-service mount mt netconf \
|
||||
prep printroot \
|
||||
profile progressbar \
|
||||
ramdisk rarpd rawspeed readclock \
|
||||
ramdisk rawspeed readclock \
|
||||
remsync rget rlogin \
|
||||
rotate setup \
|
||||
slip spell sprofalyze sprofdiff srccrc \
|
||||
|
@ -1,4 +0,0 @@
|
||||
PROG= rarpd
|
||||
MAN= rarpd.8
|
||||
|
||||
.include <bsd.prog.mk>
|
@ -1,75 +0,0 @@
|
||||
.TH RARPD 8
|
||||
.SH NAME
|
||||
rarpd \- reverse address resolution protocol daemon
|
||||
.SH SYNOPSIS
|
||||
.B rarpd
|
||||
.RB [ \-d [\fIlevel\fR]]
|
||||
.I network-name
|
||||
\&...
|
||||
.SH DESCRIPTION
|
||||
.de SP
|
||||
.if t .sp 0.4
|
||||
.if n .sp
|
||||
..
|
||||
.B Rarpd
|
||||
listens on the given networks for broadcast packets asking for reverse address
|
||||
resolution. These packets are sent by hosts at boot time to find out their
|
||||
IP address.
|
||||
.B Rarpd
|
||||
looks up the six octet ethernet number in the
|
||||
.B /etc/ethers
|
||||
file finding a host name. This name is translated to the IP address of the
|
||||
host by a DNS lookup. The IP address is then sent to the host.
|
||||
.PP
|
||||
Under MINIX 3 the program forks as needed to give each network its own server.
|
||||
Under Minix-vmd all networks are handled in the same program using async I/O.
|
||||
.SS "Private Ethernet Addresses"
|
||||
For VU practical work, where students have to create their own IP stack
|
||||
starting at the bottom with RARP, this implementation recognizes Ethernet
|
||||
addresses starting with octet 0x76 as special. The next octet is used as a
|
||||
additional host number and the next and last four octets as an IP address
|
||||
that this Ethernet address is additional for. The IP address is translated
|
||||
back to a name, and the first component of that name gets a dash and the
|
||||
additional host number added to it. That hostname is then looked up and its
|
||||
IP address returned in a RARP reply. Example:
|
||||
.PP
|
||||
.RS
|
||||
.ta +\w'flotsam-3.example.commmm'u
|
||||
76:3:c0:a8:e7:fa Additional 3, IP 192.168.231.250
|
||||
.SP
|
||||
flotsam.example.com Reverse lookup on 192.168.231.250
|
||||
.SP
|
||||
flotsam-3.example.com Splicing in additional number
|
||||
.SP
|
||||
192.168.231.42 Forward lookup
|
||||
.RE
|
||||
.PP
|
||||
In this example a RARP query for 76:3:c0:a8:e7:fa gets 192.168.231.42 as reply.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-d [\fIlevel\fP]
|
||||
Turns on debugging messages at the given level, by default 1. At level 1 you
|
||||
will be shown what answers are sent, and at level 2 or higher you will be told
|
||||
about queries from unknown hosts or host on the wrong network.
|
||||
The debug level can also be increased by 1 at runtime by sending signal
|
||||
.B SIGUSR1
|
||||
or turned off (set to 0) with
|
||||
.BR SIGUSR2 .
|
||||
.SH "SEE ALSO"
|
||||
.BR ifconfig (8),
|
||||
.BR ethers (5),
|
||||
.BR hosts (5),
|
||||
.BR inet (8),
|
||||
.BR boot (8),
|
||||
.BR dhcpd (8),
|
||||
.BR irdpd (8),
|
||||
.BR inetd (8),
|
||||
.BR nonamed (8).
|
||||
.SH NOTES
|
||||
A "network name" is the device name of the IP device of a network, i.e.
|
||||
.BR ip0 ,
|
||||
.BR ip1 ", ..."
|
||||
.PP
|
||||
The RARP protocol has gone out of fashion in favour of DHCP.
|
||||
.SH AUTHOR
|
||||
Kees J. Bot (kjb@cs.vu.nl)
|
@ -1,370 +0,0 @@
|
||||
/*
|
||||
rarpd.c
|
||||
|
||||
Created: Nov 12, 1992 by Philip Homburg
|
||||
|
||||
Changed: May 13, 1995 by Kees J. Bot
|
||||
Rewrite to handle multiple ethernets.
|
||||
|
||||
Changed: Jul 18, 1995 by Kees J. Bot
|
||||
Do RARP requests (formerly inet's job)
|
||||
|
||||
Changed: Dec 14, 1996 by Kees J. Bot
|
||||
Query the netmask
|
||||
|
||||
Changed: Dec 11, 2000 by Kees J. Bot
|
||||
Dressed down to be only a RARP server, giving the floor to DHCP
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/asynchio.h>
|
||||
#include <net/hton.h>
|
||||
#include <net/gen/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <net/gen/in.h>
|
||||
#include <net/gen/inet.h>
|
||||
#include <net/gen/ether.h>
|
||||
#include <net/gen/eth_io.h>
|
||||
#include <net/gen/if_ether.h>
|
||||
#include <net/gen/ip_io.h>
|
||||
#include <arpa/nameser.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define MAX_RARP_RETRIES 5
|
||||
#define RARP_TIMEOUT 5
|
||||
|
||||
#undef HTONS
|
||||
#define HTONS htons
|
||||
|
||||
typedef struct rarp46
|
||||
{
|
||||
ether_addr_t a46_dstaddr;
|
||||
ether_addr_t a46_srcaddr;
|
||||
ether_type_t a46_ethtype;
|
||||
u16_t a46_hdr, a46_pro;
|
||||
u8_t a46_hln, a46_pln;
|
||||
u16_t a46_op;
|
||||
ether_addr_t a46_sha;
|
||||
u8_t a46_spa[4];
|
||||
ether_addr_t a46_tha;
|
||||
u8_t a46_tpa[4];
|
||||
char a46_padding[ETH_MIN_PACK_SIZE - (4*6 + 2*4 + 4*2 + 2*1)];
|
||||
} rarp46_t;
|
||||
|
||||
#define ETH_RARP_PROTO 0x8035
|
||||
|
||||
#define RARP_ETHERNET 1
|
||||
|
||||
#define RARP_REQUEST 3
|
||||
#define RARP_REPLY 4
|
||||
|
||||
static char *program;
|
||||
static unsigned debug;
|
||||
|
||||
#define between(a, c, z) ((unsigned) (c) - (a) <= (unsigned) (z) - (a))
|
||||
|
||||
static void report(const char *label)
|
||||
{
|
||||
fprintf(stderr, "%s: %s: %s\n", program, label, strerror(errno));
|
||||
}
|
||||
|
||||
static void fatal(const char *label)
|
||||
{
|
||||
report(label);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *allocate(size_t size)
|
||||
{
|
||||
void *mem;
|
||||
|
||||
if ((mem= malloc(size)) == NULL) fatal("Can't allocate memory");
|
||||
return mem;
|
||||
}
|
||||
|
||||
static char *ethdev(int n)
|
||||
{
|
||||
static char an_ethdev[]= "/dev/ethNNN";
|
||||
|
||||
sprintf(an_ethdev + sizeof(an_ethdev)-4, "%d", n);
|
||||
return an_ethdev;
|
||||
}
|
||||
|
||||
static char *ipdev(int n)
|
||||
{
|
||||
static char an_ipdev[]= "/dev/ipNNN";
|
||||
|
||||
sprintf(an_ipdev + sizeof(an_ipdev)-4, "%d", n);
|
||||
return an_ipdev;
|
||||
}
|
||||
|
||||
typedef struct ethernet {
|
||||
int n; /* Network number. */
|
||||
int eth_fd; /* Open low level ethernet device. */
|
||||
ether_addr_t eth_addr; /* Ethernet address of this net. */
|
||||
char packet[ETH_MAX_PACK_SIZE]; /* Incoming packet. */
|
||||
ipaddr_t ip_addr; /* IP address of this net. */
|
||||
ipaddr_t ip_mask; /* Associated netmask. */
|
||||
} ethernet_t;
|
||||
|
||||
static ethernet_t *ethernets;
|
||||
|
||||
static void onsig(int sig)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGUSR1: debug++; break;
|
||||
case SIGUSR2: debug= 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
static void rarp_reply(ethernet_t *ep, char *hostname, ipaddr_t ip_addr,
|
||||
ether_addr_t eth_addr)
|
||||
{
|
||||
rarp46_t rarp46;
|
||||
|
||||
/* Construct a RARP reply packet and send it. */
|
||||
rarp46.a46_dstaddr= eth_addr;
|
||||
rarp46.a46_hdr= HTONS(RARP_ETHERNET);
|
||||
rarp46.a46_pro= HTONS(ETH_IP_PROTO);
|
||||
rarp46.a46_hln= 6;
|
||||
rarp46.a46_pln= 4;
|
||||
rarp46.a46_op= HTONS(RARP_REPLY);
|
||||
rarp46.a46_sha= ep->eth_addr;
|
||||
memcpy(rarp46.a46_spa, &ep->ip_addr, sizeof(ipaddr_t));
|
||||
rarp46.a46_tha= eth_addr;
|
||||
memcpy(rarp46.a46_tpa, &ip_addr, sizeof(ipaddr_t));
|
||||
|
||||
if (debug >= 1) {
|
||||
printf("%s: Replying %s (%s) to %s\n",
|
||||
ethdev(ep->n), inet_ntoa(*(struct in_addr *)&ip_addr), hostname,
|
||||
ether_ntoa(ð_addr));
|
||||
}
|
||||
(void) write(ep->eth_fd, &rarp46, sizeof(rarp46));
|
||||
}
|
||||
|
||||
static int addhostname(char *addname, char *hostname, int n)
|
||||
{
|
||||
/* Create an additional hostname for a given hostname by adding "-n" to
|
||||
* the first part. E.g. given "wombat.cs.vu.nl" and n=2 return
|
||||
* "wombat-2.cs.vu.nl". This is useful for VU practical work where
|
||||
* people get a few extra ethernet addresses on a machine and are asked
|
||||
* to build a TCP/IP stack on it.
|
||||
*/
|
||||
char *dot;
|
||||
|
||||
if (strlen(hostname) + 4 >= 1024) return 0;
|
||||
if ((dot= strchr(hostname, '.')) == NULL) dot= strchr(hostname, 0);
|
||||
sprintf(addname, "%.*s-%d%s", (dot - hostname), hostname, n, dot);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [-d[level]] network-name ...\n", program);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static int ifname2n(const char *name)
|
||||
{
|
||||
/* Translate an interface name, ip0, ip1, etc, to a number. */
|
||||
const char *np;
|
||||
char *end;
|
||||
unsigned long n;
|
||||
|
||||
np= name;
|
||||
if (*np++ != 'i' || *np++ != 'p') usage();
|
||||
n= strtoul(np, &end, 10);
|
||||
if (end == np || *end != 0) usage();
|
||||
if (n >= 1000) {
|
||||
fprintf(stderr, "%s: Network number of \"%s\" is a bit large\n",
|
||||
program, name);
|
||||
exit(1);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
ethernet_t *ep;
|
||||
nwio_ethopt_t ethopt;
|
||||
nwio_ethstat_t ethstat;
|
||||
char hostname[1024];
|
||||
struct hostent *hostent;
|
||||
struct sigaction sa;
|
||||
nwio_ipconf_t ipconf;
|
||||
asynchio_t asyn;
|
||||
ssize_t n;
|
||||
ipaddr_t ip_addr;
|
||||
rarp46_t rarp46;
|
||||
int fd;
|
||||
int n_eths;
|
||||
|
||||
program= argv[0];
|
||||
asyn_init(&asyn);
|
||||
|
||||
debug= 0;
|
||||
i= 1;
|
||||
while (i < argc && argv[i][0] == '-') {
|
||||
char *opt= argv[i++]+1;
|
||||
|
||||
if (opt[0] == '-' && opt[1] == 0) break; /* -- */
|
||||
|
||||
while (*opt != 0) switch (*opt++) {
|
||||
case 'd':
|
||||
debug= 1;
|
||||
if (between('0', *opt, '9')) debug= strtoul(opt, &opt, 10);
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if ((n_eths= (argc - i)) == 0) usage();
|
||||
|
||||
#if __minix_vmd
|
||||
/* Minix-vmd can handle all nets at once using async I/O. */
|
||||
ethernets= allocate(n_eths * sizeof(ethernets[0]));
|
||||
for (i= 0; i < n_eths; i++) {
|
||||
ethernets[i].n= ifname2n(argv[argc - n_eths + i]);
|
||||
}
|
||||
#else
|
||||
/* Minix forks n-1 times to handle each net in a process each. */
|
||||
for (i= 0; i < n_eths; i++) {
|
||||
if (i+1 < n_eths) {
|
||||
switch (fork()) {
|
||||
case -1: fatal("fork()");
|
||||
case 0: break;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
ethernets= allocate(1 * sizeof(ethernets[0]));
|
||||
ethernets[0].n= ifname2n(argv[argc - n_eths + i]);
|
||||
}
|
||||
n_eths= 1;
|
||||
#endif
|
||||
|
||||
sa.sa_handler= onsig;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags= 0;
|
||||
sigaction(SIGUSR1, &sa, NULL);
|
||||
sigaction(SIGUSR2, &sa, NULL);
|
||||
|
||||
for (i= 0; i < n_eths; i++) {
|
||||
ep= ðernets[i];
|
||||
if ((ep->eth_fd= open(ethdev(ep->n), O_RDWR)) < 0) fatal(ethdev(ep->n));
|
||||
|
||||
if (ioctl(ep->eth_fd, NWIOGETHSTAT, ðstat) < 0) {
|
||||
fprintf(stderr, "%s: %s: Unable to get eth statistics: %s\n",
|
||||
program, ethdev(ep->n), strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
ep->eth_addr= ethstat.nwes_addr;
|
||||
if (debug >= 1) {
|
||||
printf("%s: Ethernet address is %s\n",
|
||||
ethdev(ep->n), ether_ntoa(&ep->eth_addr));
|
||||
}
|
||||
|
||||
ethopt.nweo_flags= NWEO_COPY | NWEO_EN_LOC | NWEO_EN_BROAD |
|
||||
NWEO_TYPESPEC;
|
||||
ethopt.nweo_type= HTONS(ETH_RARP_PROTO);
|
||||
|
||||
if (ioctl(ep->eth_fd, NWIOSETHOPT, ðopt) < 0) {
|
||||
fprintf(stderr, "%s: %s: Unable to set eth options: %s\n",
|
||||
program, ethdev(ep->n), strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* What are my address and netmask? */
|
||||
if ((fd= open(ipdev(ep->n), O_RDWR)) < 0) fatal(ipdev(ep->n));
|
||||
if (ioctl(fd, NWIOGIPCONF, &ipconf) < 0) fatal(ipdev(ep->n));
|
||||
|
||||
ep->ip_addr= ipconf.nwic_ipaddr;
|
||||
ep->ip_mask= ipconf.nwic_netmask;
|
||||
close(fd);
|
||||
if (debug >= 1) {
|
||||
printf("%s: IP address is %s / ",
|
||||
ipdev(ep->n), inet_ntoa(*(struct in_addr *)&ep->ip_addr));
|
||||
printf("%s\n", inet_ntoa(*(struct in_addr *)&ep->ip_mask));
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for RARP requests, reply, repeat. */
|
||||
for(;;) {
|
||||
fflush(NULL);
|
||||
|
||||
/* Wait for a RARP request. */
|
||||
for (i= 0; i < n_eths; i++) {
|
||||
ep= ðernets[i];
|
||||
|
||||
n= asyn_read(&asyn, ep->eth_fd, ep->packet, sizeof(ep->packet));
|
||||
if (n != -1) break;
|
||||
if (errno != EINPROGRESS) {
|
||||
report(ethdev(ep->n));
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
/* RARP request? */
|
||||
if (i < n_eths
|
||||
&& n >= sizeof(rarp46)
|
||||
&& (memcpy(&rarp46, ep->packet, sizeof(rarp46)), 1)
|
||||
&& rarp46.a46_hdr == HTONS(RARP_ETHERNET)
|
||||
&& rarp46.a46_pro == HTONS(ETH_IP_PROTO)
|
||||
&& rarp46.a46_hln == 6
|
||||
&& rarp46.a46_pln == 4
|
||||
&& rarp46.a46_op == HTONS(RARP_REQUEST)
|
||||
) {
|
||||
if ((ether_ntohost(hostname, &rarp46.a46_tha) == 0
|
||||
|| (rarp46.a46_tha.ea_addr[0] == 'v'
|
||||
&& (memcpy(&ip_addr, rarp46.a46_tha.ea_addr+2, 4), 1)
|
||||
&& (hostent= gethostbyaddr((char*) &ip_addr,
|
||||
4, AF_INET)) != NULL
|
||||
&& addhostname(hostname, hostent->h_name,
|
||||
rarp46.a46_tha.ea_addr[1])))
|
||||
&& (hostent= gethostbyname(hostname)) != NULL
|
||||
&& hostent->h_addrtype == AF_INET
|
||||
) {
|
||||
/* Host is found in the ethers file and the DNS, or the
|
||||
* ethernet address denotes a special additional address
|
||||
* used for implementing a TCP/IP stack in user space.
|
||||
*/
|
||||
for (i= 0; hostent->h_addr_list[i] != NULL; i++) {
|
||||
memcpy(&ip_addr, hostent->h_addr_list[i], sizeof(ipaddr_t));
|
||||
|
||||
/* Check if the address is on this network. */
|
||||
if (((ip_addr ^ ep->ip_addr) & ep->ip_mask) == 0) break;
|
||||
}
|
||||
|
||||
if (hostent->h_addr_list[i] != NULL) {
|
||||
rarp_reply(ep, hostname, ip_addr, rarp46.a46_tha);
|
||||
} else {
|
||||
if (debug >= 2) {
|
||||
printf("%s: Host '%s' (%s) is on the wrong net\n",
|
||||
ethdev(ep->n),
|
||||
hostname, ether_ntoa(&rarp46.a46_tha));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (debug >= 2) {
|
||||
printf("%s: RARP request from unknown host '%s'\n",
|
||||
ethdev(ep->n), ether_ntoa(&rarp46.a46_tha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for another request. */
|
||||
if (asyn_wait(&asyn, 0, NULL) < 0) {
|
||||
report("asyn_wait()");
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
MAN= boot.cfg.5 configfile.5 crontab.5 ethers.5 \
|
||||
MAN= boot.cfg.5 configfile.5 crontab.5 \
|
||||
fstab.5 keymap.5 \
|
||||
passwd.5 rhosts.5 statvfs.5 \
|
||||
termcap.5 ttytab.5 TZ.5 utmp.5 \
|
||||
|
@ -1,36 +0,0 @@
|
||||
.TH ETHERS 5
|
||||
.SH NAME
|
||||
ethers \- ethernet address to hostname database
|
||||
.SH SYNOPSIS
|
||||
.B /etc/ethers
|
||||
.SH DESCRIPTION
|
||||
The ethers database translates ethernet addresses to hostnames for use by
|
||||
the RARP daemon (see
|
||||
.BR rarpd (8).)
|
||||
.B /etc/ethers
|
||||
may look like this:
|
||||
.PP
|
||||
.RS
|
||||
.ta +20n +10n
|
||||
0:0:c0:a:77:23 flotsam
|
||||
.br
|
||||
0:0:c0:a:68:ce jetsam
|
||||
.RE
|
||||
.PP
|
||||
The six octet ethernet numbers must be entered as shown above, the hex
|
||||
constants must use lowercase letters and may not have leading zeros.
|
||||
Comments are marked with '#'.
|
||||
.PP
|
||||
See
|
||||
.BR rarpd (8)
|
||||
on why you shouldn't list Sun hosts in this file.
|
||||
.SH FILES
|
||||
.TP 15n
|
||||
/etc/ethers
|
||||
Ethernet addresses database.
|
||||
.SH "SEE ALSO"
|
||||
.BR hosts (5),
|
||||
.BR rarpd (8),
|
||||
.BR boot (8).
|
||||
.SH AUTHOR
|
||||
Kees J. Bot (kjb@cs.vu.nl)
|
Loading…
x
Reference in New Issue
Block a user