LDAP + nnrpd

Dan Foster dsf at gblx.net
Sun Nov 26 11:57:51 UTC 2000


Hot Diggety! Katsuhiro Kondou was rumored to have wrote:
> 
> In article <3A1D1B01.B88907D4 at citec.es>,
> 	Jorge Moratilla <jmoratilla at spain.sunedu.com> wrote;
> 
> } I would like to know if is posible to use LDAP to authenticate users in
> } nnrpd instead using RADIUS.  If so, what changes I need to make.  I
> 
> Current inn does not provide ldap auth, but if someone
> writes it, it can easily be incorporated.

Appended at end is a sample nnrpd_auth.pl that works for me. It is very
basic, and a quick proof of concept that I made; should not be used for
INN source distribution without some source code cleanup :)

Senor Moratilla is more than welcome to play around with it.

Basic instructions:

Save the file at the end of this email to the ~news/new_nnrpd.pl. Edit it --
change o=mydomain to whatever root dn you use (two places to change it). Also,
change the hostname of the LDAP server. (one place to change it)

It also assumes your users' dn will be in form of: uid=<username>, ou=People,
o=<your domain>. Feel free to change it if that is not what your setup uses.

*** Make sure *** that you have both the LDAP C SDK libs and Mozilla PerLDAP 
installed on the box you are testing reader functions with. (You can use a
workstation or a test/dev box.) Or none of this will work at all.

See: http://www.mozilla.org/directory/perldap.html for info on where to get
tools and how to install it. After getting both installed, then proceed to
the next step.

Get INN via CVSup. That will give you the latest development snapshot, but if
you are doing this for a production system -- grab the latest release image
via ftp from ftp://ftp.isc.org/isc/inn/ somewhere. I cannot reach ftp.isc.org
right now, so I can't check exact filenames. Sorry.

If you are still doing CVSup, then make a file called inn.cvsup with these
lines:

*default host=inn-cvs.isc.org
*default base=/src/cvsup
*default prefix=/src/inn-dev
*default release=cvs tag=.
*default delete use-rel-suffix
*default compress
inn

Just make sure that /src/cvsup and /src/inn-dev dirs exists, or change these
pathnames. Then do:

# cvsup inn.cvsup

After it pulls down all files, then:

# cd /src/inn-dev/inn

Build INN with ./configure --with-perl flag (and any others that you might
prefer). For example:

# ./configure --with-perl
# make
# make install
# su - news
$ cd etc
then edit various files - normal configuration stuff. afterwards, do this:
$ makedbz -i
$ cd db
$ mv history.n.dir history.dir
$ mv history.n.hash history.hash
$ mv history.n.index history.index
$ cd ~/bin/filter
$ cp nnrpd_auth.pl nnrpd_auth.pl.orig
$ cp ~/new_nnrpd.pl nnrpd_auth.pl
(this assumes the sample file I provided was saved with filename 'new_nnrpd.pl')
$ vi ~/etc/nnrp.access
I have only one line in it:

*::::!*

(that disallows read+post for everyone unless they authenticate via LDAP.)

Also, don't forget to set up /etc/aliases (or /etc/mail/aliases), rebuild it,
set up syslog.conf, touch your news.notice file, kill -HUP syslogd, etc. Then:

$ rc.news

I believe that if you run nnrpd standalone (ie, not out of inetd) then if
you make any changes to the source file, you will have to also do:

$ ctlinnd perl n ; ctlinnd perl y

to make innd reload the perl filters.

To test this stuff, do:

$ telnet localhost 119
<your inn banner message appears>

If it says: InterNetNews server, then give it this command:

mode reader

If it says: InterNetNews NNRP server, then type this command in quickly!
(it must be done within 10 seconds or it will time out and close the conn.)

authinfo user yourusername

after that, enter the next command:

authinfo pass yourpassword

(of course, change 'yourusername' and 'yourpassword' to match your actual
username and password.)

You should either see 281 Ok if auth succeeded, or 502 Authentication Error
if it failed.

You may want to make a short expect script to help you test all of this
without getting tired of entering all this every single time. Be sure to
delete the expect script afterwards because it's not very secure to keep
plaintext passwords in a file like that.

Good luck :) Should work for you if you've done all the steps right. I whipped
up the modifications in about 20 minutes, so there's a lot of room for
improvements :-)

I also wanted to note that nnrp.access use was found in nnrpd_auth.pl before
I modified it, so I just left it alone. Should delete that code entirely, and
just get this information from an extended LDAP schema for each user.

I'm sure I missed some things, or not well optimized (I'm not much of a
programmer, alas) -- feel free to point them out, along with any suggestions
or fixes/improvements.

-Dan

#
# $Id: nnrpd_auth.pl.in,v 1.3 2000/09/18 22:38:49 kondou Exp $
#
# Sample authentication code for nnrpd hook.
# We do this via LDAP.

#
# This file is loaded when nnrpd starts up. If it defines a sub named
# `authenticate', then that function will be called during processing of a
# connect, auth request or disconnect.  Attributes about the connection are 
# passed to the program in the %attributes global variable.  It should return 
# an array with 5 elements:
#
# 1) NNTP response code.  Should be one of the codes from %connectcodes or %authcodes
# 2) Reading Allowed. Should be a boolean value.
# 3) Posting Allowed. Should be a boolean value.
# 4) Wildmat expression that says what groups to provide access to.
# 5) Maximum bytes per second a client is permitted to use for retrieving
#    articles
#
# All five of these are required.  If there is a problem with them then nnrpd
# will die and syslog the exact reason.

#
# Sample Auth program
#

use vars qw(@readerconfig);

use Mozilla::LDAP::Conn;

$defaultgroups = "*";

require "/usr/local/news/lib/innshellvars.pl";

my (%connectcodes) = ("read/post" => 200, "read" => 201, "authneeded" => 480, "permdenied" => 502);
my (%authcodes)    = ("allowed" => 281, "denied" => 502);

%server = (
        "host" => "ldapserver.hostname.here",
        "port" => "389",
        "root" => "ou=People, o=mydomain",
        "cert" => "",
        "scope" => "subtree"
);

sub loadnnrp {
  my($file) = shift(@_);
  my($block, $perm, $user, $pass, %tmp);
  
  open(F, $file) || die "Could not open $file: $!\n";
  
  while (<F>) {
    my (%tmp);
    
    chomp;	
    s/\#.*//g;
    ($block, $perm, $user, $pass, $tmp{groups}) = split(/:/);
    if (!defined($tmp{groups})) {
      undef %tmp;
      next;
    }
    #   Change the glob-type pattern to a regexp
    if ( ! ($block =~ /(\d+\.\d+\.\d+\.\d+)\/(\d+)/))
    {
      #   turn a glob type pattern into a regexp
      #       escape dots
      $block =~ s/\./\./g;
      #       turn ? into .
      $block =~ s/\?/./g;
      #       turn * into .*
      $block =~ s/\*/.*/g;
    }
    $tmp{block} = $block;

    $tmp{canread} = 1 if ($perm =~ /r/i);
    $tmp{canpost} = 1 if ($perm =~ /p/i);
    unshift(@readerconfig, \%tmp);
  }
  close(F);    
}

# This is called by nnrpd when it first starts up.
sub auth_init {
  # ancient crud. we oughta nuke this stuff eventually ;)
  &loadnnrp($inn::newsetc . "/nnrp.access");

  # for standalone nnrpd, optimization would be to make it do a LDAP server
  # connection here, to do it only _once_ and then reuse the open connection
  # for trying each user. (similarly, don't tear down connection after each
  # user lookup, perhaps only upon nnrpd exit if it is posslbe to trap that.)
}

# This is called when a user connects or authenticates
sub authenticate {
  my $key;
  foreach $key (keys %attributes) {
  }
  if ($attributes{type} eq "connect") {
    my (@results) = checkhost();
    return @results;
  } elsif ($attributes{type} eq "authenticate") {
    return checkuser();
  } elsif ($attributes{type} eq "disconnect") {
  }
  return 502;
}

sub ldap_connect {
        Mozilla::LDAP::Utils::userCredentials(\%server);
        $conn = new Mozilla::LDAP::Conn(\%server);

	return $conn;
}

sub ldap_disconnect {
        # Close the connection
        $conn->close if $conn;
}

sub checkuser {
  my $user = $attributes{'username'};
  my $pass = $attributes{'password'};

  # filling in some blanks
  $server{bind} = "uid=$user, ou=People, o=mydomain";
  $server{pswd} = "$pass";

  # try to connect to the LDAP server as the user, with creds
  ldap_connect();

  # if either the user's password didn't match or the LDAP server is
  # unavailable, then we can't proceed any further.
  if (! $conn)
  {
	# error...bailing out. no way to be descriptive to user abt error, alas
	# I'd suggest adding some code to determine what kind of error, and
	# possibly page an admin if server is unreachable or unavailable,
	# as opposed to a simply incorrect password case. this kind of thing
	# is highly site-specific.
	# so.
	# we tell user that their request was denied, and refuse to serve 'em
	# "sorry, pal."
	return ($authcodes{'denied'}, undef, undef, undef, undef);
  }

  # this is only a quick proof of concept example; trivial to extend your
  # LDAP schema to contain attribs to store this info, and to check it
  # for now, we'll just hardcode it to 'y' to allow posting if user auth'd ok
  $news_post = "y";

  # same deal as above, regarding default subscription/groups
  if (!defined($subscription)) {
    $subscription = $defaultgroups;
  }

  # be friendly to the LDAP server, and close the connection
  ldap_disconnect();

  # happy is he who pleases the user with a 'yer good, go for it!'
  return ($authcodes{'allowed'}, 1, $news_post , $subscription, undef);
}

sub permtocode {
  my ($read, $post) = @_;

  return $connectcodes{'read/post'} if ($post);
  return $connectcodes{'read'} if ($read);
  return $connectcodes{'authneeded'};
}

sub checkhost {
  my ($key, $block, $mask, $ip);

  foreach $key (@readerconfig) {
    # Process CIDR style entries first
    my ($read, $post) = ($key->{canread}, $key->{canpost});

    if ($key->{block} =~ /(\d+\.\d+\.\d+\.\d+)\/(\d+)/) {
      $block = unpack('N', pack('C4', split(/\./, $1)));
      $mask = (0xffffffff << (32 - $2)) & 0xffffffff;
      $block = $block & $mask;
      $ip = unpack('N', pack('C4', split(/\./, $attributes{ipaddress})));
      if (($ip & $mask) == $block) {
        return (permtocode($read, $post) , $read, $post, $key->{groups}, undef);
      }
    }
    if ($attributes{ipaddress} =~ /$key->{block}/) {
      return (permtocode($read, $post), $read, $post, $key->{groups}, undef);
    }
    if ($attributes{hostname} =~ /$key->{block}/) {
      return (permtocode($read, $post), $read, $post, $key->{groups}, undef);
    }
  }
  return ($connectcodes{'permdenied'}, undef, undef, undef, undef);
}



More information about the inn-workers mailing list