Commit c6d5a774 authored by Kurt Zeilenga's avatar Kurt Zeilenga
Browse files

Zap ldapv2-only stuff

parent 301bd905
......@@ -4,6 +4,5 @@
## Clients for OpenLDAP
SUBDIRS = tools ud
CLEANDIRS = mail500 maildap
SUBDIRS = tools
This is the README file for mail500, a mailer that does X.500 lookups
via LDAP.
If you are planning to run mail500 at your site, there are several
things you will have to tailor in main.c:
LDAPHOST - The host running an LDAP server
base[] - The array telling mail500 where/how to search for
things. See the explanation below.
*** WHAT mail500 DOES: ***
mail500 is designed to be invoked as a mailer (e.g., from sendmail),
similar to the way /bin/mail works. It takes a few required arguments
and then a list of addresses to deliver to. It expects to find the
message to deliver on its standard input. It looks up the addresses in
X.500 to figure out where to route the mail, and then execs sendmail to
do the actual delivery. It supports simple aliases, groups, and
mailing lists, the details of which are given below.
*** HOW IT WORKS (from the sendmail side): ***
The idea is that you might have a rule like this in your
file somewhere in rule set 0:
R$*<>$* $#mail500$$:<$1>
This rule says that any address that ends in will cause
the mail500 mailer to be called to deliver the mail. You probably
also want to do something to prevent addresses like terminator!
or from being passed to mail500.
At U-M, we do this by adding rules like this to rule set 9 where we
strip off our local names:
R<>$*:$* $>10<@>$1:$2
R$+%$+<> $>10$1%$2<@>
R$+!$+<> $>10$1!$2<@>
See the sample in this directory for more details.
The mail500 mailer should be defined similar to this in the file:
Mmail500, P=/usr/local/etc/mail500, F=DFMSmnXuh, A=mail500 -f $f -h $h -m $n@$w $u
This defines how mail500 will be treated by sendmail and what
arguments it will have when it's called. The various flags specified
by the F=... parameter are explained in your local sendmail book (with
any luck). The arguments to mail500 are as follows:
-f Who the mail is from. This will be used as the address
to which any errors should be sent (unless the address
specifies a mailing list - see below). Normally, sendmail
defines the $f macro to be the sender.
-h The domain for which the mail is destined. This is passed
in to mail500 via the $h macro, which is set by the
$@ metasymbol in the rule added to rule set 0 above.
It's normally used when searching for groups.
-m The mailer-daemon address. If errors have to be sent,
this is the address they will come from. $n is normally
set to mailer-daemon and $w is normally the local host
The final argument $u is used to stand for the addresses to which to
deliver the mail.
*** HOW IT WORKS (from the mail500 side): ***
When mail500 gets invoked with one or more names to which to
deliver mail, it searches for each name in X.500. Where it searches,
and what kind(s) of search(es) it does are compile-time configurable
by changing the base array in main.c. For example, the configuration
we use at U-M is like this:
Base base[] =
{ "ou=People, o=University of Michigan, c=US", 0
"uid=%s", "cn=%s", NULL,
"ou=System Groups, ou=Groups, o=University of Michigan, c=US", 1
"(&(cn=%s)(associatedDomain=%h))", NULL, NULL,
"ou=User Groups, ou=Groups, o=University of Michigan, c=US", 1
"(&(cn=%s)(associatedDomain=%h))", NULL, NULL,
which means that in delivering mail to "name" mail500 would do the
the following searches, stopping if it found anything at any step:
Search (18) [2]: c=US@o=University of Michigan@ou=People
Search subtree (uid=name)
Search (18) [3]: c=US@o=University of Michigan@ou=People
Search subtree (cn=name)
Search (18) [4]: c=US@o=University of Michigan@ou=Groups@ou=System Groups
Search subtree & ((cn=name)(
Search (18) [5]: c=US@o=University of Michigan@ou=Groups@ou=User Groups
Search subtree & ((cn=name)(
Notice that when specifying a filter %s is replaced by the name,
or user portion of the address while %h is replaced by whatever is
passed in to mail500 via the -h option (typically the host portion
of the address).
You can also specify whether you want search results that matched
because the entry's RDN matched the search to be given preference
or not. At U-M, we only give such preference in the mail group
portion of the searches. Beware with this option: the algorithm
used to decide whether an entry's RDN matched the search is very
simple-minded, and may not always be correct.
There is currently no limit on the number of areas searched (the base
array can be as large as you want), and an arbitrary limit of 2 filters
for each base. If you want more than that, simply changing the 3 in
the typedef for Base should do the trick.
*** HOW IT WORKS (from the X.500 side): ***
In X.500, there are several new attribute types and one new object
class defined that mail500 makes use of. At its most basic, for normal
entries mail500 will deliver to the value(s) listed in the
rfc822Mailbox attribute of the entry. For example, at U-M my entry has
the attribute
So mail sent to will be delivered via mail500 to that
address. If there were multiple values for the mail attribute, multiple
copies of the mail would be sent.
A new object class, rfc822MailGroup, and several new attributes have
been defined to handle email groups/mailing lists. To use this, you
will need to add this to your local oidtable.oc:
# object class for representing rfc 822 mailgroups
rfc822MailGroup: umichObjectClass.2 : \
top : \
cn : \
rfc822Mailbox, member, memberOfGroup, owner, \
errorsTo, rfc822ErrorsTo, requestsTo, rfc822RequestsTo,
joinable, associatedDomain, \
description, multiLineDescription, \
userPassword, krbName, \
telecommunicationAttributeSet, postalAttributeSet
And you will need to add these to your local
# attrs for rfc822mailgroups
multiLineDescription: umichAttributeType.2 : CaseIgnoreList
rfc822ErrorsTo: umichAttributeType.26 : CaseIgnoreIA5String
rfc822RequestsTo: umichAttributeType.27 : CaseIgnoreIA5String
joinable: umichAttributeType.28 : Boolean
memberOfGroup: umichAttributeType.29 : DN
errorsTo: umichAttributeType.30 : DN
requestsTo: umichAttributeType.31 : DN
The idea was to define a kind of hybrid mail group that could handle
people who were in X.500 or not. So, for example, members of a group
can be specified via the member attribute (for X.500 members) or the
rfc822MailBox attribute (for non-X.500 members). Similarly for the
errorsTo and rfc822ErrorsTo, and the requestsTo and rfc822RequestsTo
To create a real mailing list, with a list maintainer, all you have to
do is create an rfc822MailGroup and fill in the errorsTo or
rfc822ErrorsTo attributes (or both). That will cause any errors
encountered when delivering mail to the group to go to the addresses
listed (or X.500 entry via it's mail attribute).
If you fill in the requestsTo or rfc822RequestsTo (or both) attributes,
mail sent to groupname-request will be sent to the addresses listed
there. mail500 does this automatically, so you don't have to explicitly
add the groupname-request alias to your group.
To allow users to join a group, there is the joinable flag. If TRUE,
mail500 will search for entries that have a memberOfGroup attribute
equal to the DN of the group, using the same algorithm it used to find
the group in the first place (i.e. the DNs and filters listed in the
base array). This allows people to join (or subscribe to) a group
without having to modify the group entry directly. If joinable is
FALSE, the search is not done.
Finally, keep in mind that this is somewhat experimental at the moment.
We are using it in production at U-M, but your mileage may vary...
This diff is collapsed.
# Mostly rfc1123 compliant
# Mail to join
# sendmail-admins carries information
# regarding this, including announcements of changes
# and discussions of interest to admins.
De$j sendmail ($v/$V) ready at $b
DlFrom $g $d
Dq$?x\"$x\" <$g>$|$g$.
H?P?Return-Path: <$g>
HReceived: $?sfrom $s $.by $j ($v/$V)
$?rwith $r $.id $i; $b
H?D?Resent-Date: $a
H?F?Resent-From: $q
H?M?Resent-Message-Id: <$t.$i@$j>
H?M?Message-Id: <$t.$i@$j>
H?D?Date: $a
H?x?Full-Name: $x
H?F?From: $q
Troot uucp daemon
# Organization:
# ruleset 3 and friends
# focus addresses, don't screw with them
# ruleset 0 and friends
# beat the hell out of addresses, convert them to
# their deliverable form
# mailers and associated rulesets
# * focused addresses are body addresses, and should be
# left as they are
# * unfocused addresses are envelope addresses, and should
# be converted to the mailers format
# ruleset 4
# remove focus on all addresses
# All addresses are passed through this rule. It functions by finding
# the host to delivery to, and marking it with <>
R$*<$+>$* $2 remove comments
R$+:$*; $@ $1:$2; done if list
R$*@$+ $: $>5$1@$2 focus rfc822 addresses
R$+!$+ $: $>6$1!$2 focus uucp
R$*<@$+>$* $: $1<@$[$2$]>$3 canonicalize
R$*<$+>$* $@ $1<$2>$3 done if focused
R$+%$+ $: $1@$2 a%b -> a@b
R$+@$+%$+ $1%$2@$3 a@b%c -> a%b@c
R$+@$+ $: $>3$1@$2 try again...
# Find the "next hop" in normal rfc822 syntax. These rules
# all return upon marking the next hop with <>
R@$+,@$+:$+ $@ <@$1>,@$2:$3 @a,@b:@c -> <@a>,@b:c
R@$+:$+ $@ <@$1>:$2 @a:b -> <@a>:b
R$+@$+ $@ $1<@$2> a@b -> a<@b>
# Focus bang style addresses. Won't change already focused addresses.
# Strips .uucp in bang paths, and converts domain syntax to rfc822 adresses.
R$*<$+>$* $@ $1<$2>$3 already focused
R$+!$+ $: <$1!>$2 a!b -> <a!>b
R<$+.uucp!>$+ <$1!>$2 <a.uucp!>b -> <a!>b
# Find a mailer. This involves finding the "real" host to deliver to,
# by removing our local name, and/or doing a "domain forward"
R$+ $: $>7$1 deliverable format
R$*<$+>$* $: $>11$1<$2>$3 domain forward
R<$+!>$+ $: $>12<$1!>$2 route uucp
R$*<@$+.bitnet>$* $#inet$@$B$:$1<@$2.bitnet>$3
R$*<>$* $#mail500$$:<$1>
R$*<>$* $#mail500$$:<$1>
#R<$+!>$+ $#uux$@$U$:<$1!>$2
R<$+!>$+ $#unet$@$U$:<$1!>$2
R$*<@$+>$* $#inet$@$2$:$1<@$2>$3
R$+ $#local$:$1
# Find the delivery address. Convert to standard-internal form,
# remove local name.
R<$-.$+!>$+ $3<@$1.$2> <a.b!>c -> c<@a.b>
R$*<@$-.uucp>$* $>8$1@$2.uucp$3 *.uucp to !
R$*<$+>$* $: $>9$1<$2>$3 strip local name
# Convert rfc822 syntax to a uucp "bang path". This works well
# on normal a@b address and route-addrs. It will also do something
# to list syntax address, but it's not clear how correct it is.
R@$+,@$+:$+ @$1!$2:$3 @a,@b:c -> @a!b:c
R@$+:$+@$+ $1!$3!$2 @a:b@c -> a!c!b
R@$+:$+!$+ $1!$2!$3 @a:b!c -> a!b!c
R$+@$+ $2!$1 a@b -> b!c
R$+ $: $>3$1 refocus
# Remove local names. You won't see things like a.b!u or u@b.uucp.
# Add new rules here to accept more than just the default locally.
R$*<@$w>$* $>10$1<@>$2 remove local name
R<$W!>$+ $>10<!>$1
R<>$*:$* $>10<@>$1:$2
R$+%$+<> $>10$1%$2<@>
R$+!$+<> $>10$1!$2<@>
# Called only from above. Refocus and loop.
R<@>,$+ $>3$1
R<@>:$+ $>3$1
R$+<@> $>3$1
R<!>$+ $>3$1
R$*<$+>$* $: $>7$1<$2>$3
# Convert domain names to uucp names, and refocus
#R$*<>$* $: $>8$1@inquiry$2
# Route uucp addresses, if we're not connected to them. We rely on the
# domain-path operator to down case addresses.
R<$+!>$+ $: <${$1$}!>$2 pathalias route
R<$+!$+!>$+ <$1!>$2!$3 <a!b!>c -> <a!>b!c
Muux, P=/usr/bin/uux, F=DFMhu, S=13, R=14,
A=uux - -gC -b -r -a$f $h!rmail ($u)
Munet, P=[IPC], F=mDFMhuX, S=13, R=14, A=IPC $h, E=\r\n
Minet, P=[IPC], F=mDFMuX, S=15, R=15, A=IPC $h, E=\r\n
Mlocal, P=/bin/mail, F=rlsDFMmn, S=16, R=16, A=mail -d $u
Mprog, P=/bin/sh, F=lsDFMe, S=16, R=16, A=sh -c $u
Mmail500, P=/usr/local/etc/mail500, F=DFMSmnXuh,
A=mail500 -f $f -h $h -m $n@$w $u
# UUCP mailers require that the sender be in ! format.
# XXX Do we add our name to other people's paths?
R$*<@$+>$* $: $>8$1@$2$3
#R<$w!>$+ $@ <$W!>$1
R<$+!>$+ $@ <$1!>$2
R$+:$*; $@ $1:$2;
R<> $@
#R$+ $@ <$W!>$1
R$+ $@ <$w!>$1
# Only add our name to local mail. Anything that's focused, leave alone.
R$*<$+>$* $@ $1<$2>$3
R$+:$*; $@ $1:$2;
#R$+ $@ <$W!>$1
R$+ $@ <$w!>$1
# SMTP mailers require that addresses be in rfc822 format. If there's no
# @ in the address, add one.
R<$W!>$+ $1<@$w>
R<$-.$+!>$+ $3<@$1.$2>
R$*<@$+>$* $@ $1<@$2>$3
R<$+!>$+ $@ $1!$2<@$w>
R$+:$*; $@ $1:$2;
R<> $@
R$+ $@ $1<@$w>
# Local and prog mailer
R$+ $@ $1
# Called on all outgoing addresses. Used to remove the <> focus
R$*<$+>$* $@ $1$2$3 defocus
This diff is collapsed.
* Copyright (c) 1991, 1992 Regents of the University of Michigan.
* All rights reserved.
* Redistribution and use in source and binary forms are permitted
* provided that this notice is preserved and that due credit is given
* to the University of Michigan at Ann Arbor. The name of the University
* may not be used to endorse or promote products derived from this
* software without specific prior written permission. This software
* is provided ``as is'' without express or implied warranty.
#include <stdio.h>
#include <pwd.h>
#include <string.h>
#include <ctype.h>
#include <lber.h>
#include <ldap.h>
#include <ldapconfig.h>
#include "ud.h"
#include <sys/types.h>
#include <krb.h>
extern LDAP *ld; /* our LDAP descriptor */
extern int verbose; /* verbosity indicator */
extern char *mygetpass(); /* getpass() passwds are too short */
#ifdef DEBUG
extern int debug; /* debug flag */
static char tktpath[20]; /* ticket file path */
static int kinit();
static int valid_tgt();
auth(who, implicit)
char *who;
int implicit;
int rc; /* return code from ldap_bind() */
char *passwd = NULL; /* returned by mygetpass() */
char **rdns; /* for fiddling with the DN */
int authmethod;
int name_provided; /* was a name passed in? */
struct passwd *pw; /* for getting user id */
char uidname[20];
char **krbnames; /* for kerberos names */
int kinited, ikrb;
char buf[5];
extern int krb_debug;
LDAPMessage *mp; /* returned from find() */
static char prompt[MED_BUF_SIZE]; /* place for us to sprintf the prompt */
static char name[MED_BUF_SIZE]; /* place to store the user's name */
static char password[MED_BUF_SIZE]; /* password entered by user */
extern struct entry Entry; /* look here for a name if needed */
extern LDAPMessage *find(); /* for looking up 'name' */
extern char *search_base; /* for printing later */
extern char *default_bind_object; /* bind as this on failure */
extern void printbase(); /* used to pretty-print a base */
extern int bind_status;
extern void Free();
static void set_bound_dn();
#ifdef DEBUG
if (debug & D_TRACE)
fprintf(stderr, "auth(%s, NULL)\n", who);
name_provided = ( who != NULL );
* The user needs to bind. If <who> is not specified, we
* assume that authenticating as user id is what user wants.
if (who == NULL && implicit && (pw = getpwuid((uid_t)geteuid()))
!= (struct passwd *) NULL) {
sprintf(uidname, "uid=%s", pw->pw_name);
/* who = pw->pw_name; /* */
who = uidname;
if ( who == NULL ) {
if ( implicit )
printf( "You must first authenticate yourself to the Directory.\n" );
#ifdef UOFM
printf(" What is your name or uniqname? ");
printf(" What is your name or user id? ");
fetch_buffer(name, sizeof(name), stdin);
if (name[0] == '\0')
return( -1 );
who = name;
#ifdef DEBUG
if (debug & D_AUTHENTICAT)
printf(" Authenticating as \"%s\"\n", who);
* Bail out if the name is bogus. If not, strip off the junk
* at the start of the DN, build a prompt, and get a password
* from the user. Then perform the ldap_bind().
if ((mp = find(who, TRUE)) == NULL) {
(void) ldap_msgfree(mp);
printf(" I could not find \"%s\" in the Directory.\n", who);
printf(" I used a search base of ");
printbase("", search_base);
#ifdef DEBUG
if (debug & D_AUTHENTICAT)
printf(" Could not find \"%s\"\n", who);
* Fill in the Entry structure. May be handy later.
(void) parse_answer(mp);
rdns = ldap_explode_dn(Entry.DN, TRUE);
printf(" Authenticating to the directory as \"%s\"...\n", *rdns );
* First, if the user has a choice of auth methods, ask which
* one they want to use. if they want kerberos, ask which
* krbname they want to bind as.
if ( (krbnames = ldap_get_values( ld, mp, "krbName" )) != NULL ) {
int choice, hassimple;
hassimple = (ldap_compare_s( ld, Entry.DN,
"userPassword", "x" ) == LDAP_COMPARE_FALSE);
(void) ldap_msgfree(mp);
/* if we're running as a server (e.g., out of inetd) */
if ( ! isatty( 1 ) ) {
strcpy( tktpath, "/tmp/ud_tktXXXXXX" );
mktemp( tktpath );
krb_set_tkt_string( tktpath );
kinited = valid_tgt( krbnames );
if ( hassimple && !kinited ) {
printf(" Which password would you like to use?\n");
printf(" 1 -> X.500 password\n");
#ifdef UOFM
printf(" 2 -> UMICH password (aka Uniqname or Kerberos password)\n");
printf(" 2 -> Kerberos password\n");
do {
printf(" Enter 1 or 2: ");
fetch_buffer(buf, sizeof(buf), stdin);
choice = atoi(buf);
} while (choice != 1 && choice != 2);
authmethod = (choice == 1 ? LDAP_AUTH_SIMPLE :
} else {
authmethod = LDAP_AUTH_KRBV4;
} else {
authmethod = LDAP_AUTH_SIMPLE;
(void) ldap_msgfree(mp);
* if they are already kinited, we don't need to ask for a
* password.
if ( authmethod == LDAP_AUTH_KRBV4 ) {
if ( ! kinited ) {
if ( krbnames[1] != NULL ) {
int i;
/* ask which one to use */
#ifdef UOFM
printf(" Which UMICH (aka Kerberos or uniqname) name would you like to use?\n");
printf(" Which Kerberos name would you like to use?\n");
for ( i = 0; krbnames[i] != NULL; i++ ) {
printf( " %d -> %s\n", i + 1,
krbnames[i] );
do {
printf(" Enter a number between 1 and %d: ", i );
fflush( stdout );
fetch_buffer(buf, sizeof(buf), stdin);
ikrb = atoi(buf) - 1;
} while ( ikrb > i - 1 || ikrb < 0 );
} else {
ikrb = 0;
/* kinit */
if ( kinit( krbnames[ikrb] ) != 0 ) {
(void) ldap_value_free(rdns);
(void) ldap_value_free(krbnames);
} else {
authmethod = LDAP_AUTH_SIMPLE;
sprintf(prompt, " Enter your X.500 password: ");
do {
passwd = mygetpass(prompt);
} while (passwd != NULL && *passwd == '\0');
if (passwd == NULL) {
(void) ldap_value_free(rdns);
(void) ldap_value_free(krbnames);
ldap_flush_cache( ld