On Tue, May 14, 2002 at 06:38:06PM +0200, JF Bethlehem wrote:
> ok, this sounds interesting, but i have almost 0 experience with forking and
> stuff under linux.
> The threaded nslookup stuff came from a snippet i downloaded (yes,
> occasionally i use snippets,
> esp. when it goes over my head)
> Could you show me some snippet, or tell me how to do this?

Oh, what the heck.

This isn't a patch, since it's way old and my code ain't the same as
yours, so deal with cut and paste.

In comm.c, you need to set some stuff up:

First, in main:

    pid_t pid;
    int fd1[2], fd2[2];

and then below that before the 'run the game' comment:

    /* okay, let's spawn our coprocess that will do our idents */

    if ( pipe(fd1) < 0 || pipe(fd2) < 0 ) {
        bug("fatal error making our pipes!", 0);
        exit( 1 );
    }
    if ( ( pid = fork()) < 0 ) {
        bug("failed to fork ident module.", 0);
        exit( 1 );
    } else if ( pid == 0 ) {
        close(fd1[1]);
        close(fd2[0]);
        if (fd1[0] != STDIN_FILENO) {
            if ( dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO ) {
                bug("couldn't reopen stdin!", 0);
                exit( 1 );
            }
            close(fd1[0]);
        }
        if (fd2[1] != STDOUT_FILENO) {
            if ( dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO ) {
                bug("couldn't reopen stdout!", 0);
                exit( 1 );
            }
            close(fd2[1]);
        }
        if ( execl("./ident", "ident", argv[1], NULL) < 0 ) {
            fprintf(stderr, "couldn't find ident process!\n");
            exit( 1 );
        }
    } else {
        close(fd1[0]);
        close(fd2[1]);
        ident_input = fd2[0];
        ident_output = fd1[1];
        fident_out = fdopen(ident_output, "w");
        fident_in = fdopen(ident_input, "r");
        setvbuf(fident_in, NULL, _IONBF, 0);
        setvbuf(fident_out, NULL, _IONBF, 0);
    }

Got it?  That basically sets up a pair of pipes, forks a child, then
fiddles the pipes around so that output on 'ident_output' gets sent to
the child, and reading from 'ident_input' is whatever the child outputs.
The child reads/writes from stdin and stdout (which makes testing a LOT
easier...)

Why 'ident'?  Because this was originally done not just for DNS, but
also RFC9-whatevr 'ident' lookups.  (Admittedly not very useful these
days, but back when most people mudded from shell accounts, it was very
useful...)

Not very hard stuff, just boring.

Anyway, now we have to actually talk to the child, so way down in the
'main loop' (game_loop_unix()), there's a bit of code to set 'maxdesc'
and FD_SET all the active sockets for the upcoming select().  So right
before the select(), insert:

        FD_SET( ident_input, &in_set );   

So that we can also grab input from the child if it wants to talk to us.
Right before the call to update_handler(), insert:

        if (FD_ISSET( ident_input, &in_set)) {
            char buffer[128];
            buffer[0] = '\0';
            fgets(buffer, 128, fident_in);
            if (*buffer) {
                int id;
                char ident[128];
                if (sscanf(buffer, "%d %s", &id, ident) != 2) {
                    id = 0;
                    strcpy(ident,"[*error*]");
                }
                for ( d = descriptor_list; d != NULL; d = d_next ) {
                    d_next = d->next;
                    if (d->id == id) {
                        d->host = str_dup(ident);
                        d->connected = CON_GET_NAME;
                        break;
                    }
                }
                if ( d && check_ban(d->host,BAN_ALL))
                {
                    write_to_buffer( d,
                     "Your site has been banned from this Mud.\n\r", 0);
                    close_socket( d );
                }
            }
        } 

That reads back what the ident child sent us and parses it.  (The child
sends back a cookie [just a counter, actually], and a string with the
hostname and possibly the ident...)

Now... one thing left to do... send the child our ident info:

Down in init_descriptor (this one is the EASY one!), just after the
'Sock.sinaddr' is logged, add this:

        sprintf( log_buf, "%d  %s %d\n", id, buf, ntohs(sock.sin_port));
        write(fileno(fident_out), log_buf, strlen(log_buf) );

(There is a reason that's done as a 'write'... because it's a lot less
pain to flush and you don't get into dumb fights with stdio's
buffering...  So I'm lazy ... sue me.)

In addition to all that, you need to define a new field in the
'DESCRIPTOR_DATA' for something I call 'id', which is that counter I
mentioned above.  Remember, you're going to be doing asynchrounous
lookups and if more than one person is doing a lookup, you need a way to
sort out whose lookup is whose.  A simple counter (a static in
init_descriptor()) works fine for this.

The child?

It's attached.  As said above, you can use it standalone to play.

[narvi:~/rom/src] 9:58:41am 111 % ./ident 5000
4 12.34.56.78 4000
4 12.34.56.78
5 12.33.21.251 9000
5 narvi.rom.org

Exciting eh?

(Well, the 9000 and 4000 -should- be ports on the -client- machine so
that ident works... but I'm too lazy to play with netstat and find open
sockets.)

Some year I might document this all better, but it's worked for 7 years
now on SunOS (where it was originally done for ROM and MadROM), Solaris
(for both of them again) and Linux.  As I recall the only trick to
Solaris compilation is the usual '-lsocket -lnsl' since Solaris sucks
and refuses to put those in the standard library.


/*
 * get_ident: a coprocess for ROM to get user identification.
 *
 * Copyright 1995 by Brian Moore   [EMAIL PROTECTED]
 * All Rights Reserved.
 *
 */

/*
 we receive on stdin a string of the following type:
<id#> <dotted.ip> <port>

For example:
13801284 198.68.17.122 1238

We return the id and ident/site info:
13801284 [email protected]
13801338 identless.site.edu

*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>

#define MAXLINE		128		/* more than ample for our input */

char * get_ident( char *, int, int);
static void sig_alrm(int);

int got_alarm = 0;

int main(int argc, char *argv[])
{
    char line[MAXLINE];
    char host_ip[MAXLINE];
    int our_port;
    int id, port;
    int n;

    if (argc != 2) {
        fprintf(stderr, "usage: %s port\n", argv[0]);
        exit(1);
    }
    our_port = atoi(argv[1]);
    if (our_port < 0 || our_port > 65535) {
        fprintf(stderr, "please use a proper port number.\n");
        exit(1);
    }
    
    while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0) {
        int pid;
        line[n] = '\0';
        id = -1;
        if (sscanf(line,"%d %80s %d", &id, host_ip, &port) == 3) {
            if ( (pid = fork()) == 0) {
                if ( fork() == 0) {
                    sprintf(line, "%d %s\n", id,
                             get_ident(host_ip, port, our_port));
                    if (write(fileno(stdout), line, strlen(line)) == 0 ) {
                        fprintf(stderr,"output failed.");
                        exit(1);
                    }
                    exit(0);
                } else
                    exit(0);
            } else 
                waitpid(pid, NULL, 0);
        } else {
            if (printf("%d [invalid]\n", id) == EOF) {
                fprintf(stderr,"output failed.");
                }
        }
    }
    return 0;
}

char *
get_ident(char * host, int port, int our_port)
{
    static char result[MAXLINE], ident[MAXLINE];
    struct hostent  *hostent;
    struct sockaddr_in sin;
    struct sockaddr_in my_addr;
    static struct sockaddr_in sa_zero;
    struct sigaction act;
    int fd = -1;
    FILE *fp;
    char *p;
    int ip;

    result[0] = ident[0] = '\0';      /* mark that we know nothing */

    ip = inet_addr(host);

    act.sa_flags = ( SA_ONESHOT | SA_NODEFER);
    act.sa_handler = sig_alrm;
    if ( sigaction( SIGALRM, &act, NULL) ) {
        fprintf(stderr,"sigalarm can't be set!");
        exit(1);
    }

/* The following is to deal with slow DNS and systems that are slow
   for us to re-connect back to for an ident check.  We give it 10
   seconds for both, which should be ample. */

    hostent = NULL;

    alarm(10);           	     /* allow 20 seconds before failure... */
    hostent = gethostbyaddr( (char *) &ip, sizeof(ip), AF_INET );

    if ( hostent && !got_alarm ) {
        /* if we have time left, get the ident */
        /* stylistic argument: some say this should be AF_INET, not PF_INET.
           Since socket() isn't getting an address, just defining a protocol
           for a descriptor, PF ("protocol family") seems more correct to
           me, and it matches the SunOS man pages. */
        if ( ( fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) {
            fprintf(stderr,"socket failed");
            exit(1);
        }
        my_addr = sa_zero;
        my_addr.sin_family = AF_INET;
	my_addr.sin_addr.s_addr = INADDR_ANY;
        if ( ( bind( fd, (struct sockaddr *) &my_addr, 
                                           sizeof(struct sockaddr_in))) < 0 ) {
            fprintf(stderr,"bind failed\n");
            exit(1);
        }

        /* Now THIS one, since we're filling in the address here, is
           AF (address family). */
        sin.sin_family = AF_INET;
        sin.sin_port = htons(113);    /* the ident port */
        bcopy(hostent->h_addr, &sin.sin_addr, hostent->h_length);

        if (connect(fd, &sin, sizeof(sin)) == 0) {
            char string[80];
            fp = fdopen(fd, "r");
            sprintf(string,"%d , %d\n", port, our_port);
            write(fd, string, strlen(string));
            fgets(string, 80, fp);
            sscanf(string, 
                "%*d , %*d : %*[^ \t\n\r:] : %*[^ \t\n\r:] : %20[^\n\r]",
                ident);
        }
        alarm(0);
        for ( p = ident; *p; p++ ) {
            *p &= 0x7f;     /* strip hi bit */
            *p = *p < ' ' ? *p + '@' : *p;    /* kill control characters */
            if (*p == '~' || *p == '@')
                *p = '-';    /* protect two characters */
        }
    }

    if (fd > 0)
        close(fd);

    sprintf(result, "%s%s%.60s",
        *ident ? ident : "",
        *ident ? "@" : "",
        hostent ? hostent->h_name : host );

    return result;
}

/* nothing really to do when the timer expires... just continue with where
   we left off */

static void
sig_alrm(int signo)
{
    got_alarm = 1;
    fprintf(stderr, "yowza!\n" );
    return;
}

Reply via email to