Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • openldap/openldap
  • hyc/openldap
  • ryan/openldap
  • iboukris/openldap
  • ondra/openldap
  • sshanks-kx/openldap
  • blaggacao/openldap
  • pbrezina/openldap
  • quanah/openldap
  • dragos_h/openldap
  • lorenz/openldap
  • tsaarni/openldap
  • fei.ding/openldap
  • orent/openldap
  • arrowplum/openldap
  • barchiesi/openldap
  • jotik/openldap
  • hamano/openldap
  • ingovoss/openldap
  • henson/openldap
  • jlrine2/openldap
  • howeverAT/openldap
  • nivanova/openldap
  • orbea/openldap
  • rdubner/openldap
  • smckinney/openldap
  • jklowden/openldap
  • dpa-openldap/openldap
  • rouzier/openldap
  • orgads/openldap
  • ffontaine/openldap
  • jiaqingz/openldap
  • dcoutadeur/openldap
  • begeragus/openldap
  • pubellit/openldap
  • glandium/openldap
  • facboy/openldap
  • thesamesam/openldap
  • Johan/openldap
  • fkooman/openldap
  • gburd/openldap
  • h-homma/openldap
  • sgallagher/openldap
  • ahmed_zaki/openldap
  • gnoe/openldap
  • mid/openldap
  • clan/openldap
47 results
Show changes
Showing
with 2923 additions and 5315 deletions
##
## Makefile for gopher clients
##
PROGRAMS= go500gw go500
SRCS= go500.c go500gw.c
XSRCS= gwversion.c goversion.c
GOOBJS = go500.o
GWOBJS = go500gw.o
LDAP_INCDIR= ../../include
LDAP_LIBDIR= ../../libraries
XLIBS = -lldap -llber -llutil
XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS)
go500 : goversion.o
$(LTLINK) -o $@ $(GOOBJS) goversion.o $(LIBS)
go500gw : gwversion.o
$(LTLINK) -o $@ $(GWOBJS) gwversion.o $(LIBS)
goversion.c: ${GOOBJS} $(LDAP_LIBDEPEND)
@-$(RM) $@
$(MKVERSION) go500 > $@
gwversion.c: ${GWOBJS} $(LDAP_LIBDEPEND)
@-$(RM) $@
$(MKVERSION) go500gw > $@
install-local: $(PROGRAMS) go500gw.help FORCE
-$(MKDIR) $(libexecdir) $(datadir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 go500 $(libexecdir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 go500gw $(libexecdir)
-$(MV) $(datadir)/go500gw.help $(datadir)/go500gw.help-
$(INSTALL) $(INSTALLFLAGS) -m 644 $(srcdir)/go500gw.help $(datadir)
This directory contains source for two programs:
go500 A gopher index search server to X.500 gateway
go500gw A more general gopher to X.500 gateway
Both programs use the LDAP protocol to talk to X.500.
What you are trying to set up looks like this:
-------- ----------------- -------- --------
| gopher | | gopher to| LDAP | | LDAP | | X.500 |
| client |<- gophr ->| LDAP g/w | API |<- LDAP ->| server |<- DAP ->| server |
|________| |__________|______| |________| |________|
go500gw ldapd
or
go500
Both go500 and go500gw can be run either from inetd or as stand-alone
servers.
go500 is useful when you always want to search a fixed portion of the X.500
tree. It does not let you browse around or change where you search.
go500gw is useful when you want to provide a more general and flexible
gateway from gopher to X.500. It allows users to browse around anywhere
in the X.500 tree, doing searches at any point.
**************************************************************************
* RUNNING go500 *
**************************************************************************
1) Make and install the ldap distribution if you have not already done so:
(cd ../; make lib-only; make inst-lib)
or
(cd ../; make all; make install)
Use the second form if you don't already have an ldapd running
somewhere you can connect to. Note that to make an ldap server,
you will need the ISODE libraries and include files.
2) Tailor go500 to your site before compiling:
vi go500.c
There are a couple of things to change in go500.c:
DAPUSER - This is the DN go500 will bind to the directory as.
You can specify NULL if you want.
DEFAULT_BASE - This is the DN of the object below which go500
will conduct its search. You typically want this to
be the DN or your organization.
DEFAULT_LDAPHOST - This is the host that is running the ldap
server. If it's not on the localhost, you'll need to
change this.
3) Make go500:
make
4) Start the ldap daemon:
ldapd [-c dsaname]
You only need to do this if you're not already running one.
5) Start go500 as a stand-alone server:
go500
or arrange to have it start from inetd:
# vi /etc/services /* add the following line */
go500 5555/tcp go500 # go500 server
# vi /etc/inetd.conf /* add the following line */
go500 stream tcp nowait nobody $(ETCDIR)/go500 go500 -I
# kill -HUP <inetdpid> /* make inetd notice the change */
where $(ETCDIR) is replaced by the ETCDIR from the top level Makefile
and <inetdpid> is replaced by the pid of the inetd process.
6) Configure your local gopher server to have an entry for go500.
A sample .link file is given below, with the things you should
change given in <>'s:
Name=<Label of your choice>
Type=7
Port=5555
Path=
Host=<host.running.go500.here>
You may also have to restart your gopher daemon, or remove
the .cache file.
7) Run a gopher client and try it out.
**************************************************************************
* RUNNING go500gw *
**************************************************************************
1) Make the ldap distribution if you have not already done so:
(cd ../; make lib-only)
or
(cd ../; make lib-only ldap-server)
Use the second form if you don't already have an ldapd running
somewhere you can connect to. Note that to make an ldap server,
you will need the ISODE libraries and include files.
2) Tailor go500gw to your site before compiling:
vi go500gw.c
There are a couple of things to change in go500gw.c:
GO500DN - This is the DN go500gw will bind to the directory as.
You can specify NULL if you want.
HELPFILE - This is the pathname of the helpfile (actually, the
file that's displayed when a user chooses "About the
Gopher to X.500 Gateway").
LDAPHOST - This is the host that is running the ldap server.
If it's not on the localhost, you'll need to change this.
3) Make go500gw:
make
4) Start the ldap daemon:
ldapd [-c dsaname]
You only need to do this if you're not already running one.
5) Start go500gw either as a stand-alone server:
go500gw
or arrange to have it start from inetd:
# vi /etc/services /* add the following line */
go500gw 5555/tcp go500gw # go500gw server
# vi /etc/inetd.conf /* add the following line */
go500gw stream tcp nowait nobody $(ETCDIR)/go500gw go500gw -I
# kill -HUP <inetdpid> /* make inetd notice the change */
where $(ETCDIR) is replaced by the ETCDIR from the top level Makefile
and <inetdpid> is replaced by the pid of the inetd process.
6) Configure your local gopher server to have an entry for go500gw.
A sample .link file is given below, with the things you should
change given in <>'s:
Name=<Label of your choice>
Type=1
Port=7777
Path=M
Host=<host.running.go500gw.here>
You may also have to restart your gopher daemon, or remove
the .cache file.
If you want to start go500gw at some point in the X.500 tree
other than the root, you can change the Path parameter above
to something like this, for example:
Path=Mo=University of Michigan,c=US
Of course, you would substitute your own organization's DN.
7) Run a gopher client and try it out.
/*
* Copyright (c) 1990 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 "portable.h"
#include <stdio.h>
#include <ac/stdlib.h>
#include <ac/ctype.h>
#include <ac/signal.h>
#include <ac/socket.h>
#include <ac/string.h>
#include <ac/syslog.h>
#include <ac/time.h>
#include <ac/unistd.h>
#include <ac/wait.h>
#include <ac/setproctitle.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <ldap.h>
#include <disptmpl.h>
#include "ldap_defaults.h"
#define ldap_debug debug
#include "ldap_log.h"
#include "lutil.h"
int debug;
int dosyslog;
int ldap_syslog;
int ldap_syslog_level;
int inetd;
int dtblsize;
char *ldaphost = NULL;
char *base = NULL;
int rdncount = GO500_RDNCOUNT;
char *filterfile = FILTERFILE;
char *templatefile = TEMPLATEFILE;
char myhost[MAXHOSTNAMELEN];
int myport;
static void usage ( char *name );
static int set_socket (int port);
static RETSIGTYPE wait4child(int sig);
static void do_queries (int s);
static void do_error (FILE *fp, char *s);
static void do_search (LDAP *ld, FILE *fp, char *buf);
static void do_read (LDAP *ld, FILE *fp, char *dn);
static void
usage( char *name )
{
fprintf( stderr, "usage: %s [-d debuglevel] [-f filterfile] [-t templatefile]\r\n\t[-a] [-l] [-p port] [-x ldaphost] [-b searchbase] [-c rdncount]\r\n", name );
exit( EXIT_FAILURE );
}
int
main( int argc, char **argv )
{
int s, ns, rc;
int port = -1;
int i, pid;
char *myname;
fd_set readfds;
struct hostent *hp;
struct sockaddr_in from;
socklen_t fromlen;
#if defined( LDAP_PROCTITLE ) && !defined( HAVE_SETPROCTITLE )
/* for setproctitle */
Argv = argv;
Argc = argc;
#endif
while ( (i = getopt( argc, argv, "b:d:f:lp:c:t:x:I" )) != EOF ) {
switch( i ) {
case 'b': /* searchbase */
base = strdup( optarg );
break;
case 'd': /* debug level */
debug |= atoi( optarg );
break;
case 'f': /* ldap filter file */
filterfile = strdup( optarg );
break;
case 'l': /* log via LOG_LOCAL3 */
dosyslog = 1;
break;
case 'p': /* port to listen to */
port = atoi( optarg );
break;
case 'c': /* number of DN components to show */
rdncount = atoi( optarg );
break;
case 't': /* ldap template file */
templatefile = strdup( optarg );
break;
case 'x': /* ldap server hostname */
ldaphost = strdup( optarg );
break;
case 'I': /* run from inetd */
inetd = 1;
break;
default:
usage( argv[0] );
}
}
#ifdef GO500_HOSTNAME
strcpy( myhost, GO500_HOSTNAME );
#else
if ( myhost[0] == '\0' && gethostname( myhost, sizeof(myhost) )
== -1 ) {
perror( "gethostname" );
exit( EXIT_FAILURE );
}
#endif
#ifdef HAVE_SYSCONF
dtblsize = sysconf( _SC_OPEN_MAX );
#elif HAVE_GETDTABLESIZE
dtblsize = getdtablesize();
#else
dtblsize = FD_SETSIZE;
#endif
#ifdef FD_SETSIZE
if (dtblsize > FD_SETSIZE) {
dtblsize = FD_SETSIZE;
}
#endif /* FD_SETSIZE*/
/* detach if stderr is redirected or no debugging */
if ( inetd == 0 )
lutil_detach( debug && !isatty( 1 ), 1 );
if ( (myname = strrchr( argv[0], '/' )) == NULL )
myname = strdup( argv[0] );
else
myname = strdup( myname + 1 );
if ( debug ) {
ber_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug);
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug);
}
#ifdef SIGPIPE
(void) SIGNAL( SIGPIPE, SIG_IGN );
#endif
if ( dosyslog ) {
#ifdef LOG_LOCAL3
openlog( myname, OPENLOG_OPTIONS, LOG_LOCAL3 );
#else
openlog( myname, OPENLOG_OPTIONS );
#endif
}
if ( dosyslog )
syslog( LOG_INFO, "initializing" );
/* set up the socket to listen on */
if ( inetd == 0 ) {
s = set_socket( port );
/* arrange to reap children */
(void) SIGNAL( SIGCHLD, wait4child );
} else {
myport = GO500_PORT;
fromlen = sizeof(from);
if ( getpeername( 0, (struct sockaddr *) &from, &fromlen )
== 0 ) {
hp = gethostbyaddr( (char *) &(from.sin_addr.s_addr),
sizeof(from.sin_addr.s_addr), AF_INET );
Debug( LDAP_DEBUG_ARGS, "connection from %s (%s)\n",
(hp == NULL) ? "unknown" : hp->h_name,
inet_ntoa( from.sin_addr ), 0 );
if ( dosyslog ) {
syslog( LOG_INFO, "connection from %s (%s)",
(hp == NULL) ? "unknown" : hp->h_name,
inet_ntoa( from.sin_addr ) );
}
#ifdef LDAP_PROCTITLE
setproctitle( hp == NULL ? inet_ntoa( from.sin_addr ) :
hp->h_name );
#endif
}
do_queries( 0 );
exit( EXIT_SUCCESS );
}
for ( ;; ) {
FD_ZERO( &readfds );
FD_SET( s, &readfds );
if ( (rc = select( dtblsize, &readfds, 0, 0 ,0 )) == -1 ) {
if ( debug ) perror( "select" );
continue;
} else if ( rc == 0 ) {
continue;
}
if ( ! FD_ISSET( s, &readfds ) )
continue;
fromlen = sizeof(from);
if ( (ns = accept( s, (struct sockaddr *) &from, &fromlen ))
== -1 ) {
if ( debug ) perror( "accept" );
exit( EXIT_FAILURE );
}
hp = gethostbyaddr( (char *) &(from.sin_addr.s_addr),
sizeof(from.sin_addr.s_addr), AF_INET );
if ( dosyslog ) {
syslog( LOG_INFO, "TCP connection from %s (%s)",
(hp == NULL) ? "unknown" : hp->h_name,
inet_ntoa( from.sin_addr ) );
}
switch( pid = fork() ) {
case 0: /* child */
tcp_close( s );
do_queries( ns );
break;
case -1: /* failed */
perror( "fork" );
break;
default: /* parent */
tcp_close( ns );
if ( debug )
fprintf( stderr, "forked child %d\n", pid );
break;
}
}
/* NOT REACHED */
}
static int
set_socket( int port )
{
int s, one;
struct sockaddr_in addr;
if ( port == -1 )
port = GO500_PORT;
myport = port;
if ( (s = socket( AF_INET, SOCK_STREAM, 0 )) == -1 ) {
perror( "socket" );
exit( EXIT_FAILURE );
}
#ifdef SO_REUSEADDR
/* set option so clients can't keep us from coming back up */
one = 1;
if ( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
sizeof(one) ) < 0 ) {
perror( "setsockopt" );
exit( EXIT_FAILURE );
}
#endif
#ifdef SO_KEEPALIVE
/* enable keep alives */
one = 1;
if ( setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, (char *) &one,
sizeof(one) ) < 0 ) {
perror( "setsockopt" );
exit( EXIT_FAILURE );
}
#endif
/* bind to a name */
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons( port );
if ( bind( s, (struct sockaddr *) &addr, sizeof(addr) ) ) {
perror( "bind" );
exit( EXIT_FAILURE );
}
/* listen for connections */
if ( listen( s, 5 ) == -1 ) {
perror( "listen" );
exit( EXIT_FAILURE );
}
if ( debug ) printf("tcp socket allocated, bound, and listening\n");
return( s );
}
static RETSIGTYPE
wait4child( int sig )
{
#ifndef HAVE_WAITPID
WAITSTATUSTYPE status;
#endif
if ( debug ) printf( "parent: catching child status\n" );
#ifdef HAVE_WAITPID
while (waitpid ((pid_t) -1, (int *) NULL, WAIT_FLAGS) > 0)
; /* NULL */
#else
while ( wait4((pid_t) -1, &status, WAIT_FLAGS, 0 ) > 0 )
; /* NULL */
#endif
(void) SIGNAL( SIGCHLD, wait4child );
}
static void
do_queries( int s )
{
char buf[1024], *query;
int len;
FILE *fp;
int rc;
struct timeval timeout;
fd_set readfds;
LDAP *ld;
if ( (fp = fdopen( s, "a+")) == NULL ) {
exit( EXIT_FAILURE );
}
timeout.tv_sec = GO500_TIMEOUT;
timeout.tv_usec = 0;
FD_ZERO( &readfds );
FD_SET( fileno( fp ), &readfds );
if ( (rc = select( dtblsize, &readfds, 0, 0, &timeout )) <= 0 )
exit( EXIT_FAILURE );
if ( fgets( buf, sizeof(buf), fp ) == NULL )
exit( EXIT_FAILURE );
len = strlen( buf );
if ( debug ) {
fprintf( stderr, "got %d bytes\n", len );
#ifdef LDAP_DEBUG
ber_bprint( buf, len );
#endif
}
/* strip of \r \n */
if ( buf[len - 1] == '\n' )
buf[len - 1] = '\0';
len--;
if ( buf[len - 1] == '\r' )
buf[len - 1] = '\0';
len--;
query = buf;
/* strip off leading white space */
while ( isspace( (unsigned char) *query )) {
++query;
--len;
}
rewind(fp);
if ( *query == '~' || *query == '@' ) {
ld = NULL;
} else if ( (ld = ldap_init( ldaphost, 0 )) == NULL ) {
fprintf(fp,
"0An error occurred (explanation)\t@%d\t%s\t%d\r\n",
LDAP_SERVER_DOWN, myhost, myport );
fprintf( fp, ".\r\n" );
rewind(fp);
exit( EXIT_FAILURE );
} else {
int deref = GO500_DEREF;
ldap_set_option(ld, LDAP_OPT_DEREF, &deref);
rc = ldap_simple_bind_s( ld, NULL, NULL );
if ( rc != LDAP_SUCCESS ) {
fprintf(fp,
"0An error occurred (explanation)\t@%d\t%s\t%d\r\n",
rc, myhost, myport );
fprintf( fp, ".\r\n" );
rewind(fp);
exit( EXIT_FAILURE );
}
}
switch ( *query ) {
case '~':
fprintf( fp, "The query you specified was not specific enough, causing a size limit\r\n" );
fprintf( fp, "to be exceeded and the first several matches found to be returned.\r\n" );
fprintf( fp, "If you did not find the match you were looking for, try issuing a more\r\n" );
fprintf( fp, "specific query, for example one that contains both first and last name.\r\n" );
fprintf( fp, ".\r\n" );
break;
case '=':
do_read( ld, fp, ++query );
break;
case '@':
do_error( fp, ++query );
break;
default:
do_search( ld, fp, query );
break;
}
fprintf( fp, ".\r\n" );
rewind(fp);
if ( ld != NULL) {
ldap_unbind( ld );
}
exit( EXIT_FAILURE );
/* NOT REACHED */
}
static void
do_error( FILE *fp, char *s )
{
int code;
code = atoi( s );
fprintf( fp, "An error occurred searching X.500. The error code was %d\r\n", code );
fprintf( fp, "The corresponding error is: %s\r\n", ldap_err2string( code ) );
fprintf( fp, "No additional information is available\r\n" );
fprintf( fp, ".\r\n" );
}
static void
do_search( LDAP *ld, FILE *fp, char *buf )
{
char *dn, *rdn;
char **title;
int rc, matches = 0;
struct timeval tv;
LDAPFiltInfo *fi;
LDAPFiltDesc *filtd;
LDAPMessage *e, *res;
static char *attrs[] = { "title", 0 };
#ifdef GO500_UFN
if ( strchr( buf, ',' ) != NULL ) {
ldap_ufn_setprefix( ld, base );
tv.tv_sec = GO500_TIMEOUT;
tv.tv_usec = 0;
ldap_ufn_timeout( (void *) &tv );
if ( (rc = ldap_ufn_search_s( ld, buf, attrs, 0, &res ))
!= LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED ) {
fprintf(fp,
"0An error occurred (explanation)\t@%d\t%s\t%d\r\n",
rc, myhost, myport );
return;
}
matches = ldap_count_entries( ld, res );
} else {
#endif
if ( (filtd = ldap_init_getfilter( filterfile )) == NULL ) {
fprintf( stderr, "Cannot open filter file (%s)\n",
filterfile );
exit( EXIT_FAILURE );
}
tv.tv_sec = GO500_TIMEOUT;
tv.tv_usec = 0;
for ( fi = ldap_getfirstfilter( filtd, "go500", buf );
fi != NULL;
fi = ldap_getnextfilter( filtd ) )
{
if ( (rc = ldap_search_st( ld, base, LDAP_SCOPE_SUBTREE,
fi->lfi_filter, attrs, 0, &tv, &res ))
!= LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED ) {
fprintf(fp, "0An error occurred (explanation)\t@%d\t%s\t%d\r\n",
rc, myhost, myport );
ldap_getfilter_free( filtd );
return;
}
if ( (matches = ldap_count_entries( ld, res )) != 0 )
break;
}
ldap_getfilter_free( filtd );
#ifdef GO500_UFN
}
#endif
if ( matches <= 0 ) {
return;
}
#ifdef GO500_SORT_ATTR
ldap_sort_entries( ld, &res, GO500_SORT_ATTR, strcasecmp );
#endif
for ( e = ldap_first_entry( ld, res ); e != NULL;
e = ldap_next_entry( ld, e ) ) {
char *s;
dn = ldap_get_dn( ld, e );
rdn = strdup( dn );
if ( (s = strchr( rdn, ',' )) != NULL )
*s = '\0';
if ( (s = strchr( rdn, '=' )) == NULL )
s = rdn;
else
++s;
title = ldap_get_values( ld, e, "title" );
if ( title != NULL ) {
char *p;
for ( p = title[0]; *p; p++ ) {
if ( *p == '/' )
*p = '\\';
}
}
fprintf( fp, "0%-20s %s\t=%s\t%s\t%d\r\n", s,
title ? title[0] : "", dn, myhost, myport );
if ( title != NULL )
ldap_value_free( title );
free( rdn );
free( dn );
}
if ( ldap_result2error( ld, res, 1 ) == LDAP_SIZELIMIT_EXCEEDED ) {
fprintf( fp, "0A size limit was exceeded (explanation)\t~\t%s\t%d\r\n",
myhost, myport );
}
}
static int
entry2textwrite( void *fp, char *buf, ber_len_t len )
{
return( fwrite( buf, len, 1, (FILE *)fp ) == 0 ? -1 : len );
}
static void
do_read( LDAP *ld, FILE *fp, char *dn )
{
static struct ldap_disptmpl *tmpllist;
ldap_init_templates( templatefile, &tmpllist );
if ( ldap_entry2text_search( ld, dn, base, NULL, tmpllist, NULL, NULL,
entry2textwrite, (void *) fp, "\r\n", rdncount,
LDAP_DISP_OPT_DOSEARCHACTIONS ) != LDAP_SUCCESS ) {
ldap_perror( ld, "ldap_entry2text_search" );
exit( EXIT_FAILURE );
}
if ( tmpllist != NULL ) {
ldap_free_templates( tmpllist );
}
}
/*
* Copyright (c) 1990 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 "portable.h"
#include <stdio.h>
#include <ac/stdlib.h>
#include <ac/ctype.h>
#include <ac/signal.h>
#include <ac/socket.h>
#include <ac/string.h>
#include <ac/syslog.h>
#include <ac/time.h>
#include <ac/unistd.h>
#include <ac/wait.h>
#include <ac/setproctitle.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <ldap.h>
#include <disptmpl.h>
#define ldap_debug debug
#include "ldap_log.h"
#include "lutil.h"
#include "ldap_defaults.h"
int debug;
int ldap_syslog;
int ldap_syslog_level;
int dosyslog;
int inetd;
int dtblsize;
char *ldaphost = NULL;
int ldapport = 0;
int searchaliases = 1;
char *helpfile = GO500GW_HELPFILE;
char *filterfile = FILTERFILE;
char *templatefile = TEMPLATEFILE;
char *friendlyfile = FRIENDLYFILE;
int rdncount = GO500GW_RDNCOUNT;
static void usage ( char *name );
static int set_socket (int port);
static RETSIGTYPE wait4child(int sig);
static void do_queries (int s);
static char *pick_oc ( char **oclist );
static int isnonleaf ( LDAP *ld, char **oclist, char *dn );
static void do_menu (LDAP *ld, FILE *fp, char *dn);
static void do_list (LDAP *ld, FILE *fp, char *dn);
static int isoc ( char **ocl, char *oc );
static int make_scope ( LDAP *ld, char *dn );
static void do_search (LDAP *ld, FILE *fp, char *query);
static int entry2textwrite( void *fp, char *buf, ber_len_t len );
static void do_read (LDAP *ld, FILE *fp, char *dn);
static void do_help (FILE *op);
static void do_sizelimit(FILE *fp, char type);
static void do_error (FILE *fp, char *s);
char myhost[MAXHOSTNAMELEN];
int myport = GO500GW_PORT;
static void
usage( char *name )
{
fprintf( stderr, "usage: %s [-d debuglevel] [-I] [-p port] [-P ldapport] [-l]\r\n\t[-x ldaphost] [-a] [-h helpfile] [-f filterfile] [-t templatefile] [-c rdncount]\r\n", name );
exit( EXIT_FAILURE );
}
int
main (int argc, char **argv )
{
int s, ns, rc;
int port = -1;
int i, pid;
char *myname;
fd_set readfds;
struct hostent *hp;
struct sockaddr_in from;
socklen_t fromlen;
#if defined( LDAP_PROCTITLE ) && !defined( HAVE_SETPROCTITLE )
/* for setproctitle */
Argv = argv;
Argc = argc;
#endif
while ( (i = getopt( argc, argv, "P:ad:f:h:lp:t:x:Ic:" )) != EOF ) {
switch( i ) {
case 'a': /* search aliases */
searchaliases = 0;
break;
case 'd': /* debugging level */
debug |= atoi( optarg );
break;
case 'f': /* ldap filter file */
filterfile = strdup( optarg );
break;
case 'h': /* gopher help file */
helpfile = strdup( optarg );
break;
case 'l': /* log to LOG_LOCAL3 */
dosyslog = 1;
break;
case 'p': /* port to listen on */
port = atoi( optarg );
break;
case 'P': /* port to connect to ldap server */
ldapport = atoi( optarg );
break;
case 't': /* ldap template file */
templatefile = strdup( optarg );
break;
case 'x': /* ldap server hostname */
ldaphost = strdup( optarg );
break;
case 'I': /* run from inetd */
inetd = 1;
break;
case 'c': /* count of DN components to show */
rdncount = atoi( optarg );
break;
default:
usage( argv[0] );
}
}
#ifdef HAVE_SYSCONF
dtblsize = sysconf( _SC_OPEN_MAX );
#elif HAVE_GETDTABLESIZE
dtblsize = getdtablesize();
#else
dtblsize = FD_SETSIZE;
#endif
#ifdef FD_SETSIZE
if ( dtblsize > FD_SETSIZE ) {
dtblsize = FD_SETSIZE;
}
#endif /* FD_SETSIZE*/
#ifdef GO500GW_HOSTNAME
strcpy( myhost, GO500GW_HOSTNAME );
#else
if ( myhost[0] == '\0' && gethostname( myhost, sizeof(myhost) )
== -1 ) {
perror( "gethostname" );
exit( EXIT_FAILURE );
}
#endif
/* detach if stderr is redirected or no debugging */
if ( inetd == 0 )
lutil_detach( debug && !isatty( 1 ), 1 );
if ( (myname = strrchr( argv[0], '/' )) == NULL )
myname = strdup( argv[0] );
else
myname = strdup( myname + 1 );
if ( debug ) {
ber_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug);
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug);
}
#ifdef SIGPIPE
(void) SIGNAL( SIGPIPE, SIG_IGN );
#endif
if ( dosyslog ) {
#ifdef LOG_LOCAL3
openlog( myname, OPENLOG_OPTIONS, LOG_LOCAL3 );
#else
openlog( myname, OPENLOG_OPTIONS );
#endif
}
if ( dosyslog )
syslog( LOG_INFO, "initializing" );
/* set up the socket to listen on */
if ( inetd == 0 ) {
s = set_socket( port );
/* arrange to reap children */
(void) SIGNAL( SIGCHLD, wait4child );
}
if ( inetd ) {
fromlen = sizeof(from);
if ( getpeername( 0, (struct sockaddr *) &from, &fromlen )
== 0 ) {
hp = gethostbyaddr( (char *) &(from.sin_addr.s_addr),
sizeof(from.sin_addr.s_addr), AF_INET );
Debug( LDAP_DEBUG_ARGS, "connection from %s (%s)\n",
(hp == NULL) ? "unknown" : hp->h_name,
inet_ntoa( from.sin_addr ), 0 );
if ( dosyslog ) {
syslog( LOG_INFO, "connection from %s (%s)",
(hp == NULL) ? "unknown" : hp->h_name,
inet_ntoa( from.sin_addr ) );
}
#ifdef LDAP_PROCTITLE
setproctitle( hp == NULL ? inet_ntoa( from.sin_addr ) :
hp->h_name );
#endif
}
do_queries( 0 );
tcp_close( 0 );
exit( EXIT_SUCCESS );
}
for ( ;; ) {
FD_ZERO( &readfds );
FD_SET( s, &readfds );
if ( (rc = select( dtblsize, &readfds, 0, 0 ,0 )) == -1 ) {
if ( debug ) perror( "select" );
continue;
} else if ( rc == 0 ) {
continue;
}
if ( ! FD_ISSET( s, &readfds ) )
continue;
fromlen = sizeof(from);
if ( (ns = accept( s, (struct sockaddr *) &from, &fromlen ))
== -1 ) {
if ( debug ) perror( "accept" );
exit( EXIT_FAILURE );
}
hp = gethostbyaddr( (char *) &(from.sin_addr.s_addr),
sizeof(from.sin_addr.s_addr), AF_INET );
if ( dosyslog ) {
syslog( LOG_INFO, "TCP connection from %s (%s)",
(hp == NULL) ? "unknown" : hp->h_name,
inet_ntoa( from.sin_addr ) );
}
switch( pid = fork() ) {
case 0: /* child */
tcp_close( s );
do_queries( ns );
break;
case -1: /* failed */
perror( "fork" );
break;
default: /* parent */
tcp_close( ns );
if ( debug )
fprintf( stderr, "forked child %d\n", pid );
break;
}
}
/* NOT REACHED */
}
static int
set_socket( int port )
{
int s, one;
struct sockaddr_in addr;
if ( port == -1 )
port = GO500GW_PORT;
myport = port;
if ( (s = socket( AF_INET, SOCK_STREAM, 0 )) == -1 ) {
perror( "socket" );
exit( EXIT_FAILURE );
}
#ifdef SO_REUSEADDR
/* set option so clients can't keep us from coming back up */
one = 1;
if ( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
sizeof(one) ) < 0 ) {
perror( "setsockopt" );
exit( EXIT_FAILURE );
}
#endif
#ifdef SO_KEEPALIVE
/* enable keep alives */
one = 1;
if ( setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, (char *) &one,
sizeof(one) ) < 0 ) {
perror( "setsockopt" );
exit( EXIT_FAILURE );
}
#endif
/* bind to a name */
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons( port );
if ( bind( s, (struct sockaddr *) &addr, sizeof(addr) ) ) {
perror( "bind" );
exit( EXIT_FAILURE );
}
/* listen for connections */
if ( listen( s, 5 ) == -1 ) {
perror( "listen" );
exit( EXIT_FAILURE );
}
if ( debug )
printf( "go500gw listening on port %d\n", port );
return( s );
}
static RETSIGTYPE
wait4child( int sig )
{
#ifndef HAVE_WAITPID
WAITSTATUSTYPE status;
#endif
if ( debug ) printf( "parent: catching child status\n" );
#ifdef HAVE_WAITPID
while (waitpid ((pid_t) -1, (int *) NULL, WAIT_FLAGS) > 0)
; /* NULL */
#else
while (wait4( (pid_t) -1, &status, WAIT_FLAGS, 0 ) > 0 )
; /* NULL */
#endif
(void) SIGNAL( SIGCHLD, wait4child );
}
static void
do_queries( int s )
{
char buf[1024], *query;
int len;
FILE *fp;
int rc;
int deref;
struct timeval timeout;
fd_set readfds;
LDAP *ld;
if ( (fp = fdopen( s, "a+")) == NULL ) {
perror( "fdopen" );
exit( EXIT_FAILURE );
}
timeout.tv_sec = GO500GW_TIMEOUT;
timeout.tv_usec = 0;
FD_ZERO( &readfds );
FD_SET( fileno( fp ), &readfds );
if ( (rc = select( dtblsize, &readfds, 0, 0, &timeout )) <= 0 )
exit( EXIT_FAILURE );
if ( fgets( buf, sizeof(buf), fp ) == NULL )
exit( EXIT_FAILURE );
len = strlen( buf );
if ( debug ) {
fprintf( stderr, "got %d bytes\n", len );
#ifdef LDAP_DEBUG
ber_bprint( buf, len );
#endif
}
/* strip of \r \n */
if ( buf[len - 1] == '\n' )
buf[len - 1] = '\0';
len--;
if ( buf[len - 1] == '\r' )
buf[len - 1] = '\0';
len--;
query = buf;
/* strip off leading white space */
while ( isspace( (unsigned char) *query )) {
++query;
--len;
}
rewind(fp);
if ( *query == 'H' || *query == 'L' || *query == 'E' ) {
switch ( *query++ ) {
case 'H': /* help file */
do_help( fp );
break;
case 'L': /* size limit explanation */
do_sizelimit( fp, *query );
break;
case 'E': /* error explanation */
do_error( fp, query );
break;
}
fprintf( fp, ".\r\n" );
rewind(fp);
exit( EXIT_SUCCESS );
/* NOT REACHED */
}
if ( (ld = ldap_init( ldaphost, ldapport )) == NULL ) {
if ( debug ) perror( "ldap_init" );
fprintf(fp, "0An error occurred (explanation)\tE%d\t%s\t%d\r\n",
LDAP_SERVER_DOWN, myhost, myport );
fprintf( fp, ".\r\n" );
rewind(fp);
exit( EXIT_FAILURE );
}
deref = LDAP_DEREF_ALWAYS;
if ( !searchaliases )
deref = LDAP_DEREF_FINDING;
ldap_set_option(ld, LDAP_OPT_DEREF, &deref);
if ( (rc = ldap_simple_bind_s( ld, NULL, NULL ))
!= LDAP_SUCCESS ) {
if ( debug ) ldap_perror( ld, "ldap_simple_bind_s" );
fprintf(fp, "0An error occurred (explanation)\tE%d\t%s\t%d\r\n",
rc, myhost, myport );
fprintf( fp, ".\r\n" );
rewind(fp);
exit( EXIT_FAILURE );
}
switch ( *query++ ) {
case 'R': /* read an entry */
do_read( ld, fp, query );
break;
case 'S': /* search */
do_search( ld, fp, query );
break;
case 'M': /* X.500 menu */
do_menu( ld, fp, query );
break;
default:
do_menu( ld, fp, "" );
break;
}
fprintf( fp, ".\r\n" );
rewind(fp);
exit( EXIT_SUCCESS );
/* NOT REACHED */
}
static char *
pick_oc( char **oclist )
{
int i;
if ( oclist == NULL )
return( "unknown" );
for ( i = 0; oclist[i] != NULL; i++ ) {
if ( strcasecmp( oclist[i], "top" ) != 0 &&
strcasecmp( oclist[i], "quipuObject" ) != 0 &&
strcasecmp( oclist[i], "quipuNonLeafObject" ) != 0 )
return( oclist[i] );
}
return( "unknown" );
}
static int
isnonleaf( LDAP *ld, char **oclist, char *dn )
{
int i, quipuobject = 0;
if ( oclist == NULL )
return( 0 );
for ( i = 0; oclist[i] != NULL; i++ ) {
if ( strcasecmp( oclist[i], "quipuObject" ) == 0 )
quipuobject = 1;
if ( strcasecmp( oclist[i], "quipuNonLeafObject" ) == 0 ||
strcasecmp( oclist[i], "externalNonLeafObject" ) == 0 )
return( 1 );
}
/*
* not a quipu thang - no easy way to tell leaves from nonleaves
* except by trying to search or list. ldap only lets us search.
*/
/* expensive - just guess for now */
return( quipuobject ? 0 : 1 );
#ifdef notdef
if ( !quipuobject ) {
int rc, numentries;
struct timeval timeout;
LDAPMessage *res = NULL;
static char *attrs[] = { "objectClass", 0 };
int sizelimit = 1;
timeout.tv_sec = GO500GW_TIMEOUT;
timeout.tv_usec = 0;
ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &sizelimit);
if ( (rc = ldap_search_st( ld, dn, LDAP_SCOPE_ONELEVEL,
"(objectClass=*)", attrs, 0, &timeout, &res ))
== LDAP_SUCCESS || rc == LDAP_SIZELIMIT_EXCEEDED ) {
sizelimit = LDAP_NO_LIMIT;
ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &sizelimit);
numentries = ldap_count_entries( ld, res );
if ( res != NULL )
ldap_msgfree( res );
return( numentries == 1 ? 1 : 0 );
}
}
return( 0 );
#endif
}
static void
do_menu( LDAP *ld, FILE *fp, char *dn )
{
char **s;
char *rdn = NULL;
LDAPFriendlyMap *fm = NULL;
if ( strcmp( dn, "" ) != 0 ) {
s = ldap_explode_dn( dn, 1 );
if ( s[1] == NULL )
rdn = ldap_friendly_name( friendlyfile, s[0], &fm );
else
rdn = s[0];
fprintf( fp, "0Read %s entry\tR%s\t%s\t%d\r\n", rdn ? rdn: s[0],
dn, myhost, myport );
ldap_value_free( s );
} else {
fprintf( fp, "0About the Gopher to X.500 Gateway\tH\t%s\t%d\r\n",
myhost, myport );
}
fprintf( fp, "7Search %s\tS%s\t%s\t%d\r\n", rdn ? rdn : "root", dn,
myhost, myport );
do_list( ld, fp, dn );
ldap_free_friendlymap( &fm );
}
static void
do_list( LDAP *ld, FILE *fp, char *dn )
{
int rc;
LDAPMessage *e, *res;
struct timeval timeout;
LDAPFriendlyMap *fm = NULL;
static char *attrs[] = { "objectClass", 0 };
int deref = LDAP_DEREF_FINDING;
timeout.tv_sec = GO500GW_TIMEOUT;
timeout.tv_usec = 0;
ldap_set_option(ld, LDAP_OPT_DEREF, &deref);
if ( (rc = ldap_search_st( ld, dn, LDAP_SCOPE_ONELEVEL,
"(!(objectClass=dSA))", attrs, 0, &timeout, &res )) != LDAP_SUCCESS
&& rc != LDAP_SIZELIMIT_EXCEEDED ) {
fprintf(fp, "0An error occurred (explanation)\tE%d\t%s\t%d\r\n",
rc, myhost, myport );
return;
}
deref = LDAP_DEREF_ALWAYS;
ldap_set_option(ld, LDAP_OPT_DEREF, &deref);
if ( ldap_count_entries( ld, res ) < 1 ) {
return;
}
#ifdef GO500GW_SORT_ATTR
ldap_sort_entries( ld, &res, GO500GW_SORT_ATTR, strcasecmp );
#endif
fm = NULL;
for ( e = ldap_first_entry( ld, res ); e != NULL;
e = ldap_next_entry( ld, e ) ) {
char **s, **oc;
char *rdn, *doc;
dn = ldap_get_dn( ld, e );
s = ldap_explode_dn( dn, 1 );
oc = ldap_get_values( ld, e, "objectClass" );
doc = pick_oc( oc );
if ( strcasecmp( doc, "country" ) == 0 ) {
rdn = ldap_friendly_name( friendlyfile, s[0], &fm );
} else {
rdn = s[0];
}
if ( rdn == NULL ) {
rdn = s[0];
}
if ( strncasecmp( rdn, "{ASN}", 5 ) != 0 ) {
if ( isnonleaf( ld, oc, dn ) ) {
fprintf( fp, "1%s (%s)\tM%s\t%s\t%d\r\n", rdn,
doc, dn, myhost, myport );
} else {
fprintf( fp, "0%s (%s)\tR%s\t%s\t%d\r\n", rdn,
doc, dn, myhost, myport );
}
}
free( dn );
ldap_value_free( s );
ldap_value_free( oc );
}
ldap_free_friendlymap( &fm );
if ( ldap_result2error( ld, res, 1 ) == LDAP_SIZELIMIT_EXCEEDED ) {
fprintf( fp, "0A size limit was exceeded (explanation)\tLL\t%s\t%d\r\n",
myhost, myport );
}
}
static int
isoc( char **ocl, char *oc )
{
int i;
for ( i = 0; ocl[i] != NULL; i++ ) {
if ( strcasecmp( ocl[i], oc ) == 0 )
return( 1 );
}
return( 0 );
}
static int
make_scope( LDAP *ld, char *dn )
{
int scope;
char **oc;
LDAPMessage *res;
struct timeval timeout;
static char *attrs[] = { "objectClass", 0 };
if ( strcmp( dn, "" ) == 0 )
return( LDAP_SCOPE_ONELEVEL );
timeout.tv_sec = GO500GW_TIMEOUT;
timeout.tv_usec = 0;
if ( ldap_search_st( ld, dn, LDAP_SCOPE_BASE, "objectClass=*",
attrs, 0, &timeout, &res ) != LDAP_SUCCESS ) {
return( -1 );
}
oc = ldap_get_values( ld, ldap_first_entry( ld, res ), "objectClass" );
if ( isoc( oc, "organization" ) || isoc( oc, "organizationalUnit" ) )
scope = LDAP_SCOPE_SUBTREE;
else
scope = LDAP_SCOPE_ONELEVEL;
ldap_value_free( oc );
ldap_msgfree( res );
return( scope );
}
static void
do_search( LDAP *ld, FILE *fp, char *query )
{
int deref;
int scope;
char *base, *filter;
char *filtertype;
int count, rc;
struct timeval timeout;
LDAPFiltInfo *fi;
LDAPMessage *e, *res;
LDAPFiltDesc *filtd;
static char *attrs[] = { "objectClass", 0 };
if ( (filter = strchr( query, '\t' )) == NULL ) {
fprintf( fp, "3Missing filter!\r\n" );
exit( EXIT_FAILURE );
}
*filter++ = '\0';
base = query;
#ifdef GO500GW_UFN
if ( strchr( filter, ',' ) != NULL ) {
ldap_ufn_setprefix( ld, base );
timeout.tv_sec = GO500GW_TIMEOUT;
timeout.tv_usec = 0;
ldap_ufn_timeout( (void *) &timeout );
deref = LDAP_DEREF_FINDING;
ldap_set_option(ld, LDAP_OPT_DEREF, &deref);
if ( (rc = ldap_ufn_search_s( ld, filter, attrs, 0, &res ))
!= LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED ) {
fprintf(fp,
"0An error occurred (explanation)\t@%d\t%s\t%d\r\n",
rc, myhost, myport );
return;
}
count = ldap_count_entries( ld, res );
} else {
#endif
if ( (scope = make_scope( ld, base )) == -1 ) {
fprintf( fp, "3Bad scope\r\n" );
exit( EXIT_FAILURE );
}
filtertype = (scope == LDAP_SCOPE_ONELEVEL ?
"go500gw onelevel" : "go500gw subtree");
deref = (scope == LDAP_SCOPE_ONELEVEL ?
LDAP_DEREF_FINDING : LDAP_DEREF_ALWAYS);
ldap_set_option(ld, LDAP_OPT_DEREF, &deref);
timeout.tv_sec = GO500GW_TIMEOUT;
timeout.tv_usec = 0;
if ( (filtd = ldap_init_getfilter( filterfile )) == NULL ) {
fprintf( stderr, "Cannot open filter file (%s)\n",
filterfile );
exit( EXIT_FAILURE );
}
count = 0;
res = NULL;
for ( fi = ldap_getfirstfilter( filtd, filtertype, filter );
fi != NULL; fi = ldap_getnextfilter( filtd ) )
{
if ( (rc = ldap_search_st( ld, base, scope,
fi->lfi_filter, attrs, 0, &timeout, &res ))
!= LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED ) {
fprintf(fp, "0An error occurred (explanation)\tE%d\t%s\t%d\r\n",
rc, myhost, myport );
return;
}
if ( (count = ldap_count_entries( ld, res )) != 0 )
break;
}
deref = LDAP_DEREF_ALWAYS;
ldap_set_option(ld, LDAP_OPT_DEREF, &deref);
ldap_getfilter_free( filtd );
#ifdef GO500GW_UFN
}
#endif
if ( count == 0 ) {
return;
}
if ( count == 1 ) {
char *dn, **oc;
e = ldap_first_entry( ld, res );
oc = ldap_get_values( ld, e, "objectClass" );
dn = ldap_get_dn( ld, e );
if ( isnonleaf( ld, oc, dn ) ) {
do_menu( ld, fp, dn );
free( dn );
return;
}
free( dn );
ldap_value_free( oc );
}
#ifdef GO500GW_SORT_ATTR
ldap_sort_entries( ld, &res, GO500GW_SORT_ATTR, strcasecmp );
#endif
for ( e = ldap_first_entry( ld, res ); e != NULL;
e = ldap_next_entry( ld, e ) ) {
char **s, **oc;
char *dn;
dn = ldap_get_dn( ld, e );
s = ldap_explode_dn( dn, 1 );
oc = ldap_get_values( ld, e, "objectClass" );
if ( isnonleaf( ld, oc, dn ) )
fprintf( fp, "1%s (%s)\tM%s\t%s\t%d\r\n", s[0],
pick_oc( oc ), dn, myhost, myport );
else
fprintf( fp, "0%s (%s)\tR%s\t%s\t%d\r\n", s[0],
pick_oc( oc ), dn, myhost, myport );
free( dn );
ldap_value_free( s );
ldap_value_free( oc );
}
if ( ldap_result2error( ld, res, 1 ) == LDAP_SIZELIMIT_EXCEEDED ) {
fprintf( fp, "0A size limit was exceeded (explanation)\tLS\t%s\t%d\r\n",
myhost, myport );
}
}
static int
entry2textwrite( void *fp, char *buf, ber_len_t len )
{
return( fwrite( buf, len, 1, (FILE *)fp ) == 0 ? -1 : len );
}
static void
do_read( LDAP *ld, FILE *fp, char *dn )
{
static struct ldap_disptmpl *tmpllist;
ldap_init_templates( templatefile, &tmpllist );
if ( ldap_entry2text_search( ld, dn, NULL, NULL, tmpllist, NULL, NULL,
entry2textwrite,(void *) fp, "\r\n", rdncount, 0 )
!= LDAP_SUCCESS ) {
int ld_errno = 0;
ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ld_errno);
fprintf(fp,
"0An error occurred (explanation)\t@%s\t%s\t%d\r\n",
ldap_err2string( ld_errno ), myhost, myport );
}
if ( tmpllist != NULL ) {
ldap_free_templates( tmpllist );
}
}
static void
do_help( FILE *op )
{
FILE *fp;
char line[BUFSIZ];
if ( (fp = fopen( helpfile, "r" )) == NULL ) {
fprintf( op, "Cannot access helpfile (%s)\r\n", helpfile );
return;
}
while ( fgets( line, sizeof(line), fp ) != NULL ) {
line[ strlen( line ) - 1 ] = '\0';
fprintf( op, "%s\r\n", line );
}
fclose( fp );
}
static void
do_sizelimit( FILE *fp, char type )
{
if ( type == 'S' ) {
fprintf( fp, "The query you specified was not specific enough, causing a size limit\r\n" );
fprintf( fp, "to be exceeded and the first several matches found to be returned.\r\n" );
fprintf( fp, "If you did not find the match you were looking for, try issuing a more\r\n" );
fprintf( fp, "specific query, for example one that contains both first and last name.\r\n" );
} else {
fprintf( fp, "Not all entries could be returned because a size limit was exceeded.\r\n" );
fprintf( fp, "There is no way to defeat this feature, but if you know who you are\r\n" );
fprintf( fp, "looking for, try choosing the \"Search\" option listed above and\r\n" );
fprintf( fp, "specifying the name of the person you want.\r\n" );
}
fprintf( fp, ".\r\n" );
}
static void
do_error( FILE *fp, char *s )
{
int code;
code = atoi( s );
fprintf( fp, "An error occurred searching X.500. The error code was %d\r\n", code );
fprintf( fp, "The corresponding error is: %s\r\n", ldap_err2string( code ) );
fprintf( fp, "No additional information is available\r\n" );
fprintf( fp, ".\r\n" );
}
This is a second pass at a general Gopher to X.500 gateway. It is
somewhat tailored to white pages usage, but is pretty general.
There are two modes of operation that the gateway supports: browsing
and searching. To browse, simply choose one of the countries,
organizations, etc. that appear in gopher as menu items. The next
level of the X.500 DIT will then be displayed. To search, choose the
search item (second choice in all menus) and type the name of whatever
it is you're looking for. At the upper levels of the tree (i.e. root
or country level) searches are assumed to be for organizations or
localities and are one-level in scope. At the lower levels of the tree
(i.e. organization or organizationalunit level) searches are assumed to
be for people and are subtree in scope. What sort of search is done
depends on what you type, but a variety of things are tried.
We would appreciate any feedback you can provide. If you have
problems, report them using our Issue Tracking System:
http://www.OpenLDAP.com/its/
or by sending e-mail to:
OpenLDAP-its@OpenLDAP.org
Additional mailing lists are available. Please see:
http://www.OpenLDAP.com/lists/
PROGRAMS= mail500
SRCS= main.c
XSRCS= version.c
OBJS= main.o
LDAP_INCDIR= ../../include
LDAP_LIBDIR= ../../libraries
XLIBS = -lldap -llber -llutil
XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS)
mail500 : version.o
$(LTLINK) -o $@ version.o $(OBJS) $(LIBS)
version.c: ${OBJS} $(LDAP_LIBDEPEND)
@-$(RM) $@
$(MKVERSION) mail500 > $@
install-local: $(PROGRAMS) FORCE
-$(MKDIR) $(libexecdir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 mail500 $(libexecdir)
*** WARNING: Preliminary ***
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, you need to create a
configuration file. Previous versions required modifying the source
code for configuration. This is no longer necessary.
there are several
*** 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 sendmail.cf
file somewhere in rule set 0:
R$*<@umich.edu>$* $#mail500$@umich.edu$:<$1>
This rule says that any address that ends in @umich.edu will cause
the mail500 mailer to be called to deliver the mail. You probably
also want to do something to prevent addresses like terminator!tim@umich.edu
or tim%terminator.rs.itd.umich.edu@umich.edu 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<@umich.edu>$*:$* $>10<@>$1:$2
R$+%$+<@umich.edu> $>10$1%$2<@>
R$+!$+<@umich.edu> $>10$1!$2<@>
You can also feed complete FQDN addresses to mail500. For instance,
you could define a class containing the list of domains you want to
serve like this:
FQ/etc/mail/mail500domains
and then use a rule in rule set 0 like this:
R$*<$=Q>$* $#mail500 $@$2 $:<$1@$2>
See the sample sendmail.cf in this directory for more details.
For sendmail 8.9 (and later) users can use MAILER(mail500) if
mail500.m4 is placed within sendmail's cf/mailer directory.
The mail500 mailer should be defined similar to this in the
sendmail.cf 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
name.
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) is controlled by a configuration file. There
are a number of different approaches to handling mail and no general
rules can be given. We will however present some examples of what you
can do. The new mail500 is designed to be flexible and able to
accommodate most scenarios.
For instance, if you are following the mail distribution model that
the old mail500 used, you need lines in the configuration file like
these:
search ldap:///ou=People, dc=OpenLDAP, dc=org??sub?\
(|(uid=%25l)(cn==%25l))
search ldap:///ou=System Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
(&(cn=%25l)(associatedDomain==%25h))
search ldap:///ou=User Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
(&(cn=%25l)(associatedDomain==%25h))
As you can see, searches are described by using LDAP URLs. You can
have as many searches as you want, but the first search that succeeds
completes the processing for a recipient address. You can provide an
attribute list in the URL and it will be honored. Otherwise, the
attribute list will default as explained below.
Filters can contain substitutions. Actually, they *should* contain
substitutions or the search result would not change with the recipient
address. Since the usual substitution character is % and it has
special meaning in URLs, you have to represent it according to the URL
syntax, that is, %25, 25 being the hex code of %. The filter can be
as complex as you want and you may make as many substitutions as you
want. Known substitutions at this time are:
%m The recipient address we are considering now, maybe fully
qualified
%h The host, that is, the value of the -h argument to
mail500
%l The local part from %m
%d The domain part from %m
So, in the above example, if the recipient address were
name@OpenLDAP.org, mail500 would do the the following searches,
stopping if it found anything at any step:
Search (18) [2]: dc=org@dc=OpenLDAP@ou=People
Search subtree (uid=name)
Search (18) [3]: dc=org@dc=OpenLDAP@ou=People
Search subtree (cn=name)
Search (18) [4]: dc=org@dc=OpenLDAP@ou=Groups@ou=System Groups
Search subtree & ((cn=name)(associatedDomain=OpenLDAP.org))
Search (18) [5]: dc=org@dc=OpenLDAP@ou=Groups@ou=User Groups
Search subtree & ((cn=name)(associatedDomain=OpenLDAP.org))
[Beware: Currently unimplemented]
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.
*** HOW IT WORKS (from the X.500 side): ***
First you need to decide what attributes you will search for and what
attributes will be used to deliver the message. In the classical
mail500, we would search by uid or cn and deliver to the mail
attribute. Another model is to search by the mail attribute and
deliver to something else, such as the uid if determined that the user
has a local account.
*** THE CONFIGURATION FILE
The configuration file is composed of lines that prescribe the
operation of mail500. Blank lines are ignored and lines beginning
with # are considered comments and ignored. Outside comments, the
sequence '\', newline, whitespace is ignored so that long lines can be
split for readability.
Attribute Definitions
Lines starting with 'attribute' define the semantics of an attribute.
Notice that attributes will be considered in the order they are
defined in the configuration file. This means that the presence of
some can preempt processing of other attributes and that attributes
that simply collect needed information must be defined before others
that use that information. The format is:
attribute name [multivalued] [final] [multiple-entries] [<syntax>] [<kind>]
If the attribute is "multivalued", all values will be considered. If
it is not and several values are found the entry is declared in error.
If the attribute is "final", its presence in an entry prevents further
analysis of the entry.
If the attribute is "multiple-entries" and it is of an appropriate
syntax that can point to other entries, all such entries are
considered, otherwise the entry is in error.
The known kinds are:
recipient The value(s) of this attribute should be
used as the address(es) to deliver the message
to if they are in an appropriate syntax. If
they otherwise point at other entries, they
should be retrieved and expanded as necessary
to complete the resolution of this entry. The
process is recursive and all.
errors The value(s) of this attribute represent the
entities that should receive error messages
for mail messages directed to this entry.
The presence of an attribute of this kind
force a change in the envelope sender address
of the message.
The known syntaxes are:
local-native-mailbox An unqualified mailbox name
rfc822 A fully qualified RFC822 mail address
rfc822-extended Currently identical to rfc822
dn The Distinguished Name of some other entry
url A URL either of the mailto: or ldap: styles,
others styles, notably file:, could be added.
No substitutions are supported currently.
search-with-filter=<filter> Do a search on all known search bases
with the give filter. The only currenty
substitution available is %D, the DN of the
current entry.
The default attributes to search
A line starting with "default-attributes" contains a comma-separated
list of attributes to use in searches everytime a specific list is not
known.
Search bases
As shown in the example above, lines starting with "search" provide
the search bases to use to initially try to resolve each entry or when
using attributes of syntax "search-with-filter".
*** EXAMPLES
A configuration file that approximates the operation of the old
mail500 runs as follows:
attribute errorsTo errors dn
attribute rfc822ErrorsTo errors rfc822
attribute requestsTo request dn
attribute rfc822RequestsTo request rfc822
attribute owner owner dn
attribute mail multivalued recipient rfc822
attribute member multivalued recipient dn
attribute joinable multiple-entries recipient \
search-with-filter=(memberOfGroup=%D)
default-attributes objectClass,title,postaladdress,telephoneNumber,\
mail,description,owner,errorsTo,rfc822ErrorsTo,requestsTo,\
rfc822RequestsTo,joinable,cn,member,moderator,onVacation,uid,\
suppressNoEmailError
# Objectclasses that, when present, identify an entry as a group
group-classes mailGroup
search ldap:///ou=People, dc=OpenLDAP, dc=org??sub?\
(|(uid=%25l)(cn==%25l))
search ldap:///ou=System Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
(&(cn=%25l)(associatedDomain==%25h))
search ldap:///ou=User Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
(&(cn=%25l)(associatedDomain==%25h))
A configuration that approximates the semantics of the mailRecipient
and mailGroup classes used by Netscape:
attribute mgrpErrorsTo errors url
attribute rfc822ErrorsTo errors rfc822
attribute mailRoutingAddress final recipient rfc822
attribute mailHost final host forward-to-host
attribute uid final recipient local-native-mailbox
attribute uniqueMember multivalued recipient dn
attribute mgrpRFC822MailMember multivalued recipient rfc822-extended
attribute mgrpDeliverTo multivalued multiple-entries recipient url
default-attributes objetcClass,mailRoutingAddress,mailHost,uid,uniqueMember,\
mgrpRFC822MailMember,mgrpErrorsTo,rfc822ErrorsTo
# Objectclasses that, when present, identify an entry as a group
group-classes mailGroup
search ldap://localhost/dc=OpenLDAP,dc=org?\
objectClass,mailRoutingAddress,mailHost,uid?\
sub?\
(&(|(mail=%25m)(mailAlternateAddress=%25m))(objectClass=mailRecipient))
search ldap://localhost/dc=OpenLDAP,dc=org?\
objectClass,uniqueMember,mgrpRFC822MailMember,mgrpErrorsTo,mgrpDeliverTo,rfc822ErrorsTo?\
sub?\
(&(|(mail=%25m)(mailAlternateAddress=%25m))(objectClass=mailGroup))
[ The rest is from the original README and I did not rewrite it yet ]
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
mail= tim@terminator.rs.itd.umich.edu
So mail sent to tim@umich.edu 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 oidtable.at:
# 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
attributes.
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...
PUSHDIVERT(-1)
#
# mail500 mailer
#
# This file should be placed in the sendmail's cf/mailer directory.
# To include this mailer in your .cf file, use the directive:
# MAILER(mail500)
#
#CQ foo.com
POPDIVERT
dnl
ifdef(`MAIL500_HOST',
`define(`MAIL500_HOST_FLAG', CONCAT(` -l ', CONCAT(MAIL500_HOST,` ')))',
`define(`MAIL500_HOST_FLAG', `')')
ifdef(`MAIL500_CONFIG_PATH',,
`define(`MAIL500_CONFIG_PATH', /etc/mail/mail500.conf)')
ifdef(`MAIL500_MAILER_PATH',,
`ifdef(`MAIL500_PATH',
`define(`MAIL500_MAILER_PATH', MAIL500_PATH)',
`define(`MAIL500_MAILER_PATH', /usr/local/libexec/mail500)')')
ifdef(`MAIL500_MAILER_FLAGS',,
`define(`MAIL500_MAILER_FLAGS', `SmnXuh')')
ifdef(`MAIL500_MAILER_ARGS',,
`define(`MAIL500_MAILER_ARGS',
CONCAT(`mail500',CONCAT(` -C ',MAIL500_CONFIG_PATH,MAIL500_HOST_FLAG,`-f $f -m $n@$w $u')))')
dnl
MAILER_DEFINITIONS
VERSIONID(`OpenLDAP mail500 990630')
######################*****##############
### MAIL500 Mailer specification ###
##################*****##################
Mmail500, P=MAIL500_MAILER_PATH, F=CONCAT(`DFM', MAIL500_MAILER_FLAGS), S=11/31, R=20/40, T=DNS/RFC822/X-Unix,
ifdef(`MAIL500_MAILER_MAX', `M=500_MAILER_MAX, ')A=MAIL500_MAILER_ARGS
PUSHDIVERT(3)
# mail500 additions
R$* < @ $=Q > $* $#mail500 $@ $2 $: <$1@$2> domain handled by mail500
POPDIVERT
/*
* Copyright (c) 1990 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.
*
* Copyright 1998,1999 The OpenLDAP Foundation
* COPYING RESTRICTIONS APPLY. See COPYRIGHT File in top level directory
* of this package for details.
*/
#include "portable.h"
#include <stdio.h>
#include <ac/stdlib.h>
#include <ac/ctype.h>
#include <ac/signal.h>
#include <ac/string.h>
#include <ac/sysexits.h>
#include <ac/syslog.h>
#include <ac/time.h>
#include <ac/unistd.h>
#include <ac/wait.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <ldap.h>
#include "ldap_defaults.h"
#ifndef MAIL500_BOUNCEFROM
#define MAIL500_BOUNCEFROM "<>"
#endif
#define USER 0x01
#define GROUP_ERRORS 0x02
#define GROUP_REQUEST 0x04
#define GROUP_MEMBERS 0x08
#define GROUP_OWNER 0x10
#define ERROR "error"
#define ERRORS "errors"
#define REQUEST "request"
#define REQUESTS "requests"
#define MEMBERS "members"
#define OWNER "owner"
#define OWNERS "owners"
LDAP *ld;
char *vacationhost = NULL;
char *errorsfrom = MAIL500_BOUNCEFROM;
char *mailfrom = NULL;
char *host = NULL;
char *ldaphost = NULL;
int hostlen = 0;
int debug;
typedef struct errs {
int e_code;
#define E_USERUNKNOWN 1
#define E_AMBIGUOUS 2
#define E_NOEMAIL 3
#define E_NOREQUEST 4
#define E_NOERRORS 5
#define E_BADMEMBER 6
#define E_JOINMEMBERNOEMAIL 7
#define E_MEMBERNOEMAIL 8
#define E_LOOP 9
#define E_NOMEMBERS 10
#define E_NOOWNER 11
#define E_GROUPUNKNOWN 12
char *e_addr;
union e_union_u {
char *e_u_loop;
LDAPMessage *e_u_msg;
} e_union;
#define e_msg e_union.e_u_msg
#define e_loop e_union.e_u_loop
} Error;
typedef struct groupto {
char *g_dn;
char *g_errorsto;
char **g_members;
int g_nmembers;
} Group;
typedef struct baseinfo {
char *b_url;
int b_m_entries;
char b_rdnpref; /* give rdn's preference when searching? */
int b_search; /* ORed with the type of thing the address */
/* looks like (USER, GROUP_ERRORS, etc.) */
/* to see if this should be searched */
} Base;
/*
* We should limit the search to objectclass=mailRecipient or
* objectclass=mailGroup.
*/
/*
Base base[] = {
{"dc=StlInter, dc=Net",
0, 0xff,
{"mail=%s", "mailAlternateAddress=%s", NULL}},
{NULL}
};
*/
Base **base = NULL;
char *sendmailargs[] = { MAIL500_SENDMAIL, "-oMrLDAP", "-odi", "-oi", "-f", NULL, NULL };
typedef struct attr_semantics {
char *as_name;
int as_m_valued; /* Is multivalued? */
int as_final; /* If true, no further expansion is tried. */
int as_syntax; /* How to interpret values */
int as_m_entries; /* Can resolve to several entries? */
int as_kind; /* Recipient, sender, etc. */
char *as_param; /* Extra info for filters and things alike */
} AttrSemantics;
#define AS_SYNTAX_UNKNOWN 0 /* Unqualified mailbox name */
#define AS_SYNTAX_NATIVE_MB 1 /* Unqualified mailbox name */
#define AS_SYNTAX_RFC822 2 /* RFC822 mail address */
#define AS_SYNTAX_HOST 3
#define AS_SYNTAX_DN 4 /* A directory entry */
#define AS_SYNTAX_RFC822_EXT 5
#define AS_SYNTAX_URL 6 /* mailto: or ldap: URL */
#define AS_SYNTAX_BOOL_FILTER 7 /* For joinable, filter in as_param */
#define AS_KIND_UNKNOWN 0
#define AS_KIND_RECIPIENT 1
#define AS_KIND_ERRORS 2 /* For ErrorsTo and similar */
#define AS_KIND_REQUEST 3
#define AS_KIND_OWNER 4
#define AS_KIND_FORWARD_TO_HOST 5 /* Expand at some other host */
#define AS_KIND_ALLOWED_SENDER 6 /* Can send to group */
#define AS_KIND_MODERATOR 7
AttrSemantics **attr_semantics = NULL;
typedef struct subst {
char sub_char;
char *sub_value;
} Subst;
char **groupclasses = NULL;
char **def_attr = NULL;
static void load_config( char *filespec );
static void split_address( char *address, char **localpart, char **domainpart);
static int entry_engine( LDAPMessage *e, char *dn, char *address, char ***to, int *nto, Group **togroups, int *ngroups, Error **err, int *nerr, int type );
static void do_address( char *name, char ***to, int *nto, Group **togroups, int *ngroups, Error **err, int *nerr, int type );
static void send_message( char **to );
static void send_errors( Error *err, int nerr );
static void do_noemail( FILE *fp, Error *err, int namelen );
static void do_ambiguous( FILE *fp, Error *err, int namelen );
static int count_values( char **list );
static void add_to( char ***list, int *nlist, char **new );
static void add_single_to( char ***list, char *new );
static int isgroup( LDAPMessage *e );
static void add_error( Error **err, int *nerr, int code, char *addr, LDAPMessage *msg );
static void unbind_and_exit( int rc );
static void send_group( Group *group, int ngroup );
static int connect_to_x500( void );
int
main ( int argc, char **argv )
{
char *myname;
char **tolist;
Error *errlist;
Group *togroups;
int numto, ngroups, numerr, nargs;
int i, j;
char *conffile = NULL;
if ( (myname = strrchr( argv[0], '/' )) == NULL )
myname = strdup( argv[0] );
else
myname = strdup( myname + 1 );
#ifdef SIGPIPE
(void) SIGNAL( SIGPIPE, SIG_IGN );
#endif
#ifdef LOG_MAIL
openlog( myname, OPENLOG_OPTIONS, LOG_MAIL );
#else
openlog( myname, OPENLOG_OPTIONS );
#endif
while ( (i = getopt( argc, argv, "d:C:f:h:l:m:v:" )) != EOF ) {
switch( i ) {
case 'd': /* turn on debugging */
debug = atoi( optarg );
break;
case 'C': /* path to configuration file */
conffile = strdup( optarg );
break;
case 'f': /* who it's from & where errors should go */
mailfrom = strdup( optarg );
for ( j = 0; sendmailargs[j] != NULL; j++ ) {
if ( strcmp( sendmailargs[j], "-f" ) == 0 ) {
sendmailargs[j+1] = mailfrom;
break;
}
}
break;
case 'h': /* hostname */
host = strdup( optarg );
hostlen = strlen(host);
break;
case 'l': /* ldap host */
ldaphost = strdup( optarg );
break;
/* mailer-daemon address - who we should */
case 'm': /* say errors come from */
errorsfrom = strdup( optarg );
break;
case 'v': /* vacation host */
vacationhost = strdup( optarg );
break;
default:
syslog( LOG_ALERT, "unknown option" );
break;
}
}
if ( mailfrom == NULL ) {
syslog( LOG_ALERT, "required argument -f not present" );
exit( EX_TEMPFAIL );
}
if ( errorsfrom == NULL ) {
syslog( LOG_ALERT, "required argument -m not present" );
exit( EX_TEMPFAIL );
}
/* if ( host == NULL ) { */
/* syslog( LOG_ALERT, "required argument -h not present" ); */
/* exit( EX_TEMPFAIL ); */
/* } */
if ( conffile == NULL ) {
syslog( LOG_ALERT, "required argument -C not present" );
exit( EX_TEMPFAIL );
}
load_config( conffile );
if ( connect_to_x500() != 0 )
exit( EX_TEMPFAIL );
setuid( geteuid() );
if ( debug ) {
char buf[1024];
int i;
syslog( LOG_ALERT, "running as %d", geteuid() );
strcpy( buf, argv[0] );
for ( i = 1; i < argc; i++ ) {
strcat( buf, " " );
strcat( buf, argv[i] );
}
syslog( LOG_ALERT, "args: (%s)", buf );
}
tolist = NULL;
numto = 0;
add_to( &tolist, &numto, sendmailargs );
nargs = numto;
ngroups = numerr = 0;
togroups = NULL;
errlist = NULL;
for ( i = optind; i < argc; i++ ) {
char *s;
int type;
char *localpart, *domainpart;
char address[1024];
/* TBC: Make this processing optional */
/* for ( j = 0; argv[i][j] != '\0'; j++ ) { */
/* if ( argv[i][j] == '.' || argv[i][j] == '_' ) */
/* argv[i][j] = ' '; */
/* } */
type = USER;
split_address( argv[i], &localpart, &domainpart );
if ( (s = strrchr( localpart, '-' )) != NULL ) {
s++;
if ((strcasecmp(s, ERROR) == 0) ||
(strcasecmp(s, ERRORS) == 0)) {
type = GROUP_ERRORS;
*(--s) = '\0';
} else if ((strcasecmp(s, REQUEST) == 0) ||
(strcasecmp(s, REQUESTS) == 0)) {
type = GROUP_REQUEST;
*(--s) = '\0';
} else if ( strcasecmp( s, MEMBERS ) == 0 ) {
type = GROUP_MEMBERS;
*(--s) = '\0';
} else if ((strcasecmp(s, OWNER) == 0) ||
(strcasecmp(s, OWNERS) == 0)) {
type = GROUP_OWNER;
*(--s) = '\0';
}
}
if ( domainpart ) {
sprintf( address, "%s@%s", localpart, domainpart );
free( localpart );
free( domainpart );
} else {
sprintf( address, "%s@%s", localpart, domainpart );
free( localpart );
}
do_address( address, &tolist, &numto, &togroups, &ngroups,
&errlist, &numerr, type );
}
/*
* If we have both errors and successful deliveries to make or if
* if there are any groups to deliver to, we basically need to read
* the message twice. So, we have to put it in a tmp file.
*/
if ( numerr > 0 && numto > nargs || ngroups > 0 ) {
FILE *fp;
char buf[BUFSIZ];
umask( 077 );
if ( (fp = tmpfile()) == NULL ) {
syslog( LOG_ALERT, "could not open tmp file" );
unbind_and_exit( EX_TEMPFAIL );
}
/* copy the message to a temp file */
while ( fgets( buf, sizeof(buf), stdin ) != NULL ) {
if ( fputs( buf, fp ) == EOF ) {
syslog( LOG_ALERT, "error writing tmpfile" );
unbind_and_exit( EX_TEMPFAIL );
}
}
if ( dup2( fileno( fp ), 0 ) == -1 ) {
syslog( LOG_ALERT, "could not dup2 tmpfile" );
unbind_and_exit( EX_TEMPFAIL );
}
fclose( fp );
}
/* deal with errors */
if ( numerr > 0 ) {
if ( debug ) {
syslog( LOG_ALERT, "sending errors" );
}
(void) rewind( stdin );
send_errors( errlist, numerr );
}
(void) ldap_unbind( ld );
/* send to groups with errorsTo */
if ( ngroups > 0 ) {
if ( debug ) {
syslog( LOG_ALERT, "sending to groups with errorsto" );
}
(void) rewind( stdin );
send_group( togroups, ngroups );
}
/* send to expanded aliases and groups w/o errorsTo */
if ( numto > nargs ) {
if ( debug ) {
syslog( LOG_ALERT, "sending to aliases and groups" );
}
(void) rewind( stdin );
send_message( tolist );
}
return( EX_OK );
}
static char *
get_config_line( FILE *cf, int *lineno)
{
static char buf[2048];
int len;
int pos;
int room;
pos = 0;
room = sizeof( buf );
while ( fgets( &buf[pos], room, cf ) ) {
(*lineno)++;
if ( pos > 0 ) {
/* Delete whitespace at the beginning of new data */
if ( isspace( buf[pos] ) ) {
char *s, *d;
for ( s = buf+pos; isspace(*s); s++ )
;
for ( d = buf+pos; *s; s++, d++ ) {
*d = *s;
}
*d = *s;
}
}
len = strlen( buf );
if ( buf[len-1] != '\n' ) {
syslog( LOG_ALERT, "Definition too long at line %d",
*lineno );
exit( EX_TEMPFAIL );
}
if ( buf[0] == '#' )
continue;
if ( strspn( buf, " \t\n" ) == len )
continue;
if ( buf[len-2] == '\\' ) {
pos = len - 2;
room = sizeof(buf) - pos;
continue;
}
/* We have a real line, we will exit the loop */
buf[len-1] = '\0';
return( buf );
}
return( NULL );
}
static void
add_url ( char *url, int rdnpref, int typemask )
{
Base **list_temp;
int size;
Base *b;
b = calloc(1, sizeof(Base));
if ( !b ) {
syslog( LOG_ALERT, "Out of memory" );
exit( EX_TEMPFAIL );
}
b->b_url = strdup( url );
b->b_rdnpref = rdnpref;
b->b_search = typemask;
if ( base == NULL ) {
base = calloc(2, sizeof(LDAPURLDesc *));
if ( !base ) {
syslog( LOG_ALERT, "Out of memory" );
exit( EX_TEMPFAIL );
}
base[0] = b;
} else {
for ( size = 0; base[size]; size++ )
;
size += 2;
list_temp = realloc( base, size*sizeof(LDAPURLDesc *) );
if ( !list_temp ) {
syslog( LOG_ALERT, "Out of memory" );
exit( EX_TEMPFAIL );
}
base = list_temp;
base[size-2] = b;
base[size-1] = NULL;
}
}
static void
add_def_attr( char *s )
{
char *p, *q;
p = s;
while ( *p ) {
p += strspn( p, "\t," );
q = strpbrk( p, " \t," );
if ( q ) {
*q = '\0';
add_single_to( &def_attr, p );
} else {
add_single_to( &def_attr, p );
break;
}
p = q + 1;
}
}
static void
add_attr_semantics( char *s )
{
char *p, *q;
AttrSemantics *as;
as = calloc( 1, sizeof( AttrSemantics ) );
p = s;
while ( isspace ( *p ) )
p++;
q = p;
while ( !isspace ( *q ) && *q != '\0' )
q++;
*q = '\0';
as->as_name = strdup( p );
p = q + 1;
while ( *p ) {
while ( isspace ( *p ) )
p++;
q = p;
while ( !isspace ( *q ) && *q != '\0' )
q++;
*q = '\0';
if ( !strcasecmp( p, "multivalued" ) ) {
as->as_m_valued = 1;
} else if ( !strcasecmp( p, "multiple-entries" ) ) {
as->as_m_entries = 1;
} else if ( !strcasecmp( p, "final" ) ) {
as->as_final = 1;
} else if ( !strcasecmp( p, "local-native-mailbox" ) ) {
as->as_syntax = AS_SYNTAX_NATIVE_MB;
} else if ( !strcasecmp( p, "rfc822" ) ) {
as->as_syntax = AS_SYNTAX_RFC822;
} else if ( !strcasecmp( p, "rfc822-extended" ) ) {
as->as_syntax = AS_SYNTAX_RFC822_EXT;
} else if ( !strcasecmp( p, "dn" ) ) {
as->as_syntax = AS_SYNTAX_DN;
} else if ( !strcasecmp( p, "url" ) ) {
as->as_syntax = AS_SYNTAX_URL;
} else if ( !strncasecmp( p, "search-with-filter=", 19 ) ) {
as->as_syntax = AS_SYNTAX_BOOL_FILTER;
q = strchr( p, '=' );
if ( q ) {
p = q + 1;
while ( *q && !isspace( *q ) ) {
q++;
}
if ( *q ) {
*q = '\0';
as->as_param = strdup( p );
p = q + 1;
} else {
as->as_param = strdup( p );
p = q;
}
} else {
syslog( LOG_ALERT,
"Missing filter in %s", s );
exit( EX_TEMPFAIL );
}
} else if ( !strcasecmp( p, "host" ) ) {
as->as_kind = AS_SYNTAX_HOST;
} else if ( !strcasecmp( p, "forward-to-host" ) ) {
as->as_kind = AS_KIND_FORWARD_TO_HOST;
} else if ( !strcasecmp( p, "recipient" ) ) {
as->as_kind = AS_KIND_RECIPIENT;
} else if ( !strcasecmp( p, "errors" ) ) {
as->as_kind = AS_KIND_ERRORS;
} else if ( !strcasecmp( p, "request" ) ) {
as->as_kind = AS_KIND_REQUEST;
} else if ( !strcasecmp( p, "owner" ) ) {
as->as_kind = AS_KIND_OWNER;
} else {
syslog( LOG_ALERT,
"Unknown semantics word %s", p );
exit( EX_TEMPFAIL );
}
p = q + 1;
}
if ( attr_semantics == NULL ) {
attr_semantics = calloc(2, sizeof(AttrSemantics *));
if ( !attr_semantics ) {
syslog( LOG_ALERT, "Out of memory" );
exit( EX_TEMPFAIL );
}
attr_semantics[0] = as;
} else {
int size;
AttrSemantics **list_temp;
for ( size = 0; attr_semantics[size]; size++ )
;
size += 2;
list_temp = realloc( attr_semantics,
size*sizeof(AttrSemantics *) );
if ( !list_temp ) {
syslog( LOG_ALERT, "Out of memory" );
exit( EX_TEMPFAIL );
}
attr_semantics = list_temp;
attr_semantics[size-2] = as;
attr_semantics[size-1] = NULL;
}
}
static void
load_config( char *filespec )
{
FILE *cf;
char *line;
int lineno = 0;
char *p;
int rdnpref;
int typemask;
cf = fopen( filespec, "r" );
if ( !cf ) {
perror( "Opening config file" );
exit( EX_TEMPFAIL );
}
while ( ( line = get_config_line( cf,&lineno ) ) ) {
p = strpbrk( line, " \t" );
if ( !p ) {
syslog( LOG_ALERT,
"Missing space at line %d", lineno );
exit( EX_TEMPFAIL );
}
if ( !strncmp( line, "search", p-line ) ) {
p += strspn( p, " \t" );
/* TBC, get these */
rdnpref = 0;
typemask = 0xFF;
add_url( p, rdnpref, typemask );
} else if ( !strncmp(line, "attribute", p-line) ) {
p += strspn(p, " \t");
add_attr_semantics( p );
} else if ( !strncmp(line, "default-attributes", p-line) ) {
p += strspn(p, " \t");
add_def_attr( p );
} else if ( !strncmp(line, "group-classes", p-line) ) {
p += strspn(p, " \t");
add_single_to( &groupclasses, p );
} else {
syslog( LOG_ALERT,
"Unparseable config definition at line %d",
lineno );
exit( EX_TEMPFAIL );
}
}
fclose( cf );
}
static int
connect_to_x500( void )
{
int opt;
if ( (ld = ldap_init( ldaphost, 0 )) == NULL ) {
syslog( LOG_ALERT, "ldap_init failed" );
return( -1 );
}
/* TBC: Set this only when it makes sense
opt = MAIL500_MAXAMBIGUOUS;
ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &opt);
*/
opt = LDAP_DEREF_ALWAYS;
ldap_set_option(ld, LDAP_OPT_DEREF, &opt);
if ( ldap_simple_bind_s( ld, NULL, NULL ) != LDAP_SUCCESS ) {
syslog( LOG_ALERT, "ldap_simple_bind_s failed" );
return( -1 );
}
return( 0 );
}
static Group *
new_group( char *dn, Group **list, int *nlist )
{
int i;
Group *this_group;
for ( i = 0; i < *nlist; i++ ) {
if ( strcmp( dn, (*list)[i].g_dn ) == 0 ) {
syslog( LOG_ALERT, "group loop 2 detected (%s)", dn );
return NULL;
}
}
if ( *nlist == 0 ) {
*list = (Group *) malloc( sizeof(Group) );
} else {
*list = (Group *) realloc( *list, (*nlist + 1) *
sizeof(Group) );
}
this_group = *list;
(*list)[*nlist].g_errorsto = NULL;
(*list)[*nlist].g_members = NULL;
(*list)[*nlist].g_nmembers = 0;
/* save the group's dn so we can check for loops above */
(*list)[*nlist].g_dn = strdup( dn );
(*nlist)++;
return( this_group );
}
static void
split_address(
char *address,
char **localpart,
char **domainpart
)
{
char *p;
if ( ( p = strrchr( address, '@' ) ) == NULL ) {
*localpart = strdup( address );
*domainpart = NULL;
} else {
*localpart = malloc( p - address + 1 );
strncpy( *localpart, address, p - address );
(*localpart)[p - address] = '\0';
p++;
*domainpart = strdup( p );
}
}
static int
dn_search(
char **dnlist,
char *address,
char ***to,
int *nto,
Group **togroups,
int *ngroups,
Error **err,
int *nerr
)
{
int rc;
int i;
int resolved = 0;
LDAPMessage *res, *e;
struct timeval timeout;
timeout.tv_sec = MAIL500_TIMEOUT;
timeout.tv_usec = 0;
for ( i = 0; dnlist[i]; i++ ) {
if ( (rc = ldap_search_st( ld, dnlist[i], LDAP_SCOPE_BASE,
"(objectclass=*)", def_attr, 0,
&timeout, &res )) != LDAP_SUCCESS ) {
if ( rc == LDAP_NO_SUCH_OBJECT ) {
add_error( err, nerr, E_BADMEMBER, dnlist[i], NULL );
continue;
} else {
syslog( LOG_ALERT, "member search return 0x%x", rc );
unbind_and_exit( EX_TEMPFAIL );
}
} else {
if ( (e = ldap_first_entry( ld, res )) == NULL ) {
syslog( LOG_ALERT, "member search error parsing entry" );
unbind_and_exit( EX_TEMPFAIL );
}
if ( entry_engine( e, dnlist[i], address, to, nto,
togroups, ngroups, err, nerr,
USER | GROUP_MEMBERS ) ) {
resolved = 1;
}
}
}
return( resolved );
}
static int
search_ldap_url(
char *url,
Subst *substs,
char *address,
int rdnpref,
int multi_entry,
char ***to,
int *nto,
Group **togroups,
int *ngroups,
Error **err,
int *nerr,
int type
)
{
LDAPURLDesc *ludp;
char *p, *s, *d;
int i;
char filter[1024];
char realfilter[1024];
LDAPMessage *e, *res;
int rc;
char **attrlist;
struct timeval timeout;
int match;
int resolved = 0;
char *dn;
timeout.tv_sec = MAIL500_TIMEOUT;
timeout.tv_usec = 0;
rc = ldap_url_parse( url, &ludp );
if ( rc ) {
switch ( rc ) {
case LDAP_URL_ERR_NOTLDAP:
syslog( LOG_ALERT,
"Not an LDAP URL: %s", url );
break;
case LDAP_URL_ERR_BADENCLOSURE:
syslog( LOG_ALERT,
"Bad Enclosure in URL: %s", url );
break;
case LDAP_URL_ERR_BADURL:
syslog( LOG_ALERT,
"Bad URL: %s", url );
break;
case LDAP_URL_ERR_BADHOST:
syslog( LOG_ALERT,
"Host is invalid in URL: %s", url );
break;
case LDAP_URL_ERR_BADATTRS:
syslog( LOG_ALERT,
"Attributes are invalid in URL: %s", url );
break;
case LDAP_URL_ERR_BADSCOPE:
syslog( LOG_ALERT,
"Scope is invalid in URL: %s", url );
break;
case LDAP_URL_ERR_BADFILTER:
syslog( LOG_ALERT,
"Filter is invalid in URL: %s", url );
break;
case LDAP_URL_ERR_BADEXTS:
syslog( LOG_ALERT,
"Extensions are invalid in URL: %s", url );
break;
case LDAP_URL_ERR_MEM:
syslog( LOG_ALERT,
"Out of memory parsing URL: %s", url );
break;
case LDAP_URL_ERR_PARAM:
syslog( LOG_ALERT,
"bad parameter parsing URL: %s", url );
break;
default:
syslog( LOG_ALERT,
"Unknown error %d parsing URL: %s",
rc, url );
break;
}
add_error( err, nerr, E_BADMEMBER,
url, NULL );
return 0;
}
if ( substs ) {
for ( s = ludp->lud_filter, d = filter; *s; s++,d++ ) {
if ( *s == '%' ) {
s++;
if ( *s == '%' ) {
*d = '%';
continue;
}
for ( i = 0; substs[i].sub_char != '\0';
i++ ) {
if ( *s == substs[i].sub_char ) {
for ( p = substs[i].sub_value;
*p; p++,d++ ) {
*d = *p;
}
d--;
break;
}
}
if ( substs[i].sub_char == '\0' ) {
syslog( LOG_ALERT,
"unknown format %c", *s );
}
} else {
*d = *s;
}
}
*d = *s;
} else {
strncpy( filter, ludp->lud_filter, sizeof( filter ) - 1 );
filter[ sizeof( filter ) - 1 ] = '\0';
}
for ( s = filter, d = realfilter; *s; s++, d++ ) {
if ( *s == '*' ) {
*d++ = '\\';
}
*d = *s;
}
*d = '\0';
if ( ludp->lud_attrs ) {
attrlist = ludp->lud_attrs;
} else {
attrlist = def_attr;
}
res = NULL;
/* TBC: we don't read the host, dammit */
rc = ldap_search_st( ld, ludp->lud_dn, ludp->lud_scope,
realfilter, attrlist, 0,
&timeout, &res );
/* some other trouble - try again later */
if ( rc != LDAP_SUCCESS &&
rc != LDAP_SIZELIMIT_EXCEEDED ) {
syslog( LOG_ALERT, "return 0x%x from X.500",
rc );
unbind_and_exit( EX_TEMPFAIL );
}
match = ldap_count_entries( ld, res );
/* trouble - try again later */
if ( match == -1 ) {
syslog( LOG_ALERT, "error parsing result from X.500" );
unbind_and_exit( EX_TEMPFAIL );
}
if ( match == 1 || multi_entry ) {
for ( e = ldap_first_entry( ld, res ); e != NULL;
e = ldap_next_entry( ld, e ) ) {
dn = ldap_get_dn( ld, e );
resolved = entry_engine( e, dn, address, to, nto,
togroups, ngroups,
err, nerr, type );
if ( !resolved ) {
add_error( err, nerr, E_NOEMAIL, address, res );
}
}
return ( resolved );
}
/* more than one match - bounce with ambiguous user? */
if ( match > 1 ) {
LDAPMessage *next, *tmpres = NULL;
char *dn;
char **xdn;
/* not giving rdn preference - bounce with ambiguous user */
if ( rdnpref == 0 ) {
add_error( err, nerr, E_AMBIGUOUS, address, res );
return 0;
}
/*
* giving rdn preference - see if any entries were matched
* because of their rdn. If so, collect them to deal with
* later (== 1 we deliver, > 1 we bounce).
*/
for ( e = ldap_first_entry( ld, res ); e != NULL; e = next ) {
next = ldap_next_entry( ld, e );
dn = ldap_get_dn( ld, e );
xdn = ldap_explode_dn( dn, 1 );
/* XXX bad, but how else can we do it? XXX */
if ( strcasecmp( xdn[0], address ) == 0 ) {
ldap_delete_result_entry( &res, e );
ldap_add_result_entry( &tmpres, e );
}
ldap_value_free( xdn );
free( dn );
}
/* nothing matched by rdn - go ahead and bounce */
if ( tmpres == NULL ) {
add_error( err, nerr, E_AMBIGUOUS, address, res );
return 0;
/* more than one matched by rdn - bounce with rdn matches */
} else if ( (match = ldap_count_entries( ld, tmpres )) > 1 ) {
add_error( err, nerr, E_AMBIGUOUS, address, tmpres );
return 0;
/* trouble... */
} else if ( match < 0 ) {
syslog( LOG_ALERT, "error parsing result from X.500" );
unbind_and_exit( EX_TEMPFAIL );
}
/* otherwise one matched by rdn - send to it */
ldap_msgfree( res );
res = tmpres;
/* trouble */
if ( (e = ldap_first_entry( ld, res )) == NULL ) {
syslog( LOG_ALERT, "error parsing entry from X.500" );
unbind_and_exit( EX_TEMPFAIL );
}
dn = ldap_get_dn( ld, e );
resolved = entry_engine( e, dn, address, to, nto,
togroups, ngroups,
err, nerr, type );
if ( !resolved ) {
add_error( err, nerr, E_NOEMAIL, address, res );
/* Don't free res if we passed it to add_error */
} else {
ldap_msgfree( res );
}
}
return( resolved );
}
static int
url_list_search(
char **urllist,
char *address,
int multi_entry,
char ***to,
int *nto,
Group **togroups,
int *ngroups,
Error **err,
int *nerr,
int type
)
{
int i;
int resolved = 0;
for ( i = 0; urllist[i]; i++ ) {
if ( !strncasecmp( urllist[i], "mail:", 5 ) ) {
char *vals[2];
vals[0] = urllist[i] + 5;
vals[1] = NULL;
add_to( to, nto, vals );
resolved = 1;
} else if ( ldap_is_ldap_url( urllist[i] ) ) {
resolved = search_ldap_url( urllist[i], NULL,
address, 0, multi_entry,
to, nto, togroups, ngroups,
err, nerr, type );
} else {
/* Produce some sensible error here */
resolved = 0;
}
}
return( resolved );
}
/*
* The entry engine processes an entry. Normally, each entry will resolve
* to one or more values that will be added to the 'to' argument. This
* argument needs not be the global 'to' list, it may be the g_to field
* in a group. Groups have no special treatment, unless they require
* a special sender.
*/
static int
entry_engine(
LDAPMessage *e,
char *dn,
char *address,
char ***to,
int *nto,
Group **togroups,
int *ngroups,
Error **err,
int *nerr,
int type
)
{
char **vals;
int i;
int resolved = 0;
char ***current_to = to;
int *current_nto = nto;
Group *current_group = NULL;
char buf[1024];
char *localpart, *domainpart;
Subst substs[2];
for ( i=0; attr_semantics[i] != NULL; i++ ) {
AttrSemantics *as = attr_semantics[i];
int nent;
vals = ldap_get_values( ld, e, as->as_name );
if ( !vals || vals[0] == NULL ) {
continue;
}
nent = count_values( vals );
if ( nent > 1 && !as->as_m_valued ) {
add_error( err, nerr, E_AMBIGUOUS, address, e );
return( 0 );
}
switch ( as->as_kind ) {
case AS_KIND_RECIPIENT:
if ( ! ( type & ( USER | GROUP_MEMBERS ) ) )
break;
switch ( as->as_syntax ) {
case AS_SYNTAX_RFC822:
add_to( current_to, current_nto, vals );
resolved = 1;
break;
case AS_SYNTAX_RFC822_EXT:
add_to( current_to, current_nto, vals );
resolved = 1;
break;
case AS_SYNTAX_NATIVE_MB:
/* We used to concatenate mailHost if set here */
/*
* We used to send a copy to the vacation host
* if onVacation to uid@vacationhost
*/
add_to( current_to, current_nto, vals );
resolved = 1;
break;
case AS_SYNTAX_DN:
if ( dn_search( vals, address,
current_to, current_nto,
togroups, ngroups,
err, nerr ) ) {
resolved = 1;
}
break;
case AS_SYNTAX_URL:
if ( url_list_search( vals, address,
as->as_m_entries,
current_to, current_nto,
togroups, ngroups,
err, nerr, type ) ) {
resolved = 1;
}
break;
case AS_SYNTAX_BOOL_FILTER:
if ( strcasecmp( vals[0], "true" ) ) {
break;
}
substs[0].sub_char = 'D';
substs[0].sub_value = dn;
substs[1].sub_char = '\0';
substs[1].sub_value = NULL;
if ( url_list_search( vals, address,
as->as_m_entries,
current_to, current_nto,
togroups, ngroups,
err, nerr, type ) ) {
resolved = 1;
}
break;
default:
syslog( LOG_ALERT,
"Invalid syntax %d for kind %d",
as->as_syntax, as->as_kind );
break;
}
break;
case AS_KIND_ERRORS:
/* This is a group with special processing */
if ( type & GROUP_ERRORS ) {
switch (as->as_kind) {
case AS_SYNTAX_RFC822:
add_to( current_to, current_nto, vals );
resolved = 1;
break;
case AS_SYNTAX_URL:
default:
syslog( LOG_ALERT,
"Invalid syntax %d for kind %d",
as->as_syntax, as->as_kind );
}
} else {
current_group = new_group( dn, togroups,
ngroups );
current_to = &current_group->g_members;
current_nto = &current_group->g_nmembers;
split_address( address,
&localpart, &domainpart );
if ( domainpart ) {
sprintf( buf, "%s-%s@%s",
localpart, ERRORS,
domainpart );
free( localpart );
free( domainpart );
} else {
sprintf( buf, "%s-%s@%s",
localpart, ERRORS,
host );
free( localpart );
}
current_group->g_errorsto = strdup( buf );
}
break;
case AS_KIND_REQUEST:
/* This is a group with special processing */
if ( type & GROUP_REQUEST ) {
add_to( current_to, current_nto, vals );
resolved = 1;
}
break;
case AS_KIND_OWNER:
/* This is a group with special processing */
if ( type & GROUP_REQUEST ) {
add_to( current_to, current_nto, vals );
resolved = 1;
}
break;
default:
syslog( LOG_ALERT,
"Invalid kind %d", as->as_kind );
/* Error, TBC */
}
ldap_value_free( vals );
if ( as->as_final ) {
return( resolved );
}
}
return( resolved );
}
static int
search_bases(
char *filter,
Subst *substs,
char *name,
char ***to,
int *nto,
Group **togroups,
int *ngroups,
Error **err,
int *nerr,
int type
)
{
int b, resolved = 0;
for ( b = 0; base[b] != NULL; b++ ) {
if ( ! (base[b]->b_search & type) ) {
continue;
}
resolved = search_ldap_url( base[b]->b_url, substs, name,
base[b]->b_rdnpref,
base[b]->b_m_entries,
to, nto, togroups, ngroups,
err, nerr, type );
if ( resolved )
break;
}
return( resolved );
}
static void
do_address(
char *name,
char ***to,
int *nto,
Group **togroups,
int *ngroups,
Error **err,
int *nerr,
int type
)
{
struct timeval timeout;
char *localpart, *domainpart;
int resolved;
Subst substs[5];
/*
* Look up the name in X.500, add the appropriate addresses found
* to the to list, or to the err list in case of error. Groups are
* handled by the do_group routine, individuals are handled here.
* When looking up name, we follow the bases hierarchy, looking
* in base[0] first, then base[1], etc. For each base, there is
* a set of search filters to try, in order. If something goes
* wrong here trying to contact X.500, we exit with EX_TEMPFAIL.
* If the b_rdnpref flag is set, then we give preference to entries
* that matched name because it's their rdn, otherwise not.
*/
split_address( name, &localpart, &domainpart );
timeout.tv_sec = MAIL500_TIMEOUT;
timeout.tv_usec = 0;
substs[0].sub_char = 'm';
substs[0].sub_value = name;
substs[1].sub_char = 'h';
substs[1].sub_value = host;
substs[2].sub_char = 'l';
substs[2].sub_value = localpart;
substs[3].sub_char = 'd';
substs[3].sub_value = domainpart;
substs[4].sub_char = '\0';
substs[4].sub_value = NULL;
resolved = search_bases( NULL, substs, name,
to, nto, togroups, ngroups,
err, nerr, type );
if ( !resolved ) {
/* not resolved - bounce with user unknown */
if ( type == USER ) {
add_error( err, nerr, E_USERUNKNOWN, name, NULL );
} else {
add_error( err, nerr, E_GROUPUNKNOWN, name, NULL );
}
}
}
static void
send_message( char **to )
{
int pid;
#ifndef HAVE_WAITPID
WAITSTATUSTYPE status;
#endif
if ( debug ) {
char buf[1024];
int i;
strcpy( buf, to[0] );
for ( i = 1; to[i] != NULL; i++ ) {
strcat( buf, " " );
strcat( buf, to[i] );
}
syslog( LOG_ALERT, "send_message execing sendmail: (%s)", buf );
}
/* parent */
if ( (pid = fork()) != 0 ) {
#ifdef HAVE_WAITPID
waitpid( pid, (int *) NULL, 0 );
#else
wait4( pid, &status, WAIT_FLAGS, 0 );
#endif
/* child */
} else {
/* to includes sendmailargs */
execv( MAIL500_SENDMAIL, to );
syslog( LOG_ALERT, "execv failed" );
exit( EX_TEMPFAIL );
}
}
static void
send_group( Group *group, int ngroup )
{
int i, pid;
char **argv;
int argc;
char *iargv[7];
#ifndef HAVE_WAITPID
WAITSTATUSTYPE status;
#endif
for ( i = 0; i < ngroup; i++ ) {
(void) rewind( stdin );
iargv[0] = MAIL500_SENDMAIL;
iargv[1] = "-f";
iargv[2] = group[i].g_errorsto;
iargv[3] = "-oMrX.500";
iargv[4] = "-odi";
iargv[5] = "-oi";
iargv[6] = NULL;
argv = NULL;
argc = 0;
add_to( &argv, &argc, iargv );
add_to( &argv, &argc, group[i].g_members );
if ( debug ) {
char buf[1024];
int i;
strcpy( buf, argv[0] );
for ( i = 1; i < argc; i++ ) {
strcat( buf, " " );
strcat( buf, argv[i] );
}
syslog( LOG_ALERT, "execing sendmail: (%s)", buf );
}
/* parent */
if ( (pid = fork()) != 0 ) {
#ifdef HAVE_WAITPID
waitpid( pid, (int *) NULL, 0 );
#else
wait4( pid, &status, WAIT_FLAGS, 0 );
#endif
/* child */
} else {
execv( MAIL500_SENDMAIL, argv );
syslog( LOG_ALERT, "execv failed" );
exit( EX_TEMPFAIL );
}
}
}
static void
send_errors( Error *err, int nerr )
{
int pid, i, namelen;
FILE *fp;
int fd[2];
char *argv[8];
char buf[1024];
#ifndef HAVE_WAITPID
WAITSTATUSTYPE status;
#endif
if ( strcmp( MAIL500_BOUNCEFROM, mailfrom ) == 0 ) {
mailfrom = errorsfrom;
}
argv[0] = MAIL500_SENDMAIL;
argv[1] = "-oMrX.500";
argv[2] = "-odi";
argv[3] = "-oi";
argv[4] = "-f";
argv[5] = MAIL500_BOUNCEFROM;
argv[6] = mailfrom;
argv[7] = NULL;
if ( debug ) {
int i;
strcpy( buf, argv[0] );
for ( i = 1; argv[i] != NULL; i++ ) {
strcat( buf, " " );
strcat( buf, argv[i] );
}
syslog( LOG_ALERT, "execing sendmail: (%s)", buf );
}
if ( pipe( fd ) == -1 ) {
syslog( LOG_ALERT, "cannot create pipe" );
exit( EX_TEMPFAIL );
}
if ( (pid = fork()) != 0 ) {
if ( (fp = fdopen( fd[1], "w" )) == NULL ) {
syslog( LOG_ALERT, "cannot fdopen pipe" );
exit( EX_TEMPFAIL );
}
fprintf( fp, "To: %s\n", mailfrom );
fprintf( fp, "From: %s\n", errorsfrom );
fprintf( fp, "Subject: undeliverable mail\n" );
fprintf( fp, "\n" );
fprintf( fp, "The following errors occurred when trying to deliver the attached mail:\n" );
for ( i = 0; i < nerr; i++ ) {
namelen = strlen( err[i].e_addr );
fprintf( fp, "\n" );
switch ( err[i].e_code ) {
case E_USERUNKNOWN:
fprintf( fp, "%s: User unknown\n", err[i].e_addr );
break;
case E_GROUPUNKNOWN:
fprintf( fp, "%s: Group unknown\n", err[i].e_addr );
break;
case E_BADMEMBER:
fprintf( fp, "%s: Group member does not exist\n",
err[i].e_addr );
fprintf( fp, "This could be because the distinguished name of the person has changed\n" );
fprintf( fp, "If this is the case, the problem can be solved by removing and\n" );
fprintf( fp, "then re-adding the person to the group.\n" );
break;
case E_NOREQUEST:
fprintf( fp, "%s: Group exists but has no request address\n",
err[i].e_addr );
break;
case E_NOERRORS:
fprintf( fp, "%s: Group exists but has no errors-to address\n",
err[i].e_addr );
break;
case E_NOOWNER:
fprintf( fp, "%s: Group exists but has no owner\n",
err[i].e_addr );
break;
case E_AMBIGUOUS:
do_ambiguous( fp, &err[i], namelen );
break;
case E_NOEMAIL:
do_noemail( fp, &err[i], namelen );
break;
case E_MEMBERNOEMAIL:
fprintf( fp, "%s: Group member exists but does not have an email address\n",
err[i].e_addr );
break;
case E_JOINMEMBERNOEMAIL:
fprintf( fp, "%s: User has joined group but does not have an email address\n",
err[i].e_addr );
break;
case E_LOOP:
fprintf( fp, "%s: User has created a mail loop by adding address %s to their X.500 entry\n",
err[i].e_addr, err[i].e_loop );
break;
case E_NOMEMBERS:
fprintf( fp, "%s: Group has no members\n",
err[i].e_addr );
break;
default:
syslog( LOG_ALERT, "unknown error %d", err[i].e_code );
unbind_and_exit( EX_TEMPFAIL );
break;
}
}
fprintf( fp, "\n------- The original message sent:\n\n" );
while ( fgets( buf, sizeof(buf), stdin ) != NULL ) {
fputs( buf, fp );
}
fclose( fp );
#ifdef HAVE_WAITPID
waitpid( pid, (int *) NULL, 0 );
#else
wait4( pid, &status, WAIT_FLAGS, 0 );
#endif
} else {
dup2( fd[0], 0 );
execv( MAIL500_SENDMAIL, argv );
syslog( LOG_ALERT, "execv failed" );
exit( EX_TEMPFAIL );
}
}
static void
do_noemail( FILE *fp, Error *err, int namelen )
{
int i, last;
char *dn, *rdn;
char **ufn, **vals;
fprintf(fp, "%s: User has no email address registered.\n",
err->e_addr );
fprintf( fp, "%*s Name, title, postal address and phone for '%s':\n\n",
namelen, " ", err->e_addr );
/* name */
dn = ldap_get_dn( ld, err->e_msg );
ufn = ldap_explode_dn( dn, 1 );
rdn = strdup( ufn[0] );
if ( strcasecmp( rdn, err->e_addr ) == 0 ) {
if ( (vals = ldap_get_values( ld, err->e_msg, "cn" ))
!= NULL ) {
for ( i = 0; vals[i]; i++ ) {
last = strlen( vals[i] ) - 1;
if ( isdigit((unsigned char) vals[i][last]) ) {
rdn = strdup( vals[i] );
break;
}
}
ldap_value_free( vals );
}
}
fprintf( fp, "%*s %s\n", namelen, " ", rdn );
free( dn );
free( rdn );
ldap_value_free( ufn );
/* titles or descriptions */
if ( (vals = ldap_get_values( ld, err->e_msg, "title" )) == NULL &&
(vals = ldap_get_values( ld, err->e_msg, "description" ))
== NULL ) {
fprintf( fp, "%*s No title or description registered\n",
namelen, " " );
} else {
for ( i = 0; vals[i] != NULL; i++ ) {
fprintf( fp, "%*s %s\n", namelen, " ", vals[i] );
}
ldap_value_free( vals );
}
/* postal address */
if ( (vals = ldap_get_values( ld, err->e_msg, "postalAddress" ))
== NULL ) {
fprintf( fp, "%*s No postal address registered\n", namelen,
" " );
} else {
fprintf( fp, "%*s ", namelen, " " );
for ( i = 0; vals[0][i] != '\0'; i++ ) {
if ( vals[0][i] == '$' ) {
fprintf( fp, "\n%*s ", namelen, " " );
while ( isspace((unsigned char) vals[0][i+1]) )
i++;
} else {
fprintf( fp, "%c", vals[0][i] );
}
}
fprintf( fp, "\n" );
ldap_value_free( vals );
}
/* telephone number */
if ( (vals = ldap_get_values( ld, err->e_msg, "telephoneNumber" ))
== NULL ) {
fprintf( fp, "%*s No phone number registered\n", namelen,
" " );
} else {
for ( i = 0; vals[i] != NULL; i++ ) {
fprintf( fp, "%*s %s\n", namelen, " ", vals[i] );
}
ldap_value_free( vals );
}
}
/* ARGSUSED */
static void
do_ambiguous( FILE *fp, Error *err, int namelen )
{
int i, last;
char *dn, *rdn;
char **ufn, **vals;
LDAPMessage *e;
i = ldap_result2error( ld, err->e_msg, 0 );
fprintf( fp, "%s: Ambiguous user. %s%d matches found:\n\n",
err->e_addr, i == LDAP_SIZELIMIT_EXCEEDED ? "First " : "",
ldap_count_entries( ld, err->e_msg ) );
for ( e = ldap_first_entry( ld, err->e_msg ); e != NULL;
e = ldap_next_entry( ld, e ) ) {
dn = ldap_get_dn( ld, e );
ufn = ldap_explode_dn( dn, 1 );
rdn = strdup( ufn[0] );
if ( strcasecmp( rdn, err->e_addr ) == 0 ) {
if ( (vals = ldap_get_values( ld, e, "cn" )) != NULL ) {
for ( i = 0; vals[i]; i++ ) {
last = strlen( vals[i] ) - 1;
if (isdigit((unsigned char) vals[i][last])) {
rdn = strdup( vals[i] );
break;
}
}
ldap_value_free( vals );
}
}
/*
if ( isgroup( e ) ) {
vals = ldap_get_values( ld, e, "description" );
} else {
vals = ldap_get_values( ld, e, "title" );
}
*/
vals = ldap_get_values( ld, e, "description" );
fprintf( fp, " %-20s %s\n", rdn, vals ? vals[0] : "" );
for ( i = 1; vals && vals[i] != NULL; i++ ) {
fprintf( fp, " %s\n", vals[i] );
}
free( dn );
free( rdn );
ldap_value_free( ufn );
if ( vals != NULL )
ldap_value_free( vals );
}
}
static int
count_values( char **list )
{
int i;
for ( i = 0; list && list[i] != NULL; i++ )
; /* NULL */
return( i );
}
static void
add_to( char ***list, int *nlist, char **new )
{
int i, nnew, oldnlist;
nnew = count_values( new );
oldnlist = *nlist;
if ( *list == NULL || *nlist == 0 ) {
*list = (char **) malloc( (nnew + 1) * sizeof(char *) );
*nlist = nnew;
} else {
*list = (char **) realloc( *list, *nlist * sizeof(char *) +
nnew * sizeof(char *) + sizeof(char *) );
*nlist += nnew;
}
for ( i = 0; i < nnew; i++ )
(*list)[i + oldnlist] = strdup( new[i] );
(*list)[*nlist] = NULL;
}
static void
add_single_to( char ***list, char *new )
{
int nlist;
if ( *list == NULL ) {
nlist = 0;
*list = (char **) malloc( 2 * sizeof(char *) );
} else {
nlist = count_values( *list );
*list = (char **) realloc( *list,
( nlist + 2 ) * sizeof(char *) );
}
(*list)[nlist] = strdup( new );
(*list)[nlist+1] = NULL;
}
static int
isgroup( LDAPMessage *e )
{
int i, j;
char **oclist;
if ( !groupclasses ) {
return( 0 );
}
oclist = ldap_get_values( ld, e, "objectClass" );
for ( i = 0; oclist[i] != NULL; i++ ) {
for ( j = 0; groupclasses[j] != NULL; j++ ) {
if ( strcasecmp( oclist[i], groupclasses[j] ) == 0 ) {
ldap_value_free( oclist );
return( 1 );
}
}
}
ldap_value_free( oclist );
return( 0 );
}
static void
add_error( Error **err, int *nerr, int code, char *addr, LDAPMessage *msg )
{
if ( *nerr == 0 ) {
*err = (Error *) malloc( sizeof(Error) );
} else {
*err = (Error *) realloc( *err, (*nerr + 1) * sizeof(Error) );
}
(*err)[*nerr].e_code = code;
(*err)[*nerr].e_addr = strdup( addr );
(*err)[*nerr].e_msg = msg;
(*nerr)++;
}
static void
unbind_and_exit( int rc )
{
int i;
if ( (i = ldap_unbind( ld )) != LDAP_SUCCESS )
syslog( LOG_ALERT, "ldap_unbind failed %d\n", i );
exit( rc );
}
# Mostly rfc1123 compliant sendmail.cf
#
# Mail sendmail-admins-request@itd.umich.edu to join
# sendmail-admins@itd.umich.edu. sendmail-admins carries information
# regarding this sendmail.cf, including announcements of changes
# and discussions of interest to admins.
#
DWtotalrecall
Dw$W.rs.itd.umich.edu
DBcunyvm.cuny.edu
DUdestroyer.rs.itd.umich.edu
DV2.2
De$j sendmail ($v/$V) ready at $b
Dj$w
DlFrom $g $d
Dnmailer-daemon
Do.:%@!^=/[]
Dq$?x\"$x\" <$g>$|$g$.
OA/etc/aliases
OQ/var/spool/mqueue
OH/usr/lib/sendmail.hf
OS/usr/lib/sendmail.st
OP
OD
OX10
Ox5
Ou1
Og1
Odb
OF0600
OL9
Oo
Or15m
Os
OT3d
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
Pspecial-delivery=100
Pfirst-class=0
Pjunk=-100
# 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 <>
S3
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 <>
S5
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.
S6
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"
S0
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$*<@umich.edu>$* $#mail500$@umich.edu$:<$1>
R$*<@itd.umich.edu>$* $#mail500$@itd.umich.edu$:<$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.
S7
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.
S8
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.
S9
R$*<@$w>$* $>10$1<@>$2 remove local name
R<$W!>$+ $>10<!>$1
R<@umich.edu>$*:$* $>10<@>$1:$2
R$+%$+<@umich.edu> $>10$1%$2<@>
R$+!$+<@umich.edu> $>10$1!$2<@>
# Called only from above. Refocus and loop.
S10
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
S11
#R$*<@inquiry.org>$* $: $>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.
S12
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?
S13
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.
S14
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.
S15
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
S16
R$+ $@ $1
#
# Called on all outgoing addresses. Used to remove the <> focus
#
S4
R$*<$+>$* $@ $1$2$3 defocus
PROGRAMS= rcpt500
SRCS= main.c cmds.c help.c query.c
XSRCS= version.c
OBJS= main.o cmds.o help.o query.o
HDRS= rcpt500.h
LDAP_INCDIR= ../../include
LDAP_LIBDIR= ../../libraries
XLIBS = -lldap -llber -llutil
XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS)
rcpt500 : version.o
$(LTLINK) -o $@ version.o $(OBJS) $(LIBS)
version.c: ${OBJS} $(LDAP_LIBDEPEND)
@-$(RM) $@
$(MKVERSION) rcpt500 > $@
install-local: $(PROGRAMS) rcpt500.help FORCE
-$(MKDIR) $(libexecdir) $(datadir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 rcpt500 $(libexecdir)
-$(MV) $(datadir)/rcpt500.help $(datadir)/rcpt500.help-
$(INSTALL) $(INSTALLFLAGS) -m 644 $(srcdir)/rcpt500.help $(datadir)
LDAP rcpt500 mail query server README
OVERVIEW
This is a mail-query server that answers X.500 white pages queries.
It is designed to be run out of your mail systems alias file, or the
equivalent. It expects to be fed the entire contents (including
headers) of an RFC822 message via standard input. It parses the
message, looking in the Subject: field or the body of the message
for a command that it recognizes.
The commands currently recognized are listed in the file cmd.c. They
all map into two actual operations the server performs: an X.500 query
or a help command. If no recognizable command is found in the
Subject: or body of the message, help is assumed. An appropriate
reply is sent to the sender of the message in response to the command.
The help command returns the contents of the file rcpt500.help. You
can modify the contents as appropriate for your local site.
The query command performs a series of X.500 searches to try to find
a person that matches the object of the query. If more than one
X.500 entry matches, a list is returned. If exactly one is matched,
detailed information is returned. Here is an example message and rcpt500
generated reply:
Query message:
Mail x500-query@umich.edu
Subject: find tim howes
.
Reply from rcpt500:
Message-Id: <199209161526.AA12041@umich.edu>
Date: Wed, 16 Sep 1992 11:26:17 -0400
From: "X.500 Query Program" <X500-Query@umich.edu>
Subject: Re: find tim howes
In-Reply-To: Your message of "Wed, 16 Sep 1992 11:26:12 -0400"
<199209161526.AA26144@terminator.cc.umich.edu>
To: "Mark Smith" <mcs@terminator.cc.umich.edu>
One exact match was found for 'tim howes':
"Timothy A Howes, Information Technology Division, Faculty and Staff"
Also known as:
Timothy Howes
Timothy A Howes 1
Timothy A Howes
Tim Howes
E-mail address:
tim@terminator.cc.umich.edu
Fax number:
+1 313 764 5140 (argus building)
Business phone:
+1 313 747-4454
Business address:
ITD Research Systems
535 W William St.
Ann Arbor, MI 48103-4943
Title:
Systems Research Programmer II, Research Systems
Uniqname:
tim
If you want to try out rcpt500 yourself before installing it at your site,
send a message to x500-query@umich.edu (we have a server running
there that serves University of Michigan white pages information).
CONFIGURING AND RUNNING rcpt500 AT YOUR LOCAL SITE
You will probably need to make changes to the file ldapconfig.h.edit to
configure rcpt500 for your local site. There are comments in the file
describing each variable you might need to change. Then type make in
the rcpt500 directory to make sure things are up to date. You will
need to install the rcpt500 binary and help files (make install). This
all assumes you have built the LDAP libraries already. If in doubt,
just do a make from the top of the LDAP distribution.
You will then need to set up an alias that your users can send mail
to that will feed the messages to rcpt500. At our site, we run sendmail
so the alias is in /usr/lib/aliases and looks like:
x500-query: "|/usr/local/etc/rcpt500 -l"
The available command line options for rcpt500 are:
-l enable logging of requests via the syslog
LOG_DAEMON facility
-h ldaphost specify LDAP server host to connect to
-b searchbase specify starting point of X.500 searches
-a don't deference aliases during searches
-s stripcount remove "stripcount" DN components from user
friendly form names that are displayed
-z sizelimit return at most "sizelimit" entries
-u dapuser DN to bind to X.500 as when searching
The search and display behavior is defined in the ldapfilter.conf and
ldaptemplates.conf files.
FEEDBACK / PROBLEM REPORTS / DISCUSSIONS
The software is provided as is without any express or implied
warranty, but you can report problems using our Issue Tracking
System:
http://www.OpenLDAP.com/its/
or by sending e-mail to:
OpenLDAP-its@OpenLDAP.org
Additional mailing lists are available. Please see:
http://www.OpenLDAP.com/lists/
/*
* cmds.c: command table for rcpt500 (X.500 email query responder)
*
* 18 June 1992 by Mark C Smith
* Copyright (c) 1992 The Regents of The University of Michigan
* All Rights Reserved
*/
#include "portable.h"
#include <ac/stdlib.h>
#include "rcpt500.h"
struct command cmds[] = {
"help", help_cmd, /* help must be the first command */
"query for", query_cmd, /* must come before "query for" */
"query", query_cmd,
"find", query_cmd,
"read", query_cmd,
"search for", query_cmd, /* must come before "search" */
"search", query_cmd,
"lookup", query_cmd,
"look up", query_cmd,
"show", query_cmd,
"finger", query_cmd,
"whois", query_cmd,
"who is", query_cmd,
"locate", query_cmd,
NULL, NULL /* end of command list */
};
/*
* help.c: for rcpt500 (X.500 email query responder)
*
* 16 June 1992 by Mark C Smith
* Copyright (c) 1992 The Regents of The University of Michigan
* All Rights Reserved
*/
#include "portable.h"
#include <stdio.h>
#include <ac/syslog.h>
#include <ac/string.h>
#include <ac/unistd.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include "ldap_defaults.h"
#include "rcpt500.h"
int
help_cmd(struct msginfo *msgp, char *reply)
{
int fd, len;
if (( fd = open( RCPT500_HELPFILE, O_RDONLY )) == -1 ) {
if ( dosyslog ) {
syslog( LOG_ERR, "open help file: %m" );
}
strcat( reply, "Unable to access the help file. Sorry!\n" );
return( 0 );
}
len = read( fd, reply + strlen( reply ), MAXSIZE );
close( fd );
if ( len == -1 ) {
if ( dosyslog ) {
syslog( LOG_ERR, "read help file: %m" );
}
strcat( reply, "Unable to read the help file. Sorry!\n" );
return( 0 );
}
*(reply + len ) = '\0';
return( 0 );
}
/*
* main.c: for rcpt500 (X.500 email query responder)
*
* 16 June 1992 by Mark C Smith
* Copyright (c) 1992 The Regents of The University of Michigan
* All Rights Reserved
*/
#include "portable.h"
#include <stdio.h>
#include <ac/stdlib.h>
#include <ac/ctype.h>
#include <ac/signal.h>
#include <ac/string.h>
#include <ac/syslog.h>
#include <ac/unistd.h>
#include "ldap_defaults.h"
#include "rcpt500.h"
int dosyslog = 0;
#ifdef LDAP_CONNECTIONLESS
int do_cldap = 0;
#endif /* LDAP_CONNECTIONLESS */
int derefaliases = 1;
int sizelimit = RCPT500_SIZELIMIT;
int rdncount = RCPT500_RDNCOUNT;
int ldapport = 0;
char *ldaphost = NULL;
char *searchbase = NULL;
char *dapuser = NULL;
char *filterfile = FILTERFILE;
char *templatefile = TEMPLATEFILE;
static char reply[ MAXSIZE * RCPT500_LISTLIMIT ];
/*
* functions
*/
static int read_msg(FILE *fp, struct msginfo *msgp);
static char *read_hdr(FILE *fp, int off, char *buf, int MAXSIZEe, char **ln_p);
static int send_reply(struct msginfo *msgp, char *body);
static int find_command(char *text, char **argp);
/*
* main is invoked by sendmail via the alias file
* the entire incoming message gets piped to our standard input
*/
int
main( int argc, char **argv )
{
char *prog, *usage = "%s [-l] [-U] [-h ldaphost] [-p ldapport] [-b searchbase] [-a] [-z sizelimit] [-u dapuser] [-f filterfile] [-t templatefile] [-c rdncount]\n";
struct msginfo msg;
int c, errflg;
*reply = '\0';
if (( prog = strrchr( argv[ 0 ], '/' )) == NULL ) {
prog = strdup( argv[ 0 ] );
} else {
prog = strdup( prog + 1 );
}
errflg = 0;
while (( c = getopt( argc, argv, "alUh:b:s:z:f:t:p:c:" )) != EOF ) {
switch( c ) {
case 'a':
derefaliases = 0;
break;
case 'l':
dosyslog = 1;
break;
case 'U':
#ifdef LDAP_CONNECTIONLESS
do_cldap = 1;
#else /* LDAP_CONNECTIONLESS */
fprintf( stderr,
"Compile with -DLDAP_CONNECTIONLESS for -U support\n" );
#endif /* LDAP_CONNECTIONLESS */
break;
case 'b':
searchbase = optarg;
break;
case 'h':
ldaphost = optarg;
break;
case 'p':
ldapport = atoi( optarg );
break;
case 'z':
sizelimit = atoi( optarg );
break;
case 'u':
dapuser = optarg;
break;
case 'f':
filterfile = optarg;
break;
case 't':
templatefile = optarg;
break;
case 'c':
rdncount = atoi( optarg );
break;
default:
++errflg;
}
}
if ( errflg || optind < argc ) {
fprintf( stderr, usage, prog );
exit( EXIT_FAILURE );
}
#ifdef SIGPIPE
(void) SIGNAL( SIGPIPE, SIG_IGN );
#endif
if ( dosyslog ) {
/*
* if syslogging requested, initialize
*/
#ifdef LOG_DAEMON
openlog( prog, OPENLOG_OPTIONS, LOG_DAEMON );
#else
openlog( prog, OPENLOG_OPTIONS );
#endif
}
if ( read_msg( stdin, &msg ) < 0 ) {
if ( dosyslog ) {
syslog( LOG_INFO, "unparseable message ignored" );
}
exit( 0 ); /* so as not to give sendmail an error */
}
if ( dosyslog ) {
syslog( LOG_INFO, "processing command \"%s %s\" from %s",
( msg.msg_command < 0 ) ? "Unknown" :
cmds[ msg.msg_command ].cmd_text,
( msg.msg_arg == NULL ) ? "" : msg.msg_arg, msg.msg_replyto );
}
if ( msg.msg_command < 0 ) {
msg.msg_command = 0; /* unknown command == help command */
}
/*
sprintf( reply, "Your request was interpreted as: %s %s\n\n",
cmds[ msg.msg_command ].cmd_text, msg.msg_arg );
*/
(*cmds[ msg.msg_command ].cmd_handler)( &msg, reply );
if ( send_reply( &msg, reply ) < 0 ) {
if ( dosyslog ) {
syslog( LOG_INFO, "reply failed: %m" );
}
exit( 0 ); /* so as not to give sendmail an error */
}
if ( dosyslog ) {
syslog( LOG_INFO, "reply OK" );
}
exit( 0 );
}
static int
read_msg( FILE *fp, struct msginfo *msgp )
{
char buf[ MAXSIZE ], *line;
int command = -1;
msgp->msg_replyto = msgp->msg_date = msgp->msg_subject = NULL;
line = NULL;
while( 1 ) {
if ( line == NULL ) {
if (( line = fgets( buf, MAXSIZE, fp )) == NULL ) {
break;
}
buf[ strlen( buf ) - 1 ] = '\0'; /* remove trailing newline */
}
if ( *buf == '\0' ) { /* start of message body */
break;
}
if ( strncasecmp( buf, "Reply-To:", 9 ) == 0 ) {
if ( msgp->msg_replyto != NULL ) {
free( msgp->msg_replyto );
}
msgp->msg_replyto = read_hdr( fp, 9, buf, MAXSIZE, &line );
} else if ( strncasecmp( buf, "From:", 5 ) == 0 &&
msgp->msg_replyto == NULL ) {
msgp->msg_replyto = read_hdr( fp, 5, buf, MAXSIZE, &line );
} else if ( strncasecmp( buf, "Date:", 5 ) == 0 ) {
msgp->msg_date = read_hdr( fp, 5, buf, MAXSIZE, &line );
} else if ( strncasecmp( buf, "Message-ID:", 5 ) == 0 ) {
msgp->msg_messageid = read_hdr( fp, 11, buf, MAXSIZE, &line );
} else if ( strncasecmp( buf, "Subject:", 8 ) == 0 ) {
if (( msgp->msg_subject =
read_hdr( fp, 8, buf, MAXSIZE, &line )) != NULL ) {
command = find_command( msgp->msg_subject, &msgp->msg_arg );
}
} else {
line = NULL; /* discard current line */
}
}
while ( command < 0 && line != NULL ) {
/*
* read the body of the message, looking for commands
*/
if (( line = fgets( buf, MAXSIZE, fp )) != NULL ) {
buf[ strlen( buf ) - 1 ] = '\0'; /* remove trailing newline */
command = find_command( buf, &msgp->msg_arg );
}
}
if ( msgp->msg_replyto == NULL ) {
return( -1 );
}
msgp->msg_command = command;
return( 0 );
}
static char *
read_hdr( FILE *fp, int offset, char *buf, int MAXSIZEe, char **linep )
{
char *hdr;
for ( hdr = buf + offset; isspace( (unsigned char) *hdr ); ++hdr ) {
;
}
if (( hdr = strdup( hdr )) == NULL ) {
if ( dosyslog ) {
syslog( LOG_ERR, "strdup: %m" );
}
exit( EXIT_FAILURE );
}
while ( 1 ) {
*linep = fgets( buf, MAXSIZE, fp );
buf[ strlen( buf ) - 1 ] = '\0'; /* remove trailing newline */
if ( *linep == NULL || !isspace( (unsigned char) **linep )) {
break;
}
if (( hdr = realloc( hdr, strlen( hdr ) +
strlen( *linep ) + 3 )) == NULL) {
if ( dosyslog ) {
syslog( LOG_ERR, "realloc: %m" );
}
exit( EXIT_FAILURE );
}
strcat( hdr, "\n" );
strcat( hdr, *linep );
}
return( hdr );
}
static int
send_reply( struct msginfo *msgp, char *body )
{
char buf[ MAXSIZE ];
FILE *cmdpipe;
int rc;
if (( cmdpipe = popen( RCPT500_PIPEMAILCMD, "w" )) == NULL ) {
if ( dosyslog ) {
syslog( LOG_ERR, "popen pipemailcmd failed: %m" );
}
return( -1 );
}
/*
* send the headers
*/
sprintf( buf, "From: %s\n", RCPT500_FROM );
rc = fwrite( buf, strlen( buf ), 1, cmdpipe );
if ( rc == 1 ) {
if ( msgp->msg_subject != NULL ) {
sprintf( buf, "Subject: Re: %s\n", msgp->msg_subject );
} else {
sprintf( buf, "Subject: query response\n" );
}
rc = fwrite( buf, strlen( buf ), 1, cmdpipe );
}
if ( rc == 1 && msgp->msg_date != NULL ) {
/*
* add "In-reply-to:" header
*/
if ( msgp->msg_messageid == NULL ) {
sprintf( buf, "In-reply-to: Your message of \"%s\"\n",
msgp->msg_date );
} else {
sprintf( buf,
"In-reply-to: Your message of \"%s\"\n %s\n",
msgp->msg_date, msgp->msg_messageid );
}
rc = fwrite( buf, strlen( buf ), 1, cmdpipe );
}
if ( rc == 1 ) {
sprintf( buf, "To: %s\n", msgp->msg_replyto );
rc = fwrite( buf, strlen( buf ), 1, cmdpipe );
}
/*
* send the header/body separator (blank line)
*/
if ( rc == 1 ) {
rc = fwrite( "\n", 1, 1, cmdpipe );
}
/*
* send the body
*/
if ( rc == 1 ) {
rc = fwrite( body, strlen( body ), 1, cmdpipe );
}
if ( rc != 1 && dosyslog ) {
syslog( LOG_ERR, "write to binmail failed: %m" );
}
if ( pclose( cmdpipe ) < 0 ) {
if ( dosyslog ) {
syslog( LOG_ERR, "pclose binmail failed: %m" );
}
return( -1 );
}
return( rc == 1 ? 0 : -1 );
}
static int
find_command( char *text, char **argp )
{
int i;
char *s, *p;
static char argbuf[ MAXSIZE ];
p = text;
for ( s = argbuf; *p != '\0'; ++p ) {
*s++ = TOLOWER( (unsigned char) *p );
}
*s = '\0';
for ( i = 0; cmds[ i ].cmd_text != NULL; ++i ) {
if (( s = strstr( argbuf, cmds[ i ].cmd_text )) != NULL
&& isspace( (unsigned char) s[ strlen( cmds[ i ].cmd_text ) ] )) {
strcpy( argbuf, text + (s - argbuf) + strlen( cmds[ i ].cmd_text ));
*argp = argbuf;
while ( isspace( (unsigned char) **argp )) {
++(*argp);
}
return( i );
}
}
return( -1 );
}
/*
* query.c: for rcpt500 (X.500 email query responder)
*
* 18 June 1992 by Mark C Smith
* Copyright (c) 1992 The Regents of The University of Michigan
* All Rights Reserved
*/
#include "portable.h"
#include <ac/stdlib.h>
#include <ac/ctype.h>
#include <ac/string.h>
#include <ac/syslog.h>
#include <ac/time.h>
#include <stdio.h>
#include <ldap.h>
#include <disptmpl.h>
#include "rcpt500.h"
#include "ldap_defaults.h"
static char buf[ MAXSIZE ];
static char *errpreface = "Your query failed: ";
static void close_ldap(LDAP *ld);
static void append_entry_list(char *rep, char *qu, LDAP *ld, LDAPMessage *msg);
static int append_text(void *reply, char *text, ber_len_t len);
static int do_read (LDAP *ld, char *dn, char *rep, struct ldap_disptmpl *tmp);
static void report_ldap_err (LDAP *ldp, char *reply);
static void remove_trailing_space (char *s);
int
query_cmd( struct msginfo *msgp, char *reply )
{
LDAP *ldp;
LDAPMessage *ldmsgp, *entry;
char *dn;
int matches, rc, ld_errno, ufn;
LDAPFiltDesc *lfdp;
LDAPFiltInfo *lfi;
struct ldap_disptmpl *tmpllist = NULL;
static char *attrs[] = { "cn", "title",
#ifdef RCPT500_SORT_ATTR
RCPT500_SORT_ATTR,
#endif
NULL };
ufn = 0;
if ( msgp->msg_arg == NULL ) {
return( help_cmd( msgp, reply ));
}
remove_trailing_space( msgp->msg_arg );
if ( *msgp->msg_arg == '\0' ) {
return( help_cmd( msgp, reply ));
}
if (( lfdp = ldap_init_getfilter( filterfile )) == NULL ) {
strcat( reply, errpreface );
strcat( reply, "filter file configuration error. Try again later." );
return( 0 );
}
/*
* open connection to LDAP server and bind as dapuser
*/
#ifdef LDAP_CONNECTIONLESS
if ( do_cldap )
ldp = cldap_open( ldaphost, ldapport );
else
#endif /* LDAP_CONNECTIONLESS */
ldp = ldap_init( ldaphost, ldapport );
if ( ldp == NULL ) {
strcat( reply, errpreface );
strcat( reply, "X.500 service unavailable. Try again later." );
ldap_getfilter_free( lfdp );
return( 0 );
}
#ifdef LDAP_CONNECTIONLESS
if ( !do_cldap )
#endif /* LDAP_CONNECTIONLESS */
if ( ldap_simple_bind_s( ldp, dapuser, NULL ) != LDAP_SUCCESS ) {
report_ldap_err( ldp, reply );
close_ldap( ldp );
ldap_getfilter_free( lfdp );
return( 0 );
}
/*
* set options for search and build filter
*/
ldap_set_option(ldp, LDAP_OPT_DEREF, &derefaliases);
ldap_set_option(ldp, LDAP_OPT_SIZELIMIT, &sizelimit);
matches = 0;
#ifdef RCPT500_UFN
#ifdef LDAP_CONNECTIONLESS
if ( !do_cldap && strchr( msgp->msg_arg, ',' ) != NULL ) {
#else /* LDAP_CONNECTIONLESS */
if ( strchr( msgp->msg_arg, ',' ) != NULL ) {
#endif /* LDAP_CONNECTIONLESS */
struct timeval tv;
ldap_ufn_setprefix( ldp, searchbase );
if (( rc = ldap_ufn_search_s( ldp, msgp->msg_arg, attrs, 0, &ldmsgp ))
!= LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
&& rc != LDAP_TIMELIMIT_EXCEEDED ) {
report_ldap_err( ldp, reply );
close_ldap( ldp );
ldap_getfilter_free( lfdp );
return( 0 );
}
matches = ldap_count_entries( ldp, ldmsgp );
ufn = 1;
} else {
#endif /* RCPT500_UFN */
for ( lfi = ldap_getfirstfilter( lfdp, "rcpt500", msgp->msg_arg );
lfi != NULL; lfi = ldap_getnextfilter( lfdp )) {
#ifdef LDAP_CONNECTIONLESS
if ( do_cldap )
rc = cldap_search_s( ldp, searchbase, LDAP_SCOPE_SUBTREE,
lfi->lfi_filter, attrs, 0, &ldmsgp, dapuser );
else
#endif /* LDAP_CONNECTIONLESS */
rc = ldap_search_s( ldp, searchbase, LDAP_SCOPE_SUBTREE,
lfi->lfi_filter, attrs, 0, &ldmsgp );
if ( rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
&& rc != LDAP_TIMELIMIT_EXCEEDED ) {
report_ldap_err( ldp, reply );
close_ldap( ldp );
ldap_getfilter_free( lfdp );
return( 0 );
}
if (( matches = ldap_count_entries( ldp, ldmsgp )) != 0 ) {
break;
}
if ( ldmsgp != NULL ) {
ldap_msgfree( ldmsgp );
}
}
#ifdef RCPT500_UFN
}
#endif /* RCPT500_UFN */
if ( matches == 0 ) {
sprintf( buf, "No matches were found for '%s'\n", msgp->msg_arg );
strcat( reply, buf );
close_ldap( ldp );
ldap_getfilter_free( lfdp );
return( 0 );
}
ld_errno = 0;
ldap_get_option(ldp, LDAP_OPT_ERROR_NUMBER, &ld_errno);
if ( ld_errno == LDAP_TIMELIMIT_EXCEEDED
|| ld_errno == LDAP_SIZELIMIT_EXCEEDED ) {
strcat( reply, "(Partial results only - a limit was exceeded)\n" );
}
if ( matches <= RCPT500_LISTLIMIT ) {
sprintf( buf, "%d %s match%s found for '%s':\n\n", matches,
ufn ? "UFN" : lfi->lfi_desc,
( matches > 1 ) ? "es" : "", msgp->msg_arg );
strcat( reply, buf );
if (( rc = ldap_init_templates( templatefile, &tmpllist )) != 0 ) {
sprintf( buf, "%s ldap_init_templates( %s ) failed (error %d)\n",
errpreface, templatefile, rc );
strcat( reply, buf );
}
for ( entry = ldap_first_entry( ldp, ldmsgp ); entry != NULL; ) {
dn = ldap_get_dn( ldp, entry );
if ( do_read( ldp, dn, reply, tmpllist ) != LDAP_SUCCESS ) {
report_ldap_err( ldp, reply );
}
free( dn );
if (( entry = ldap_next_entry( ldp, entry )) != NULL ) {
strcat( reply, "\n-------\n\n" );
}
}
if ( tmpllist != NULL ) {
ldap_free_templates( tmpllist );
}
ldap_msgfree( ldmsgp );
} else {
sprintf( buf, "%d %s matches were found for '%s':\n",
matches, ufn ? "UFN" : lfi->lfi_desc, msgp->msg_arg );
strcat( reply, buf );
append_entry_list( reply, msgp->msg_arg, ldp, ldmsgp );
ldap_msgfree( ldmsgp );
}
close_ldap( ldp );
ldap_getfilter_free( lfdp );
return( 0 );
}
static void
close_ldap( LDAP *ld )
{
#ifdef LDAP_CONNECTIONLESS
if ( do_cldap )
cldap_close( ld );
else
#endif /* LDAP_CONNECTIONLESS */
ldap_unbind( ld );
}
static void
append_entry_list( char *reply, char *query, LDAP *ldp, LDAPMessage *ldmsgp )
{
LDAPMessage *e;
char *dn, *rdn, *s, **title;
int free_rdn = 0;
#ifdef RCPT500_SORT_ATTR
ldap_sort_entries( ldp, &ldmsgp, RCPT500_SORT_ATTR, strcasecmp );
#endif
for ( e = ldap_first_entry( ldp, ldmsgp ); e != NULL;
e = ldap_next_entry( ldp, e )) {
dn = ldap_get_dn( ldp, e );
if (( s = strchr( dn, ',' )) != NULL ) {
*s = '\0';
}
if (( s = strchr( dn, '=' )) == NULL ) {
rdn = dn;
} else {
rdn = s + 1;
}
#ifdef UOFM
/*
* if this entry's rdn is an exact match for the thing looked up, we
* return the CN that has a digit after it, so that the user is
* returned something guaranteed to yield exactly one match if they
* pick it from the list and query it
*/
if ( strcasecmp( rdn, query ) == 0 ) {
char **cn;
int i;
if (( cn = ldap_get_values( ldp, e, "cn" )) != NULL ) {
for ( i = 0; cn[i] != NULL; i++ ) {
if ( isdigit((unsigned char) cn[i][strlen( cn[i] ) - 1])) {
rdn = strdup( cn[i] );
free_rdn = 1;
break;
}
}
ldap_value_free( cn );
}
}
#endif /* UOFM */
title = ldap_get_values( ldp, e, "title" );
sprintf( buf, " %-20s %s\n", rdn, title ? title[0] : "" );
strcat( reply, buf );
if ( title != NULL ) {
ldap_value_free( title );
}
free( dn );
if ( free_rdn ) {
free( rdn );
}
}
}
static int
append_text( void *reply, char *text, ber_len_t len )
{
strcat( (char *) reply, text );
return( len );
}
static int
do_read( LDAP *ldp, char *dn, char *reply, struct ldap_disptmpl *tmpll )
{
int rc;
static char *maildefvals[] = { "None registered in this service", NULL };
static char *defattrs[] = { "mail", NULL };
static char **defvals[] = { maildefvals, NULL };
rc = ldap_entry2text_search( ldp, dn, searchbase, NULL, tmpll,
defattrs, defvals, append_text, (void *)reply, "\n",
rdncount, LDAP_DISP_OPT_DOSEARCHACTIONS );
return( rc );
}
static void
report_ldap_err( LDAP *ldp, char *reply )
{
int ld_errno = 0;
ldap_get_option(ldp, LDAP_OPT_ERROR_NUMBER, &ld_errno);
strcat( reply, errpreface );
strcat( reply, ldap_err2string( ld_errno ));
strcat( reply, "\n" );
}
static void
remove_trailing_space( char *s )
{
char *p = s + strlen( s ) - 1;
while ( isspace( (unsigned char) *p ) && p > s ) {
--p;
}
*(++p) = '\0';
}
/*
* rcpt500.h: includes for rcpt500 (X.500 email query responder)
*
* 16 June 1992 by Mark C Smith
* Copyright (c) 1992 The Regents of The University of Michigan
* All Rights Reserved
*/
#include <ldap_cdefs.h>
LDAP_BEGIN_DECL
struct msginfo {
char *msg_subject;
char *msg_replyto; /* actually could be from From: line */
char *msg_date;
char *msg_messageid;
int msg_command;
char *msg_arg;
};
struct command {
char *cmd_text; /* text for command, e.g. "HELP" */
/* pointer to handler function */
int (*cmd_handler) LDAP_P((struct msginfo *msgp, char *reply));
};
#define MAXSIZE 8096
/*
* functions
*/
int help_cmd LDAP_P((struct msginfo *msgp, char *reply));
int query_cmd LDAP_P((struct msginfo *msgp, char *reply));
/*
* externs
*/
/* cmds.c */
extern struct command cmds[];
/* main.c */
extern int dosyslog;
#ifdef LDAP_CONNECTIONLESS
extern int do_cldap;
#endif
extern int derefaliases;
extern int sizelimit;
extern int rdncount;
extern int ldapport;
extern char *ldaphost;
extern char *searchbase;
extern char *dapuser;
extern char *filterfile;
extern char *templatefile;
LDAP_END_DECL
How to use the University of Michigan X.500 Email Query Service
By sending electronic mail to the address:
x500-query@umich.edu
you can access the campus X.500 Directory. The Directory contains
information about all faculty, staff, and students of the University,
including phone numbers, mailing addresses, job titles, email
addresses, and more.
To query the service, send a piece of email to this address with a
command similar to:
find <name>
e.g. find robert jones
somewhere in the Subject: field or anywhere in the body (text) of the
message. You will receive a reply that contains information about the
target of your query (in the above example, you would get information
on all "robert jones" who are at the University). The <name> can be
a person's full name, last name, or uniqname. An search for exact
matches is first tried; if there are no exact matches, an approximate
search (using soundex rules) is done.
If your query matches exactly one person, you will receive detailed
information about that person. If more than one is matched, a list will
be returned that should hopefully provide you with enough information
to be able to determine which individual you really want. Another
query can then be made to obtain the detailed information.
This service understands a number of synonyms for the "find" command,
including: "whois", "search", "look up", "show." You can also send
a message with the word "help" in it to get this text returned to you.
Note that this service is entirely automated -- no humans will respond
to requests or other email sent here.
We would appreciate any feedback you can provide.
If you have problems, report them using our Issue Tracking System:
http://www.OpenLDAP.com/its/
or by sending e-mail to:
OpenLDAP-its@OpenLDAP.org
Additional mailing lists are available. Please see:
http://www.OpenLDAP.com/lists/
This is the end of the help text.
# Makefile for LDAP tools
# $OpenLDAP$
## This work is part of OpenLDAP Software <http://www.openldap.org/>.
##
## Makefile for LDAP tools
## Copyright 1998-2024 The OpenLDAP Foundation.
## All rights reserved.
##
SRCS = ldapsearch.c ldapmodify.c ldapdelete.c ldapmodrdn.c ldappasswd.c
OBJS = ldapsearch.o ldapmodify.o ldapdelete.o ldapmodrdn.o ldappasswd.o
## Redistribution and use in source and binary forms, with or without
## modification, are permitted only as authorized by the OpenLDAP
## Public License.
##
## A copy of this license is available in the file LICENSE in the
## top-level directory of the distribution or, alternatively, at
## <http://www.OpenLDAP.org/license.html>.
SRCS = ldapsearch.c ldapmodify.c ldapdelete.c ldapmodrdn.c \
ldappasswd.c ldapwhoami.c ldapvc.c ldapcompare.c \
ldapexop.c ldapurl.c common.c
OBJS = ldapsearch.o ldapmodify.o ldapdelete.o ldapmodrdn.o \
ldappasswd.o ldapwhoami.o ldapvc.o ldapcompare.o \
ldapexop.o ldapurl.o common.o
LDAP_INCDIR= ../../include
LDAP_LIBDIR= ../../libraries
XLIBS = -lldif -lldap -llber -llutil
XXLIBS = $(SECURITY_LIBS) $(LDIF_LIBS) $(LUTIL_LIBS)
MKVOPTS = -s
XLIBS = $(LDAP_L)
XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS) $(CLIENT_LIBS)
XSRCS = ldsversion.c ldmversion.c lddversion.c ldrversion.c ldpversion.c \
ldwversion.c ldvversion.c ldcversion.c ldeversion.c lduversion.c
XSRCS = ldsversion.c ldmversion.c lddversion.c ldrversion.c ldpversion.c
PROGRAMS = ldapsearch ldapmodify ldapdelete ldapmodrdn \
ldappasswd ldapwhoami ldapvc ldapcompare ldapexop ldapurl
PROGRAMS = ldapsearch ldapmodify ldapdelete ldapmodrdn ldapadd ldappasswd
ldapsearch: ldsversion.o
$(LTLINK) -o $@ ldapsearch.o ldsversion.o $(LIBS)
$(LTLINK) -o $@ ldapsearch.o common.o ldsversion.o $(LIBS)
ldapmodify: ldmversion.o
$(LTLINK) -o $@ ldapmodify.o ldmversion.o $(LIBS)
$(LTLINK) -o $@ ldapmodify.o common.o ldmversion.o $(LIBS)
ldapdelete: lddversion.o
$(LTLINK) -o $@ ldapdelete.o lddversion.o $(LIBS)
$(LTLINK) -o $@ ldapdelete.o common.o lddversion.o $(LIBS)
ldapmodrdn: ldrversion.o
$(LTLINK) -o $@ ldapmodrdn.o ldrversion.o $(LIBS)
$(LTLINK) -o $@ ldapmodrdn.o common.o ldrversion.o $(LIBS)
ldappasswd: ldpversion.o
$(LTLINK) -o $@ ldappasswd.o ldpversion.o $(LIBS)
$(LTLINK) -o $@ ldappasswd.o common.o ldpversion.o $(LIBS)
ldapwhoami: ldwversion.o
$(LTLINK) -o $@ ldapwhoami.o common.o ldwversion.o $(LIBS)
ldapvc: ldvversion.o
$(LTLINK) -o $@ ldapvc.o common.o ldvversion.o $(LIBS)
ldapcompare: ldcversion.o
$(LTLINK) -o $@ ldapcompare.o common.o ldcversion.o $(LIBS)
ldapexop: ldeversion.o
$(LTLINK) -o $@ ldapexop.o common.o ldeversion.o $(LIBS)
ldapadd: ldapmodify
@-$(RM) $@$(EXEEXT)
$(LN_H) ldapmodify$(EXEEXT) ldapadd$(EXEEXT)
ldapurl: lduversion.o
$(LTLINK) -o $@ ldapurl.o lduversion.o $(LIBS)
ldsversion.c: ldapsearch.o $(LDAP_LIBDEPEND)
ldsversion.c: Makefile
@-$(RM) $@
$(MKVERSION) ldapsearch > $@
$(MKVERSION) $(MKVOPTS) ldapsearch > $@
ldmversion.c: ldapmodify.o $(LDAP_LIBDEPEND)
ldsversion.o: ldapsearch.o common.o $(XLIBS)
ldmversion.c: Makefile
@-$(RM) $@
$(MKVERSION) ldapmodify > $@
$(MKVERSION) $(MKVOPTS) ldapmodify > $@
ldmversion.o: ldapmodify.o common.o $(XLIBS)
lddversion.c: ldapdelete.o $(LDAP_LIBDEPEND)
lddversion.c: Makefile
@-$(RM) $@
$(MKVERSION) ldapdelete > $@
$(MKVERSION) $(MKVOPTS) ldapdelete > $@
ldpversion.c: ldappasswd.o $(LDAP_LIBDEPEND)
lddversion.o: ldapdelete.o common.o $(XLIBS)
ldpversion.c: Makefile
@-$(RM) $@
$(MKVERSION) ldappasswd > $@
$(MKVERSION) $(MKVOPTS) ldappasswd > $@
ldpversion.o: ldappasswd.o common.o $(XLIBS)
ldrversion.c: Makefile
@-$(RM) $@
$(MKVERSION) $(MKVOPTS) ldapmodrdn > $@
ldrversion.o: ldapmodrdn.o common.o $(XLIBS)
ldrversion.c: ldapmodrdn.o $(LDAP_LIBDEPEND)
ldwversion.c: Makefile
@-$(RM) $@
$(MKVERSION) ldapmodrdn > $@
$(MKVERSION) $(MKVOPTS) ldapwhoami > $@
ldwversion.o: ldapwhoami.o common.o $(XLIBS)
ldvversion.c: Makefile
@-$(RM) $@
$(MKVERSION) $(MKVOPTS) ldapvc > $@
ldvversion.o: ldapvc.o common.o $(XLIBS)
ldcversion.c: Makefile
@-$(RM) $@
$(MKVERSION) $(MKVOPTS) ldapcompare > $@
ldcversion.o: ldapcompare.o common.o $(XLIBS)
ldeversion.c: Makefile
@-$(RM) $@
$(MKVERSION) $(MKVOPTS) ldapexop > $@
ldeversion.o: ldapexop.o common.o $(XLIBS)
lduversion.c: Makefile
@-$(RM) $@
$(MKVERSION) $(MKVOPTS) ldapurl > $@
lduversion.o: ldapurl.o $(XLIBS)
install-local: FORCE
-$(MKDIR) $(bindir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 ldapsearch $(bindir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 ldapmodify $(bindir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 ldapdelete $(bindir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 ldapmodrdn $(bindir)
$(LTINSTALL) $(INSTALLFLAGS) -m 755 ldappasswd $(bindir)
$(RM) $(bindir)/ldapadd$(EXEEXT)
$(LN) $(bindir)/ldapmodify$(EXEEXT) $(bindir)/ldapadd$(EXEEXT)
-$(MKDIR) $(DESTDIR)$(bindir)
@( \
for prg in $(PROGRAMS); do \
$(LTINSTALL) $(INSTALLFLAGS) $(STRIP_OPTS) -m 755 $$prg$(EXEEXT) \
$(DESTDIR)$(bindir); \
done \
)
$(RM) $(DESTDIR)$(bindir)/ldapadd$(EXEEXT)
$(LN_S) $(DESTDIR)$(bindir)/ldapmodify$(EXEEXT) $(DESTDIR)$(bindir)/ldapadd$(EXEEXT)
/* common.c - common routines for the ldap client tools */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 1998-2024 The OpenLDAP Foundation.
* Portions Copyright 2003 Kurt D. Zeilenga.
* Portions Copyright 2003 IBM Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* ACKNOWLEDGEMENTS:
* This file was initially created by Hallvard B. Furuseth based (in
* part) upon argument parsing code for individual tools located in
* this directory. Additional contributors include:
* Kurt D. Zeilenga (additional common argument and control support)
*/
#include "portable.h"
#include <stdio.h>
#include <ac/stdlib.h>
#include <ac/signal.h>
#include <ac/string.h>
#include <ac/ctype.h>
#include <ac/unistd.h>
#include <ac/errno.h>
#include <ac/time.h>
#include <ac/socket.h>
#ifdef HAVE_CYRUS_SASL
#ifdef HAVE_SASL_SASL_H
#include <sasl/sasl.h>
#else
#include <sasl.h>
#endif
#endif
#include <ldap.h>
#include "ldif.h"
#include "lutil.h"
#include "lutil_ldap.h"
#include "ldap_defaults.h"
#include "ldap_pvt.h"
#include "lber_pvt.h"
#include "common.h"
/* input-related vars */
/* misc. parameters */
tool_type_t tool_type;
int contoper = 0;
int debug = 0;
char *infile = NULL;
int dont = 0;
int nocanon = 0;
int referrals = 0;
int verbose = 0;
int ldif = 0;
ber_len_t ldif_wrap = 0;
char *prog = NULL;
/* connection */
char *ldapuri = NULL;
int use_tls = 0;
int protocol = -1;
int version = 0;
/* authc/authz */
int authmethod = -1;
char *binddn = NULL;
int want_bindpw = 0;
struct berval passwd = { 0, NULL };
char *pw_file = NULL;
#ifdef HAVE_CYRUS_SASL
unsigned sasl_flags = LDAP_SASL_AUTOMATIC;
char *sasl_realm = NULL;
char *sasl_authc_id = NULL;
char *sasl_authz_id = NULL;
char *sasl_mech = NULL;
char *sasl_secprops = NULL;
#endif
/* controls */
int assertctl;
char *assertion = NULL;
struct berval assertionvalue = BER_BVNULL;
char *authzid = NULL;
int authzcrit = 1;
/* support deprecated early version of proxyAuthz */
#define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ "2.16.840.1.113730.3.4.12"
#ifdef LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ
char *proxydn = NULL;
#endif /* LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ */
int manageDIT = 0;
int manageDSAit = 0;
int noop = 0;
int ppolicy = 0;
int preread = 0;
static char *preread_attrs = NULL;
int postread = 0;
static char *postread_attrs = NULL;
ber_int_t pr_morePagedResults = 1;
struct berval pr_cookie = { 0, NULL };
#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
int chaining = 0;
static int chainingResolve = -1;
static int chainingContinuation = -1;
#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
static int sessionTracking = 0;
static char *sessionTrackingName;
struct berval stValue;
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
ber_int_t vlvPos;
ber_int_t vlvCount;
struct berval *vlvContext;
static int bauthzid;
LDAPControl *unknown_ctrls = NULL;
int unknown_ctrls_num = 0;
/* options */
struct timeval nettimeout = { -1 , 0 };
typedef int (*print_ctrl_fn)( LDAP *ld, LDAPControl *ctrl );
static int print_preread( LDAP *ld, LDAPControl *ctrl );
static int print_postread( LDAP *ld, LDAPControl *ctrl );
static int print_paged_results( LDAP *ld, LDAPControl *ctrl );
static int print_psearch( LDAP *ld, LDAPControl *ctrl );
#ifdef LDAP_CONTROL_AUTHZID_RESPONSE
static int print_authzid( LDAP *ld, LDAPControl *ctrl );
#endif
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
static int print_ppolicy( LDAP *ld, LDAPControl *ctrl );
#endif
static int print_sss( LDAP *ld, LDAPControl *ctrl );
static int print_vlv( LDAP *ld, LDAPControl *ctrl );
#ifdef LDAP_CONTROL_X_DEREF
static int print_deref( LDAP *ld, LDAPControl *ctrl );
#endif
#ifdef LDAP_CONTROL_X_WHATFAILED
static int print_whatfailed( LDAP *ld, LDAPControl *ctrl );
#endif
static int print_syncstate( LDAP *ld, LDAPControl *ctrl );
static int print_syncdone( LDAP *ld, LDAPControl *ctrl );
#ifdef LDAP_CONTROL_X_DIRSYNC
static int print_dirsync( LDAP *ld, LDAPControl *ctrl );
#endif
#ifdef LDAP_CONTROL_X_ACCOUNT_USABILITY
static int print_account_usability( LDAP *ld, LDAPControl *ctrl );
#endif
#ifdef LDAP_CONTROL_X_PASSWORD_EXPIRED
static int print_netscape_pwexpired( LDAP *ld, LDAPControl *ctrl );
static int print_netscape_pwexpiring( LDAP *ld, LDAPControl *ctrl );
#endif
static struct tool_ctrls_t {
const char *oid;
unsigned mask;
print_ctrl_fn func;
} tool_ctrl_response[] = {
{ LDAP_CONTROL_PRE_READ, TOOL_ALL, print_preread },
{ LDAP_CONTROL_POST_READ, TOOL_ALL, print_postread },
{ LDAP_CONTROL_PAGEDRESULTS, TOOL_SEARCH, print_paged_results },
{ LDAP_CONTROL_PERSIST_ENTRY_CHANGE_NOTICE, TOOL_SEARCH, print_psearch },
#ifdef LDAP_CONTROL_AUTHZID_RESPONSE
/* this is generally deprecated in favor of LDAP WhoAmI? operation, hence only supported as a VC inner control */
{ LDAP_CONTROL_AUTHZID_RESPONSE, TOOL_VC, print_authzid },
#endif
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
{ LDAP_CONTROL_PASSWORDPOLICYRESPONSE, TOOL_ALL, print_ppolicy },
#endif
{ LDAP_CONTROL_SORTRESPONSE, TOOL_SEARCH, print_sss },
{ LDAP_CONTROL_VLVRESPONSE, TOOL_SEARCH, print_vlv },
#ifdef LDAP_CONTROL_X_DEREF
{ LDAP_CONTROL_X_DEREF, TOOL_SEARCH, print_deref },
#endif
#ifdef LDAP_CONTROL_X_WHATFAILED
{ LDAP_CONTROL_X_WHATFAILED, TOOL_ALL, print_whatfailed },
#endif
{ LDAP_CONTROL_SYNC_STATE, TOOL_SEARCH, print_syncstate },
{ LDAP_CONTROL_SYNC_DONE, TOOL_SEARCH, print_syncdone },
#ifdef LDAP_CONTROL_X_DIRSYNC
{ LDAP_CONTROL_X_DIRSYNC, TOOL_SEARCH, print_dirsync },
#endif
#ifdef LDAP_CONTROL_X_ACCOUNT_USABILITY
{ LDAP_CONTROL_X_ACCOUNT_USABILITY, TOOL_SEARCH, print_account_usability },
#endif
#ifdef LDAP_CONTROL_X_PASSWORD_EXPIRED
{ LDAP_CONTROL_X_PASSWORD_EXPIRED, TOOL_ALL, print_netscape_pwexpired },
{ LDAP_CONTROL_X_PASSWORD_EXPIRING, TOOL_ALL, print_netscape_pwexpiring },
#endif
{ NULL, 0, NULL }
};
/* "features" */
enum { Intr_None = 0, Intr_Abandon, Intr_Cancel, Intr_Ignore };
static volatile sig_atomic_t gotintr, abcan;
int backlog;
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
static int
st_value( LDAP *ld, struct berval *value )
{
char *ip = NULL, *name = NULL;
struct berval id = { 0 };
char namebuf[ MAXHOSTNAMELEN ];
#ifdef LDAP_PF_INET6
char ip6buf[ INET6_ADDRSTRLEN ];
#endif
if ( gethostname( namebuf, sizeof( namebuf ) ) == 0 ) {
name = namebuf;
}
{
int sd;
if ( ldap_get_option( ld, LDAP_OPT_DESC, &sd ) == LDAP_SUCCESS ) {
struct sockaddr_storage sa;
socklen_t sl = sizeof(sa);
if ( getsockname( sd, (struct sockaddr *)&sa, &sl ) == 0 ) {
if ( sa.ss_family == AF_INET ) {
struct sockaddr_in *sai = (struct sockaddr_in *)&sa;
ip = inet_ntoa( sai->sin_addr );
}
#ifdef LDAP_PF_INET6
else if ( sa.ss_family == AF_INET6 ) {
struct sockaddr_in6 *sai = (struct sockaddr_in6 *)&sa;
ip = inet_ntop( AF_INET6, &sai->sin6_addr, ip6buf, sizeof( ip6buf ));
}
#endif
}
}
}
if ( sessionTrackingName != NULL ) {
ber_str2bv( sessionTrackingName , 0, 0, &id );
} else
#ifdef HAVE_CYRUS_SASL
if ( sasl_authz_id != NULL ) {
ber_str2bv( sasl_authz_id, 0, 0, &id );
} else if ( sasl_authc_id != NULL ) {
ber_str2bv( sasl_authc_id, 0, 0, &id );
} else
#endif /* HAVE_CYRUS_SASL */
if ( binddn != NULL ) {
ber_str2bv( binddn, 0, 0, &id );
}
if ( ldap_create_session_tracking_value( ld,
ip, name, LDAP_CONTROL_X_SESSION_TRACKING_USERNAME,
&id, &stValue ) )
{
fprintf( stderr, _("Session tracking control encoding error!\n") );
return -1;
}
return 0;
}
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
RETSIGTYPE
do_sig( int sig )
{
gotintr = abcan;
}
void
tool_init( tool_type_t type )
{
tool_type = type;
ldap_pvt_setlocale(LC_MESSAGES, "");
ldap_pvt_bindtextdomain(OPENLDAP_PACKAGE, LDAP_LOCALEDIR);
ldap_pvt_textdomain(OPENLDAP_PACKAGE);
}
void
tool_destroy( void )
{
static int destroyed;
if ( destroyed++ )
return;
#ifdef HAVE_CYRUS_SASL
sasl_done();
#endif
#ifdef HAVE_TLS
ldap_pvt_tls_destroy();
#endif
if ( ldapuri != NULL ) {
ber_memfree( ldapuri );
ldapuri = NULL;
}
if ( pr_cookie.bv_val != NULL ) {
ber_memfree( pr_cookie.bv_val );
BER_BVZERO( &pr_cookie );
}
if ( passwd.bv_val != NULL ) {
ber_memfree( passwd.bv_val );
BER_BVZERO( &passwd );
}
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
if ( !BER_BVISNULL( &stValue ) ) {
ber_memfree( stValue.bv_val );
BER_BVZERO( &stValue );
}
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
}
void
tool_common_usage( void )
{
static const char *const descriptions[] = {
N_(" -d level set LDAP debugging level to `level'\n"),
N_(" -D binddn bind DN\n"),
N_(" -e [!]<ext>[=<extparam>] general extensions (! indicates criticality)\n")
N_(" [!]assert=<filter> (RFC 4528; a RFC 4515 Filter string)\n")
N_(" [!]authzid=<authzid> (RFC 4370; \"dn:<dn>\" or \"u:<user>\")\n")
N_(" [!]bauthzid (RFC 3829)\n")
#ifdef LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ
#if 0
/* non-advertized support for proxyDN */
N_(" [!]proxydn=<dn> (a RFC 4514 DN string)\n")
#endif
#endif
#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
N_(" [!]chaining[=<resolveBehavior>[/<continuationBehavior>]]\n")
N_(" one of \"chainingPreferred\", \"chainingRequired\",\n")
N_(" \"referralsPreferred\", \"referralsRequired\"\n")
#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
N_(" [!]manageDSAit (RFC 3296)\n")
N_(" [!]noop\n")
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
N_(" ppolicy\n")
#endif
N_(" [!]postread[=<attrs>] (RFC 4527; comma-separated attr list)\n")
N_(" [!]preread[=<attrs>] (RFC 4527; comma-separated attr list)\n")
N_(" [!]relax\n")
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
N_(" [!]sessiontracking[=<username>]\n")
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
N_(" abandon, cancel, ignore (SIGINT sends abandon/cancel,\n"
" or ignores response; if critical, doesn't wait for SIGINT.\n"
" not really controls)\n")
N_(" -H URI LDAP Uniform Resource Identifier(s)\n"),
N_(" -I use SASL Interactive mode\n"),
N_(" -n show what would be done but don't actually do it\n"),
N_(" -N do not use reverse DNS to canonicalize SASL host name\n"),
N_(" -O props SASL security properties\n"),
N_(" -o <opt>[=<optparam>] any libldap ldap.conf options, plus\n"),
N_(" ldif_wrap=<width> (in columns, or \"no\" for no wrapping)\n"),
N_(" nettimeout=<timeout> (in seconds, or \"none\" or \"max\")\n"),
N_(" -Q use SASL Quiet mode\n"),
N_(" -R realm SASL realm\n"),
N_(" -U authcid SASL authentication identity\n"),
N_(" -v run in verbose mode (diagnostics to standard output)\n"),
N_(" -V print version info (-VV only)\n"),
N_(" -w passwd bind password (for simple authentication)\n"),
N_(" -W prompt for bind password\n"),
N_(" -x Simple authentication\n"),
N_(" -X authzid SASL authorization identity (\"dn:<dn>\" or \"u:<user>\")\n"),
N_(" -y file Read password from file\n"),
N_(" -Y mech SASL mechanism\n"),
N_(" -Z Start TLS request (-ZZ to require successful response)\n"),
NULL
};
const char *const *cpp;
fputs( _("Common options:\n"), stderr );
for( cpp = descriptions; *cpp != NULL; cpp++ ) {
if( strchr( options, (*cpp)[3] ) || (*cpp)[3] == ' ' ) {
fputs( _(*cpp), stderr );
}
}
tool_destroy();
}
void tool_perror(
const char *func,
int err,
const char *extra,
const char *matched,
const char *info,
char **refs )
{
fprintf( stderr, "%s: %s (%d)%s\n",
func, ldap_err2string( err ), err, extra ? extra : "" );
if ( matched && *matched ) {
fprintf( stderr, _("\tmatched DN: %s\n"), matched );
}
if ( info && *info ) {
fprintf( stderr, _("\tadditional info: %s\n"), info );
}
if ( refs && *refs ) {
int i;
fprintf( stderr, _("\treferrals:\n") );
for( i=0; refs[i]; i++ ) {
fprintf( stderr, "\t\t%s\n", refs[i] );
}
}
}
void
tool_args( int argc, char **argv )
{
int i;
while (( i = getopt( argc, argv, options )) != EOF ) {
int crit, ival;
char *control, *cvalue, *next;
switch( i ) {
case 'c': /* continuous operation mode */
contoper++;
break;
case 'C': /* referrals: obsolete */
referrals++;
break;
case 'd':
ival = strtol( optarg, &next, 10 );
if (next == NULL || next[0] != '\0') {
fprintf( stderr, "%s: unable to parse debug value \"%s\"\n", prog, optarg);
exit(EXIT_FAILURE);
}
debug |= ival;
break;
case 'D': /* bind DN */
if( binddn != NULL ) {
fprintf( stderr, "%s: -D previously specified\n", prog );
exit( EXIT_FAILURE );
}
binddn = optarg;
break;
case 'e': /* general extensions (controls and such) */
/* should be extended to support comma separated list of
* [!]key[=value] parameters, e.g. -e !foo,bar=567
*/
crit = 0;
cvalue = NULL;
while ( optarg[0] == '!' ) {
crit++;
optarg++;
}
control = optarg;
if ( (cvalue = strchr( control, '=' )) != NULL ) {
*cvalue++ = '\0';
}
if ( strcasecmp( control, "assert" ) == 0 ) {
if( assertctl ) {
fprintf( stderr, "assert control previously specified\n");
exit( EXIT_FAILURE );
}
if( cvalue == NULL ) {
fprintf( stderr, "assert: control value expected\n" );
usage();
}
assertctl = 1 + crit;
assert( assertion == NULL );
assertion = cvalue;
} else if ( strcasecmp( control, "authzid" ) == 0 ) {
if( authzid != NULL ) {
fprintf( stderr, "authzid control previously specified\n");
exit( EXIT_FAILURE );
}
#ifdef LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ
if( proxydn != NULL ) {
fprintf( stderr, "authzid control incompatible with proxydn\n");
exit( EXIT_FAILURE );
}
#endif /* LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ */
if( cvalue == NULL ) {
fprintf( stderr, "authzid: control value expected\n" );
usage();
}
if( !crit ) {
fprintf( stderr, "authzid: must be marked critical\n" );
usage();
} else if ( crit > 1 ) {
/* purposely flag proxied authorization
* as non-critical, to test DSA */
authzcrit = 0;
}
assert( authzid == NULL );
authzid = cvalue;
#ifdef LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ
} else if ( strcasecmp( control, "proxydn" ) == 0 ) {
if( proxydn != NULL ) {
fprintf( stderr, "proxydn control previously specified\n");
exit( EXIT_FAILURE );
}
if( authzid != NULL ) {
fprintf( stderr, "proxydn control incompatible with authzid\n");
exit( EXIT_FAILURE );
}
if( cvalue == NULL ) {
fprintf( stderr, "proxydn: control value expected\n" );
usage();
}
if( !crit ) {
fprintf( stderr, "proxydn: must be marked critical\n" );
usage();
} else if ( crit > 1 ) {
/* purposely flag proxied authorization
* as non-critical, to test DSA */
authzcrit = 0;
}
assert( proxydn == NULL );
proxydn = cvalue;
#endif /* LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ */
} else if ( strcasecmp( control, "bauthzid" ) == 0 ) {
if( bauthzid ) {
fprintf( stderr, "bauthzid control previously specified\n");
exit( EXIT_FAILURE );
}
if( cvalue != NULL ) {
fprintf( stderr, "bauthzid: no control value expected\n" );
usage();
}
bauthzid = 1 + crit;
} else if ( ( strcasecmp( control, "relax" ) == 0 ) ||
( strcasecmp( control, "manageDIT" ) == 0 ) )
{
if( manageDIT ) {
fprintf( stderr,
"relax control previously specified\n");
exit( EXIT_FAILURE );
}
if( cvalue != NULL ) {
fprintf( stderr,
"relax: no control value expected\n" );
usage();
}
manageDIT = 1 + crit;
} else if ( strcasecmp( control, "manageDSAit" ) == 0 ) {
if( manageDSAit ) {
fprintf( stderr,
"manageDSAit control previously specified\n");
exit( EXIT_FAILURE );
}
if( cvalue != NULL ) {
fprintf( stderr,
"manageDSAit: no control value expected\n" );
usage();
}
manageDSAit = 1 + crit;
} else if ( strcasecmp( control, "noop" ) == 0 ) {
if( noop ) {
fprintf( stderr, "noop control previously specified\n");
exit( EXIT_FAILURE );
}
if( cvalue != NULL ) {
fprintf( stderr, "noop: no control value expected\n" );
usage();
}
noop = 1 + crit;
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
} else if ( strcasecmp( control, "ppolicy" ) == 0 ) {
if( ppolicy ) {
fprintf( stderr, "ppolicy control previously specified\n");
exit( EXIT_FAILURE );
}
if( cvalue != NULL ) {
fprintf( stderr, "ppolicy: no control value expected\n" );
usage();
}
if( crit ) {
fprintf( stderr, "ppolicy: critical flag not allowed\n" );
usage();
}
ppolicy = 1;
#endif
} else if ( strcasecmp( control, "preread" ) == 0 ) {
if( preread ) {
fprintf( stderr, "preread control previously specified\n");
exit( EXIT_FAILURE );
}
preread = 1 + crit;
preread_attrs = cvalue;
} else if ( strcasecmp( control, "postread" ) == 0 ) {
if( postread ) {
fprintf( stderr, "postread control previously specified\n");
exit( EXIT_FAILURE );
}
postread = 1 + crit;
postread_attrs = cvalue;
#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
} else if ( strcasecmp( control, "chaining" ) == 0 ) {
if ( chaining ) {
fprintf( stderr, "chaining control previously specified\n");
exit( EXIT_FAILURE );
}
chaining = 1 + crit;
if ( cvalue != NULL ) {
char *continuation;
continuation = strchr( cvalue, '/' );
if ( continuation ) {
/* FIXME: this makes sense only in searches */
*continuation++ = '\0';
if ( strcasecmp( continuation, "chainingPreferred" ) == 0 ) {
chainingContinuation = LDAP_CHAINING_PREFERRED;
} else if ( strcasecmp( continuation, "chainingRequired" ) == 0 ) {
chainingContinuation = LDAP_CHAINING_REQUIRED;
} else if ( strcasecmp( continuation, "referralsPreferred" ) == 0 ) {
chainingContinuation = LDAP_REFERRALS_PREFERRED;
} else if ( strcasecmp( continuation, "referralsRequired" ) == 0 ) {
chainingContinuation = LDAP_REFERRALS_REQUIRED;
} else {
fprintf( stderr,
"chaining behavior control "
"continuation value \"%s\" invalid\n",
continuation );
exit( EXIT_FAILURE );
}
}
if ( strcasecmp( cvalue, "chainingPreferred" ) == 0 ) {
chainingResolve = LDAP_CHAINING_PREFERRED;
} else if ( strcasecmp( cvalue, "chainingRequired" ) == 0 ) {
chainingResolve = LDAP_CHAINING_REQUIRED;
} else if ( strcasecmp( cvalue, "referralsPreferred" ) == 0 ) {
chainingResolve = LDAP_REFERRALS_PREFERRED;
} else if ( strcasecmp( cvalue, "referralsRequired" ) == 0 ) {
chainingResolve = LDAP_REFERRALS_REQUIRED;
} else {
fprintf( stderr,
"chaining behavior control "
"resolve value \"%s\" invalid\n",
cvalue);
exit( EXIT_FAILURE );
}
}
#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
} else if ( strcasecmp( control, "sessiontracking" ) == 0 ) {
if ( sessionTracking ) {
fprintf( stderr, "%s: session tracking can be only specified once\n", prog );
exit( EXIT_FAILURE );
}
sessionTracking = 1;
if ( crit ) {
fprintf( stderr, "sessiontracking: critical flag not allowed\n" );
usage();
}
if ( cvalue ) {
sessionTrackingName = cvalue;
}
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
/* this shouldn't go here, really; but it's a feature... */
} else if ( strcasecmp( control, "abandon" ) == 0 ) {
abcan = Intr_Abandon;
if ( crit ) {
gotintr = abcan;
}
} else if ( strcasecmp( control, "cancel" ) == 0 ) {
abcan = Intr_Cancel;
if ( crit ) {
gotintr = abcan;
}
} else if ( strcasecmp( control, "ignore" ) == 0 ) {
abcan = Intr_Ignore;
if ( crit ) {
gotintr = abcan;
}
} else if ( strcasecmp( control, "backlog" ) == 0 ) {
/* special search: accumulate lots of responses
* but don't read any, force slapd writer to wait.
* Then abandon the search and issue a new one.
*/
backlog = 1;
} else if ( tool_is_oid( control ) ) {
LDAPControl *tmpctrls, ctrl;
if ( unknown_ctrls != NULL ) {
int i;
for ( i = 0; unknown_ctrls[ i ].ldctl_oid != NULL; i++ ) {
if ( strcmp( control, unknown_ctrls[ i ].ldctl_oid ) == 0 ) {
fprintf( stderr, "%s control previously specified\n", control );
exit( EXIT_FAILURE );
}
}
}
tmpctrls = (LDAPControl *)ber_memrealloc( unknown_ctrls,
(unknown_ctrls_num + 1)*sizeof( LDAPControl ) );
if ( tmpctrls == NULL ) {
fprintf( stderr, "%s: no memory?\n", prog );
exit( EXIT_FAILURE );
}
unknown_ctrls = tmpctrls;
ctrl.ldctl_oid = control;
ctrl.ldctl_value.bv_val = NULL;
ctrl.ldctl_value.bv_len = 0;
ctrl.ldctl_iscritical = crit;
if ( cvalue != NULL ) {
struct berval bv;
size_t len = strlen( cvalue );
int retcode;
bv.bv_len = LUTIL_BASE64_DECODE_LEN( len );
bv.bv_val = ber_memalloc( bv.bv_len + 1 );
retcode = lutil_b64_pton( cvalue,
(unsigned char *)bv.bv_val,
bv.bv_len );
if ( retcode == -1 || (unsigned) retcode > bv.bv_len ) {
fprintf( stderr, "Unable to parse value of general control %s\n",
control );
usage();
}
bv.bv_len = retcode;
ctrl.ldctl_value = bv;
}
/* don't free it */
control = NULL;
unknown_ctrls[ unknown_ctrls_num ] = ctrl;
unknown_ctrls_num++;
} else {
fprintf( stderr, "Invalid general control name: %s\n",
control );
usage();
}
break;
case 'f': /* read from file */
if( infile != NULL ) {
fprintf( stderr, "%s: -f previously specified\n", prog );
exit( EXIT_FAILURE );
}
infile = optarg;
break;
case 'H': /* ldap URI */
if( ldapuri != NULL ) {
fprintf( stderr, "%s: -H previously specified\n", prog );
exit( EXIT_FAILURE );
}
ldapuri = ber_strdup( optarg );
if ( ldapuri == NULL ) {
exit( EXIT_FAILURE );
}
break;
case 'I':
#ifdef HAVE_CYRUS_SASL
if( authmethod != -1 && authmethod != LDAP_AUTH_SASL ) {
fprintf( stderr, "%s: incompatible previous "
"authentication choice\n",
prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SASL;
sasl_flags = LDAP_SASL_INTERACTIVE;
break;
#else
fprintf( stderr, "%s: was not compiled with SASL support\n",
prog );
exit( EXIT_FAILURE );
#endif
case 'M':
/* enable Manage DSA IT */
manageDSAit++;
break;
case 'n': /* print operations, don't actually do them */
dont++;
break;
case 'N':
nocanon++;
break;
case 'o':
control = optarg;
if ( (cvalue = strchr( control, '=' )) != NULL ) {
*cvalue++ = '\0';
}
for ( next=control; *next; next++ ) {
if ( *next == '-' ) {
*next = '_';
}
}
if ( strcasecmp( control, "nettimeout" ) == 0 ) {
if( nettimeout.tv_sec != -1 ) {
fprintf( stderr, "nettimeout option previously specified\n");
exit( EXIT_FAILURE );
}
if( cvalue == NULL || cvalue[0] == '\0' ) {
fprintf( stderr, "nettimeout: option value expected\n" );
usage();
}
if ( strcasecmp( cvalue, "none" ) == 0 ) {
nettimeout.tv_sec = 0;
} else if ( strcasecmp( cvalue, "max" ) == 0 ) {
nettimeout.tv_sec = LDAP_MAXINT;
} else {
ival = strtol( cvalue, &next, 10 );
if ( next == NULL || next[0] != '\0' ) {
fprintf( stderr,
_("Unable to parse network timeout \"%s\"\n"), cvalue );
exit( EXIT_FAILURE );
}
nettimeout.tv_sec = ival;
}
if( nettimeout.tv_sec < 0 || nettimeout.tv_sec > LDAP_MAXINT ) {
fprintf( stderr, _("%s: invalid network timeout (%ld) specified\n"),
prog, (long)nettimeout.tv_sec );
exit( EXIT_FAILURE );
}
} else if ( strcasecmp( control, "ldif_wrap" ) == 0 ) {
if ( cvalue == 0 ) {
ldif_wrap = LDIF_LINE_WIDTH;
} else if ( strcasecmp( cvalue, "no" ) == 0 ) {
ldif_wrap = LDIF_LINE_WIDTH_MAX;
} else {
unsigned int u;
if ( lutil_atou( &u, cvalue ) ) {
fprintf( stderr,
_("Unable to parse ldif_wrap=\"%s\"\n"), cvalue );
exit( EXIT_FAILURE );
}
ldif_wrap = (ber_len_t)u;
}
} else if ( ldap_pvt_conf_option( control, cvalue, 1 ) ) {
fprintf( stderr, "Invalid general option name: %s\n",
control );
usage();
}
break;
case 'O':
#ifdef HAVE_CYRUS_SASL
if( sasl_secprops != NULL ) {
fprintf( stderr, "%s: -O previously specified\n", prog );
exit( EXIT_FAILURE );
}
if( authmethod != -1 && authmethod != LDAP_AUTH_SASL ) {
fprintf( stderr, "%s: incompatible previous "
"authentication choice\n", prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SASL;
sasl_secprops = optarg;
#else
fprintf( stderr, "%s: not compiled with SASL support\n", prog );
exit( EXIT_FAILURE );
#endif
break;
case 'P':
ival = strtol( optarg, &next, 10 );
if ( next == NULL || next[0] != '\0' ) {
fprintf( stderr, "%s: unable to parse protocol version \"%s\"\n", prog, optarg );
exit( EXIT_FAILURE );
}
switch( ival ) {
case 2:
if( protocol == LDAP_VERSION3 ) {
fprintf( stderr, "%s: -P 2 incompatible with version %d\n",
prog, protocol );
exit( EXIT_FAILURE );
}
protocol = LDAP_VERSION2;
break;
case 3:
if( protocol == LDAP_VERSION2 ) {
fprintf( stderr, "%s: -P 2 incompatible with version %d\n",
prog, protocol );
exit( EXIT_FAILURE );
}
protocol = LDAP_VERSION3;
break;
default:
fprintf( stderr, "%s: protocol version should be 2 or 3\n",
prog );
usage();
}
break;
case 'Q':
#ifdef HAVE_CYRUS_SASL
if( authmethod != -1 && authmethod != LDAP_AUTH_SASL ) {
fprintf( stderr, "%s: incompatible previous "
"authentication choice\n",
prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SASL;
sasl_flags = LDAP_SASL_QUIET;
break;
#else
fprintf( stderr, "%s: not compiled with SASL support\n",
prog );
exit( EXIT_FAILURE );
#endif
case 'R':
#ifdef HAVE_CYRUS_SASL
if( sasl_realm != NULL ) {
fprintf( stderr, "%s: -R previously specified\n", prog );
exit( EXIT_FAILURE );
}
if( authmethod != -1 && authmethod != LDAP_AUTH_SASL ) {
fprintf( stderr, "%s: incompatible previous "
"authentication choice\n",
prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SASL;
sasl_realm = optarg;
#else
fprintf( stderr, "%s: not compiled with SASL support\n",
prog );
exit( EXIT_FAILURE );
#endif
break;
case 'U':
#ifdef HAVE_CYRUS_SASL
if( sasl_authc_id != NULL ) {
fprintf( stderr, "%s: -U previously specified\n", prog );
exit( EXIT_FAILURE );
}
if( authmethod != -1 && authmethod != LDAP_AUTH_SASL ) {
fprintf( stderr, "%s: incompatible previous "
"authentication choice\n",
prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SASL;
sasl_authc_id = optarg;
#else
fprintf( stderr, "%s: not compiled with SASL support\n",
prog );
exit( EXIT_FAILURE );
#endif
break;
case 'v': /* verbose mode */
verbose++;
break;
case 'V': /* version */
version++;
break;
case 'w': /* password */
passwd.bv_val = ber_strdup( optarg );
if ( passwd.bv_val == NULL ) {
exit( EXIT_FAILURE );
}
{
char* p;
for( p = optarg; *p != '\0'; p++ ) {
*p = '\0';
}
}
passwd.bv_len = strlen( passwd.bv_val );
break;
case 'W':
want_bindpw++;
break;
case 'y':
pw_file = optarg;
break;
case 'Y':
#ifdef HAVE_CYRUS_SASL
if( sasl_mech != NULL ) {
fprintf( stderr, "%s: -Y previously specified\n", prog );
exit( EXIT_FAILURE );
}
if( authmethod != -1 && authmethod != LDAP_AUTH_SASL ) {
fprintf( stderr,
"%s: incompatible with authentication choice\n", prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SASL;
sasl_mech = optarg;
#else
fprintf( stderr, "%s: not compiled with SASL support\n", prog );
exit( EXIT_FAILURE );
#endif
break;
case 'x':
if( authmethod != -1 && authmethod != LDAP_AUTH_SIMPLE ) {
fprintf( stderr, "%s: incompatible with previous "
"authentication choice\n", prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SIMPLE;
break;
case 'X':
#ifdef HAVE_CYRUS_SASL
if( sasl_authz_id != NULL ) {
fprintf( stderr, "%s: -X previously specified\n", prog );
exit( EXIT_FAILURE );
}
if( authmethod != -1 && authmethod != LDAP_AUTH_SASL ) {
fprintf( stderr, "%s: -X incompatible with "
"authentication choice\n", prog );
exit( EXIT_FAILURE );
}
authmethod = LDAP_AUTH_SASL;
sasl_authz_id = optarg;
#else
fprintf( stderr, "%s: not compiled with SASL support\n", prog );
exit( EXIT_FAILURE );
#endif
break;
case 'Z':
#ifdef HAVE_TLS
use_tls++;
#else
fprintf( stderr, "%s: not compiled with TLS support\n", prog );
exit( EXIT_FAILURE );
#endif
break;
default:
if( handle_private_option( i ) ) break;
fprintf( stderr, "%s: unrecognized option -%c\n",
prog, optopt );
usage();
}
}
{
/* prevent bad linking */
LDAPAPIInfo api;
api.ldapai_info_version = LDAP_API_INFO_VERSION;
if ( ldap_get_option(NULL, LDAP_OPT_API_INFO, &api)
!= LDAP_OPT_SUCCESS )
{
fprintf( stderr, "%s: ldap_get_option(API_INFO) failed\n", prog );
exit( EXIT_FAILURE );
}
if (api.ldapai_info_version != LDAP_API_INFO_VERSION) {
fprintf( stderr, "LDAP APIInfo version mismatch: "
"library %d, header %d\n",
api.ldapai_info_version, LDAP_API_INFO_VERSION );
exit( EXIT_FAILURE );
}
if( api.ldapai_api_version != LDAP_API_VERSION ) {
fprintf( stderr, "LDAP API version mismatch: "
"library %d, header %d\n",
api.ldapai_api_version, LDAP_API_VERSION );
exit( EXIT_FAILURE );
}
if( strcmp(api.ldapai_vendor_name, LDAP_VENDOR_NAME ) != 0 ) {
fprintf( stderr, "LDAP vendor name mismatch: "
"library %s, header %s\n",
api.ldapai_vendor_name, LDAP_VENDOR_NAME );
exit( EXIT_FAILURE );
}
if( api.ldapai_vendor_version != LDAP_VENDOR_VERSION ) {
fprintf( stderr, "LDAP vendor version mismatch: "
"library %d, header %d\n",
api.ldapai_vendor_version, LDAP_VENDOR_VERSION );
exit( EXIT_FAILURE );
}
if (version) {
fprintf( stderr, "%s: %s\t(LDAP library: %s %d)\n",
prog, __Version,
LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION );
if (version > 1) exit( EXIT_SUCCESS );
}
ldap_memfree( api.ldapai_vendor_name );
ber_memvfree( (void **)api.ldapai_extensions );
}
if (protocol == -1)
protocol = LDAP_VERSION3;
if (authmethod == -1 && protocol > LDAP_VERSION2) {
#ifdef HAVE_CYRUS_SASL
if ( binddn != NULL ) {
authmethod = LDAP_AUTH_SIMPLE;
} else {
authmethod = LDAP_AUTH_SASL;
}
#else
authmethod = LDAP_AUTH_SIMPLE;
#endif
}
if( protocol == LDAP_VERSION2 ) {
if( assertctl || authzid || manageDIT || manageDSAit ||
#ifdef LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ
proxydn ||
#endif /* LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ */
#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
chaining ||
#endif
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
sessionTracking ||
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
noop || ppolicy || preread || postread )
{
fprintf( stderr, "%s: -e/-M incompatible with LDAPv2\n", prog );
exit( EXIT_FAILURE );
}
#ifdef HAVE_TLS
if( use_tls ) {
fprintf( stderr, "%s: -Z incompatible with LDAPv2\n", prog );
exit( EXIT_FAILURE );
}
#endif
#ifdef HAVE_CYRUS_SASL
if( authmethod == LDAP_AUTH_SASL ) {
fprintf( stderr, "%s: -[IOQRUXY] incompatible with LDAPv2\n",
prog );
exit( EXIT_FAILURE );
}
#endif
}
if ( ( pw_file || want_bindpw ) && !BER_BVISNULL( &passwd ) ) {
fprintf( stderr, "%s: -%c incompatible with -w\n",
prog, ( pw_file ? 'y' : 'W' ) );
exit( EXIT_FAILURE );
}
}
LDAP *
tool_conn_setup( int dont, void (*private_setup)( LDAP * ) )
{
LDAP *ld = NULL;
if ( debug ) {
#ifdef LDAP_DEBUG
if( ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &debug )
!= LBER_OPT_SUCCESS )
{
fprintf( stderr,
"Could not set LBER_OPT_DEBUG_LEVEL %d\n", debug );
}
if( ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &debug )
!= LDAP_OPT_SUCCESS )
{
fprintf( stderr,
"Could not set LDAP_OPT_DEBUG_LEVEL %d\n", debug );
}
#else /* !LDAP_DEBUG */
fprintf( stderr,
"Must compile with LDAP_DEBUG for debugging\n", prog );
#endif /* !LDAP_DEBUG */
}
#ifdef SIGPIPE
(void) SIGNAL( SIGPIPE, SIG_IGN );
#endif
if ( abcan ) {
SIGNAL( SIGINT, do_sig );
}
if ( !dont ) {
int rc;
if ( ldapuri != NULL ) {
LDAPURLDesc *ludlist, **ludp;
char **urls = NULL;
int nurls = 0;
rc = ldap_url_parselist( &ludlist, ldapuri );
if ( rc != LDAP_URL_SUCCESS ) {
fprintf( stderr,
"Could not parse LDAP URI(s)=%s (%d)\n",
ldapuri, rc );
exit( EXIT_FAILURE );
}
for ( ludp = &ludlist; *ludp != NULL; ) {
LDAPURLDesc *lud = *ludp;
char **tmp;
if ( lud->lud_dn != NULL && lud->lud_dn[ 0 ] != '\0' &&
( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) )
{
/* if no host but a DN is provided,
* use DNS SRV to gather the host list
* and turn it into a list of URIs
* using the scheme provided */
char *domain = NULL,
*hostlist = NULL,
**hosts = NULL;
int i,
len_proto = strlen( lud->lud_scheme );
if ( ldap_dn2domain( lud->lud_dn, &domain )
|| domain == NULL )
{
fprintf( stderr,
"DNS SRV: Could not turn "
"DN=\"%s\" into a domain\n",
lud->lud_dn );
goto dnssrv_free;
}
rc = ldap_domain2hostlist( domain, &hostlist );
if ( rc ) {
fprintf( stderr,
"DNS SRV: Could not turn "
"domain=%s into a hostlist\n",
domain );
goto dnssrv_free;
}
hosts = ldap_str2charray( hostlist, " " );
if ( hosts == NULL ) {
fprintf( stderr,
"DNS SRV: Could not parse "
"hostlist=\"%s\"\n",
hostlist );
goto dnssrv_free;
}
for ( i = 0; hosts[ i ] != NULL; i++ )
/* count'em */ ;
tmp = (char **)ber_memrealloc( urls, sizeof( char * ) * ( nurls + i + 1 ) );
if ( tmp == NULL ) {
fprintf( stderr,
"DNS SRV: out of memory?\n" );
goto dnssrv_free;
}
urls = tmp;
urls[ nurls ] = NULL;
for ( i = 0; hosts[ i ] != NULL; i++ ) {
size_t len = len_proto
+ STRLENOF( "://" )
+ strlen( hosts[ i ] )
+ 1;
urls[ nurls + i + 1 ] = NULL;
urls[ nurls + i ] = (char *)malloc( sizeof( char ) * len );
if ( urls[ nurls + i ] == NULL ) {
fprintf( stderr,
"DNS SRV: out of memory?\n" );
goto dnssrv_free;
}
snprintf( urls[ nurls + i ], len, "%s://%s",
lud->lud_scheme, hosts[ i ] );
}
nurls += i;
dnssrv_free:;
ber_memvfree( (void **)hosts );
ber_memfree( hostlist );
ber_memfree( domain );
} else {
tmp = (char **)ber_memrealloc( urls, sizeof( char * ) * ( nurls + 2 ) );
if ( tmp == NULL ) {
fprintf( stderr,
"DNS SRV: out of memory?\n" );
break;
}
urls = tmp;
urls[ nurls + 1 ] = NULL;
urls[ nurls ] = ldap_url_desc2str( lud );
if ( urls[ nurls ] == NULL ) {
fprintf( stderr,
"DNS SRV: out of memory?\n" );
break;
}
nurls++;
}
*ludp = lud->lud_next;
lud->lud_next = NULL;
ldap_free_urldesc( lud );
}
if ( ludlist != NULL ) {
ldap_free_urllist( ludlist );
exit( EXIT_FAILURE );
} else if ( urls == NULL ) {
exit( EXIT_FAILURE );
}
ldap_memfree( ldapuri );
ldapuri = ldap_charray2str( urls, " " );
ber_memvfree( (void **)urls );
}
if ( verbose ) {
fprintf( stderr, "ldap_initialize( %s )\n",
ldapuri != NULL ? ldapuri : "<DEFAULT>" );
}
rc = ldap_initialize( &ld, ldapuri );
if( rc != LDAP_SUCCESS ) {
fprintf( stderr,
"Could not create LDAP session handle for URI=%s (%d): %s\n",
ldapuri, rc, ldap_err2string(rc) );
exit( EXIT_FAILURE );
}
if( private_setup ) private_setup( ld );
/* referrals: obsolete */
if( ldap_set_option( ld, LDAP_OPT_REFERRALS,
referrals ? LDAP_OPT_ON : LDAP_OPT_OFF ) != LDAP_OPT_SUCCESS )
{
fprintf( stderr, "Could not set LDAP_OPT_REFERRALS %s\n",
referrals ? "on" : "off" );
tool_exit( ld, EXIT_FAILURE );
}
#ifdef HAVE_CYRUS_SASL
/* canon */
if( nocanon ) {
if( ldap_set_option( ld, LDAP_OPT_X_SASL_NOCANON,
LDAP_OPT_ON ) != LDAP_OPT_SUCCESS )
{
fprintf( stderr, "Could not set LDAP_OPT_X_SASL_NOCANON on\n" );
tool_exit( ld, EXIT_FAILURE );
}
}
#endif
if( ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &protocol )
!= LDAP_OPT_SUCCESS )
{
fprintf( stderr, "Could not set LDAP_OPT_PROTOCOL_VERSION %d\n",
protocol );
tool_exit( ld, EXIT_FAILURE );
}
if ( nettimeout.tv_sec > 0 ) {
if ( ldap_set_option( ld, LDAP_OPT_NETWORK_TIMEOUT, (void *) &nettimeout )
!= LDAP_OPT_SUCCESS )
{
fprintf( stderr, "Could not set LDAP_OPT_NETWORK_TIMEOUT %ld\n",
(long)nettimeout.tv_sec );
tool_exit( ld, EXIT_FAILURE );
}
}
rc = ldap_connect( ld );
if( rc != LDAP_SUCCESS ) {
fprintf( stderr,
"Could not connect to URI=%s (%d): %s\n",
ldapuri, rc, ldap_err2string(rc) );
tool_exit( ld, EXIT_FAILURE );
}
if ( use_tls ) {
rc = ldap_start_tls_s( ld, NULL, NULL );
if ( rc != LDAP_SUCCESS ) {
char *msg=NULL;
ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&msg);
tool_perror( "ldap_start_tls", rc, NULL, NULL, msg, NULL );
ldap_memfree(msg);
if ( use_tls > 1 || rc < 0 ) {
tool_exit( ld, EXIT_FAILURE );
}
}
}
}
return ld;
}
void
tool_bind( LDAP *ld )
{
LDAPControl **sctrlsp = NULL;
LDAPControl *sctrls[4];
LDAPControl sctrl[3];
int nsctrls = 0;
int rc, msgid;
LDAPMessage *result = NULL;
int err;
char *matched = NULL;
char *info = NULL;
char **refs = NULL;
LDAPControl **ctrls = NULL;
char msgbuf[256];
msgbuf[0] = 0;
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
if ( ppolicy ) {
LDAPControl c;
c.ldctl_oid = LDAP_CONTROL_PASSWORDPOLICYREQUEST;
c.ldctl_value.bv_val = NULL;
c.ldctl_value.bv_len = 0;
c.ldctl_iscritical = 0;
sctrl[nsctrls] = c;
sctrls[nsctrls] = &sctrl[nsctrls];
sctrls[++nsctrls] = NULL;
}
#endif
if ( bauthzid ) {
LDAPControl c;
c.ldctl_oid = LDAP_CONTROL_AUTHZID_REQUEST;
c.ldctl_iscritical = bauthzid > 1;
BER_BVZERO( &c.ldctl_value );
sctrl[nsctrls] = c;
sctrls[nsctrls] = &sctrl[nsctrls];
sctrls[++nsctrls] = NULL;
}
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
if ( sessionTracking ) {
LDAPControl c;
if ( BER_BVISNULL( &stValue) && st_value( ld, &stValue ) ) {
tool_exit( ld, EXIT_FAILURE );
}
c.ldctl_oid = LDAP_CONTROL_X_SESSION_TRACKING;
c.ldctl_iscritical = 0;
c.ldctl_value = stValue;
sctrl[nsctrls] = c;
sctrls[nsctrls] = &sctrl[nsctrls];
sctrls[++nsctrls] = NULL;
}
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
if ( nsctrls ) {
sctrlsp = sctrls;
}
assert( nsctrls < (int) (sizeof(sctrls)/sizeof(sctrls[0])) );
if ( pw_file || want_bindpw ) {
assert( passwd.bv_val == NULL && passwd.bv_len == 0 );
if ( pw_file ) {
if ( lutil_get_filed_password( pw_file, &passwd ) ) {
tool_exit( ld, EXIT_FAILURE );
}
} else {
char *pw = getpassphrase( _("Enter LDAP Password: ") );
if ( pw == NULL ) { /* Allow EOF to exit. */
tool_exit( ld, EXIT_FAILURE );
}
passwd.bv_val = ber_strdup( pw );
if ( passwd.bv_val == NULL ) {
tool_exit( ld, EXIT_FAILURE );
}
passwd.bv_len = strlen( passwd.bv_val );
}
}
if ( authmethod == LDAP_AUTH_SASL ) {
#ifdef HAVE_CYRUS_SASL
void *defaults;
const char *rmech = NULL;
if( sasl_secprops != NULL ) {
rc = ldap_set_option( ld, LDAP_OPT_X_SASL_SECPROPS,
(void *) sasl_secprops );
if( rc != LDAP_OPT_SUCCESS ) {
fprintf( stderr,
"Could not set LDAP_OPT_X_SASL_SECPROPS: %s\n",
sasl_secprops );
tool_exit( ld, LDAP_LOCAL_ERROR );
}
}
defaults = lutil_sasl_defaults( ld,
sasl_mech,
sasl_realm,
sasl_authc_id,
passwd.bv_val,
sasl_authz_id );
do {
rc = ldap_sasl_interactive_bind( ld, binddn, sasl_mech,
sctrlsp, NULL, sasl_flags, lutil_sasl_interact, defaults,
result, &rmech, &msgid );
if ( rc != LDAP_SASL_BIND_IN_PROGRESS )
break;
ldap_msgfree( result );
if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1 || !result ) {
ldap_get_option( ld, LDAP_OPT_RESULT_CODE, (void*)&err );
ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&info );
tool_perror( "ldap_sasl_interactive_bind",
err, NULL, NULL, info, NULL );
ldap_memfree( info );
tool_exit( ld, err );
}
} while ( rc == LDAP_SASL_BIND_IN_PROGRESS );
lutil_sasl_freedefs( defaults );
if ( rc != LDAP_SUCCESS ) {
ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&info );
tool_perror( "ldap_sasl_interactive_bind",
rc, NULL, NULL, info, NULL );
ldap_memfree( info );
tool_exit( ld, rc );
}
#else
fprintf( stderr, "%s: not compiled with SASL support\n", prog );
tool_exit( ld, LDAP_NOT_SUPPORTED );
#endif
} else {
/* simple bind */
rc = ldap_sasl_bind( ld, binddn, LDAP_SASL_SIMPLE, &passwd,
sctrlsp, NULL, &msgid );
if ( msgid == -1 ) {
tool_perror( "ldap_sasl_bind(SIMPLE)", rc,
NULL, NULL, NULL, NULL );
tool_exit( ld, rc );
}
rc = ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &result );
if ( rc == -1 ) {
tool_perror( "ldap_result", -1, NULL, NULL, NULL, NULL );
tool_exit( ld, LDAP_LOCAL_ERROR );
}
if ( rc == 0 ) {
tool_perror( "ldap_result", LDAP_TIMEOUT, NULL, NULL, NULL, NULL );
tool_exit( ld, LDAP_LOCAL_ERROR );
}
}
if ( result ) {
rc = ldap_parse_result( ld, result, &err, &matched, &info, &refs,
&ctrls, 1 );
if ( rc != LDAP_SUCCESS ) {
tool_perror( "ldap_bind parse result", rc, NULL, matched, info, refs );
tool_exit( ld, LDAP_LOCAL_ERROR );
}
}
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
if ( ctrls && ppolicy ) {
LDAPControl *ctrl;
int expire, grace, len = 0;
LDAPPasswordPolicyError pErr = -1;
ctrl = ldap_control_find( LDAP_CONTROL_PASSWORDPOLICYRESPONSE,
ctrls, NULL );
if ( ctrl && ldap_parse_passwordpolicy_control( ld, ctrl,
&expire, &grace, &pErr ) == LDAP_SUCCESS )
{
if ( pErr != PP_noError ){
msgbuf[0] = ';';
msgbuf[1] = ' ';
strcpy( msgbuf+2, ldap_passwordpolicy_err2txt( pErr ));
len = strlen( msgbuf );
}
if ( expire >= 0 ) {
sprintf( msgbuf+len,
" (Password expires in %d seconds)",
expire );
} else if ( grace >= 0 ) {
sprintf( msgbuf+len,
" (Password expired, %d grace logins remain)",
grace );
}
}
}
#endif
if ( ctrls && bauthzid ) {
LDAPControl *ctrl;
ctrl = ldap_control_find( LDAP_CONTROL_AUTHZID_RESPONSE,
ctrls, NULL );
if ( ctrl ) {
LDAPControl *ctmp[2];
ctmp[0] = ctrl;
ctmp[1] = NULL;
tool_print_ctrls( ld, ctmp );
}
}
#ifdef LDAP_CONTROL_X_PASSWORD_EXPIRED
if ( ctrls ) {
LDAPControl *ctrl;
ctrl = ldap_control_find( LDAP_CONTROL_X_PASSWORD_EXPIRED,
ctrls, NULL );
if ( !ctrl )
ctrl = ldap_control_find( LDAP_CONTROL_X_PASSWORD_EXPIRING,
ctrls, NULL );
if ( ctrl ) {
LDAPControl *ctmp[2];
ctmp[0] = ctrl;
ctmp[1] = NULL;
tool_print_ctrls( ld, ctmp );
}
}
#endif
if ( ctrls ) {
ldap_controls_free( ctrls );
}
if ( err != LDAP_SUCCESS
|| msgbuf[0]
|| ( matched && matched[ 0 ] )
|| ( info && info[ 0 ] )
|| refs )
{
tool_perror( "ldap_bind", err, msgbuf, matched, info, refs );
if( matched ) ber_memfree( matched );
if( info ) ber_memfree( info );
if( refs ) ber_memvfree( (void **)refs );
if ( err != LDAP_SUCCESS ) tool_exit( ld, err );
}
}
void
tool_unbind( LDAP *ld )
{
int err = ldap_set_option( ld, LDAP_OPT_SERVER_CONTROLS, NULL );
if ( err != LDAP_OPT_SUCCESS ) {
fprintf( stderr, "Could not unset controls\n");
}
(void) ldap_unbind_ext( ld, NULL, NULL );
}
void
tool_exit( LDAP *ld, int status )
{
if ( ld != NULL ) {
tool_unbind( ld );
}
tool_destroy();
exit( status );
}
/* Set server controls. Add controls extra_c[0..count-1], if set. */
void
tool_server_controls( LDAP *ld, LDAPControl *extra_c, int count )
{
int i = 0, j, crit = 0, err;
LDAPControl c[16], **ctrls;
if ( ! ( assertctl
|| authzid
#ifdef LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ
|| proxydn
#endif /* LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ */
|| manageDIT
|| manageDSAit
|| noop
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
|| ppolicy
#endif
|| preread
|| postread
#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
|| chaining
#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
|| sessionTracking
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
|| count
|| unknown_ctrls_num ) )
{
return;
}
ctrls = (LDAPControl**) malloc(sizeof(c) + (count + unknown_ctrls_num + 1)*sizeof(LDAPControl*));
if ( ctrls == NULL ) {
fprintf( stderr, "No memory\n" );
tool_exit( ld, EXIT_FAILURE );
}
if ( assertctl ) {
if ( BER_BVISNULL( &assertionvalue ) ) {
err = ldap_create_assertion_control_value( ld,
assertion, &assertionvalue );
if ( err ) {
fprintf( stderr,
"Unable to create assertion value "
"\"%s\" (%d)\n", assertion, err );
}
}
c[i].ldctl_oid = LDAP_CONTROL_ASSERT;
c[i].ldctl_value = assertionvalue;
c[i].ldctl_iscritical = assertctl > 1;
ctrls[i] = &c[i];
i++;
}
if ( authzid ) {
c[i].ldctl_value.bv_val = authzid;
c[i].ldctl_value.bv_len = strlen( authzid );
c[i].ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
c[i].ldctl_iscritical = authzcrit;
ctrls[i] = &c[i];
i++;
}
#ifdef LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ
/* NOTE: doesn't need an extra count because it's incompatible
* with authzid */
if ( proxydn ) {
BerElementBuffer berbuf;
BerElement *ber = (BerElement *)&berbuf;
ber_init2( ber, NULL, LBER_USE_DER );
if ( ber_printf( ber, "s", proxydn ) == -1 ) {
tool_exit( ld, EXIT_FAILURE );
}
if ( ber_flatten2( ber, &c[i].ldctl_value, 0 ) == -1 ) {
tool_exit( ld, EXIT_FAILURE );
}
c[i].ldctl_oid = LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ;
c[i].ldctl_iscritical = authzcrit;
ctrls[i] = &c[i];
i++;
}
#endif /* LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ */
if ( manageDIT ) {
c[i].ldctl_oid = LDAP_CONTROL_MANAGEDIT;
BER_BVZERO( &c[i].ldctl_value );
c[i].ldctl_iscritical = manageDIT > 1;
ctrls[i] = &c[i];
i++;
}
if ( manageDSAit ) {
c[i].ldctl_oid = LDAP_CONTROL_MANAGEDSAIT;
BER_BVZERO( &c[i].ldctl_value );
c[i].ldctl_iscritical = manageDSAit > 1;
ctrls[i] = &c[i];
i++;
}
if ( noop ) {
c[i].ldctl_oid = LDAP_CONTROL_NOOP;
BER_BVZERO( &c[i].ldctl_value );
c[i].ldctl_iscritical = noop > 1;
ctrls[i] = &c[i];
i++;
}
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
if ( ppolicy ) {
c[i].ldctl_oid = LDAP_CONTROL_PASSWORDPOLICYREQUEST;
BER_BVZERO( &c[i].ldctl_value );
c[i].ldctl_iscritical = 0;
ctrls[i] = &c[i];
i++;
}
#endif
if ( preread ) {
BerElementBuffer berbuf;
BerElement *ber = (BerElement *)&berbuf;
char **attrs = NULL;
if( preread_attrs ) {
attrs = ldap_str2charray( preread_attrs, "," );
}
ber_init2( ber, NULL, LBER_USE_DER );
if( ber_printf( ber, "{v}", attrs ) == -1 ) {
fprintf( stderr, "preread attrs encode failed.\n" );
tool_exit( ld, EXIT_FAILURE );
}
err = ber_flatten2( ber, &c[i].ldctl_value, 0 );
if( err < 0 ) {
fprintf( stderr, "preread flatten failed (%d)\n", err );
tool_exit( ld, EXIT_FAILURE );
}
c[i].ldctl_oid = LDAP_CONTROL_PRE_READ;
c[i].ldctl_iscritical = preread > 1;
ctrls[i] = &c[i];
i++;
if( attrs ) ldap_charray_free( attrs );
}
if ( postread ) {
BerElementBuffer berbuf;
BerElement *ber = (BerElement *)&berbuf;
char **attrs = NULL;
if( postread_attrs ) {
attrs = ldap_str2charray( postread_attrs, "," );
}
ber_init2( ber, NULL, LBER_USE_DER );
if( ber_printf( ber, "{v}", attrs ) == -1 ) {
fprintf( stderr, "postread attrs encode failed.\n" );
tool_exit( ld, EXIT_FAILURE );
}
err = ber_flatten2( ber, &c[i].ldctl_value, 0 );
if( err < 0 ) {
fprintf( stderr, "postread flatten failed (%d)\n", err );
tool_exit( ld, EXIT_FAILURE );
}
c[i].ldctl_oid = LDAP_CONTROL_POST_READ;
c[i].ldctl_iscritical = postread > 1;
ctrls[i] = &c[i];
i++;
if( attrs ) ldap_charray_free( attrs );
}
#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
if ( chaining ) {
if ( chainingResolve > -1 ) {
BerElementBuffer berbuf;
BerElement *ber = (BerElement *)&berbuf;
ber_init2( ber, NULL, LBER_USE_DER );
err = ber_printf( ber, "{e" /* } */, chainingResolve );
if ( err == -1 ) {
ber_free( ber, 1 );
fprintf( stderr, _("Chaining behavior control encoding error!\n") );
tool_exit( ld, EXIT_FAILURE );
}
if ( chainingContinuation > -1 ) {
err = ber_printf( ber, "e", chainingContinuation );
if ( err == -1 ) {
ber_free( ber, 1 );
fprintf( stderr, _("Chaining behavior control encoding error!\n") );
tool_exit( ld, EXIT_FAILURE );
}
}
err = ber_printf( ber, /* { */ "N}" );
if ( err == -1 ) {
ber_free( ber, 1 );
fprintf( stderr, _("Chaining behavior control encoding error!\n") );
tool_exit( ld, EXIT_FAILURE );
}
if ( ber_flatten2( ber, &c[i].ldctl_value, 0 ) == -1 ) {
tool_exit( ld, EXIT_FAILURE );
}
} else {
BER_BVZERO( &c[i].ldctl_value );
}
c[i].ldctl_oid = LDAP_CONTROL_X_CHAINING_BEHAVIOR;
c[i].ldctl_iscritical = chaining > 1;
ctrls[i] = &c[i];
i++;
}
#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
#ifdef LDAP_CONTROL_X_SESSION_TRACKING
if ( sessionTracking ) {
if ( BER_BVISNULL( &stValue ) && st_value( ld, &stValue ) ) {
tool_exit( ld, EXIT_FAILURE );
}
c[i].ldctl_oid = LDAP_CONTROL_X_SESSION_TRACKING;
c[i].ldctl_iscritical = 0;
c[i].ldctl_value = stValue;
ctrls[i] = &c[i];
i++;
}
#endif /* LDAP_CONTROL_X_SESSION_TRACKING */
while ( count-- ) {
ctrls[i++] = extra_c++;
}
for ( count = 0; count < unknown_ctrls_num; count++ ) {
ctrls[i++] = &unknown_ctrls[count];
}
ctrls[i] = NULL;
err = ldap_set_option( ld, LDAP_OPT_SERVER_CONTROLS, ctrls );
if ( err != LDAP_OPT_SUCCESS ) {
for ( j = 0; j < i; j++ ) {
if ( ctrls[j]->ldctl_iscritical ) crit = 1;
}
fprintf( stderr, "Could not set %scontrols\n",
crit ? "critical " : "" );
}
free( ctrls );
if ( crit ) {
tool_exit( ld, EXIT_FAILURE );
}
}
int
tool_check_abandon( LDAP *ld, int msgid )
{
int rc;
LDAPControl *sctrls[1] = { NULL };
switch ( gotintr ) {
case Intr_Cancel:
rc = ldap_cancel_s( ld, msgid, sctrls, NULL );
fprintf( stderr, "got interrupt, cancel got %d: %s\n",
rc, ldap_err2string( rc ) );
return -1;
case Intr_Abandon:
rc = ldap_abandon_ext( ld, msgid, sctrls, NULL );
fprintf( stderr, "got interrupt, abandon got %d: %s\n",
rc, ldap_err2string( rc ) );
return -1;
case Intr_Ignore:
/* just unbind, ignoring the request */
return -1;
}
return 0;
}
static int
print_prepostread( LDAP *ld, LDAPControl *ctrl, struct berval *what)
{
BerElement *ber;
struct berval bv;
tool_write_ldif( LDIF_PUT_COMMENT, "==> ",
what->bv_val, what->bv_len );
ber = ber_init( &ctrl->ldctl_value );
if ( ber == NULL ) {
/* error? */
return 1;
} else if ( ber_scanf( ber, "{m{" /*}}*/, &bv ) == LBER_ERROR ) {
/* error? */
return 1;
} else {
tool_write_ldif( LDIF_PUT_VALUE, "dn", bv.bv_val, bv.bv_len );
while ( ber_scanf( ber, "{m" /*}*/, &bv ) != LBER_ERROR ) {
int i;
BerVarray vals = NULL;
char *str = NULL;
if ( ber_scanf( ber, "[W]", &vals ) == LBER_ERROR ||
vals == NULL )
{
/* error? */
return 1;
}
if ( ldif ) {
char *ptr;
str = malloc( bv.bv_len + STRLENOF(": ") + 1 );
ptr = str;
ptr = lutil_strncopy( ptr, bv.bv_val, bv.bv_len );
ptr = lutil_strcopy( ptr, ": " );
}
for ( i = 0; vals[ i ].bv_val != NULL; i++ ) {
tool_write_ldif(
ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? str : bv.bv_val, vals[ i ].bv_val, vals[ i ].bv_len );
}
ber_bvarray_free( vals );
if ( str ) free( str );
}
}
if ( ber != NULL ) {
ber_free( ber, 1 );
}
tool_write_ldif( LDIF_PUT_COMMENT, "<== ",
what->bv_val, what->bv_len );
return 0;
}
static int
print_preread( LDAP *ld, LDAPControl *ctrl )
{
static struct berval what = BER_BVC( "preread" );
return print_prepostread( ld, ctrl, &what );
}
static int
print_postread( LDAP *ld, LDAPControl *ctrl )
{
static struct berval what = BER_BVC( "postread" );
return print_prepostread( ld, ctrl, &what );
}
static int
print_paged_results( LDAP *ld, LDAPControl *ctrl )
{
ber_int_t estimate;
/* note: pr_cookie is being malloced; it's freed
* the next time the control is sent, but the last
* time it's not; we don't care too much, because
* the last time an empty value is returned... */
if ( ldap_parse_pageresponse_control( ld, ctrl, &estimate, &pr_cookie )
!= LDAP_SUCCESS )
{
/* error? */
return 1;
} else {
char buf[ BUFSIZ ], *ptr = buf;
int plen;
if ( estimate > 0 ) {
plen = sprintf( buf, "estimate=%d cookie=", estimate );
} else {
plen = sprintf( buf, "cookie=" );
}
if ( pr_cookie.bv_len > 0 ) {
struct berval bv;
bv.bv_len = LUTIL_BASE64_ENCODE_LEN(
pr_cookie.bv_len ) + 1;
ptr = ber_memalloc( bv.bv_len + 1 + plen );
bv.bv_val = ptr + plen;
strcpy( ptr, buf );
bv.bv_len = lutil_b64_ntop(
(unsigned char *) pr_cookie.bv_val,
pr_cookie.bv_len,
bv.bv_val, bv.bv_len );
pr_morePagedResults = 1;
plen += bv.bv_len;
}
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "pagedresults: " : "pagedresults",
ptr, plen );
if ( ptr != buf )
ber_memfree( ptr );
}
return 0;
}
static int
print_psearch( LDAP *ld, LDAPControl *ctrl )
{
int rc;
int chgtype;
int chgpres;
long chgnum;
struct berval prevdn;
rc = ldap_parse_entrychange_control( ld, ctrl, &chgtype, &prevdn,
&chgpres, &chgnum );
if ( rc == LDAP_SUCCESS ) {
char buf[ BUFSIZ ];
char *ptr = buf;
int blen = sizeof(buf), len;
switch( chgtype ) {
case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_ADD:
len = snprintf( ptr, blen, "add" );
ptr += len;
blen -= len;
break;
case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_DELETE:
len = snprintf( ptr, blen, "delete" );
ptr += len;
blen -= len;
break;
case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_MODIFY:
len = snprintf( ptr, blen, "modify" );
ptr += len;
blen -= len;
break;
case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_RENAME:
len = snprintf( ptr, blen, "moddn" );
ptr += len;
blen -= len;
if ( prevdn.bv_val != NULL ) {
len = snprintf( ptr, blen, " prevdn %s", prevdn.bv_val );
ptr += len;
blen -= len;
}
break;
}
if ( chgpres ) {
len = snprintf( ptr, blen, " changeNumber %ld", chgnum) ;
ptr += len;
blen -= len;
}
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "persistentSearch: " : "persistentSearch", buf, len );
}
return rc;
}
static int
print_sss( LDAP *ld, LDAPControl *ctrl )
{
int rc;
ber_int_t err;
char *attr;
rc = ldap_parse_sortresponse_control( ld, ctrl, &err, &attr );
if ( rc == LDAP_SUCCESS ) {
char buf[ BUFSIZ ];
rc = snprintf( buf, sizeof(buf), "(%d) %s%s%s",
err, ldap_err2string(err), attr ? " " : "", attr ? attr : "" );
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "sortResult: " : "sortResult", buf, rc );
}
return rc;
}
static int
print_vlv( LDAP *ld, LDAPControl *ctrl )
{
int rc;
ber_int_t err;
struct berval bv;
rc = ldap_parse_vlvresponse_control( ld, ctrl, &vlvPos, &vlvCount,
&vlvContext, &err );
if ( rc == LDAP_SUCCESS ) {
char buf[ BUFSIZ ];
if ( vlvContext && vlvContext->bv_len > 0 ) {
bv.bv_len = LUTIL_BASE64_ENCODE_LEN(
vlvContext->bv_len ) + 1;
bv.bv_val = ber_memalloc( bv.bv_len + 1 );
bv.bv_len = lutil_b64_ntop(
(unsigned char *) vlvContext->bv_val,
vlvContext->bv_len,
bv.bv_val, bv.bv_len );
} else {
bv.bv_val = "";
bv.bv_len = 0;
}
rc = snprintf( buf, sizeof(buf), "pos=%d count=%d context=%s (%d) %s",
vlvPos, vlvCount, bv.bv_val,
err, ldap_err2string(err));
if ( bv.bv_len )
ber_memfree( bv.bv_val );
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "vlvResult: " : "vlvResult", buf, rc );
}
return rc;
}
#ifdef LDAP_CONTROL_X_DEREF
static int
print_deref( LDAP *ld, LDAPControl *ctrl )
{
LDAPDerefRes *drhead = NULL, *dr;
int rc;
rc = ldap_parse_derefresponse_control( ld, ctrl, &drhead );
if ( rc != LDAP_SUCCESS ) {
return rc;
}
for ( dr = drhead; dr != NULL; dr = dr->next ) {
LDAPDerefVal *dv;
ber_len_t len;
char *buf, *ptr;
len = strlen( dr->derefAttr ) + STRLENOF(": ");
for ( dv = dr->attrVals; dv != NULL; dv = dv->next ) {
if ( dv->vals != NULL ) {
int j;
ber_len_t tlen = strlen(dv->type);
for ( j = 0; dv->vals[ j ].bv_val != NULL; j++ ) {
len += STRLENOF("<:=>;") + tlen + 4*((dv->vals[ j ].bv_len - 1)/3 + 1);
}
}
}
len += dr->derefVal.bv_len + STRLENOF("\n");
buf = ldap_memalloc( len + 1 );
if ( buf == NULL ) {
rc = LDAP_NO_MEMORY;
goto done;
}
ptr = buf;
ptr = lutil_strcopy( ptr, dr->derefAttr );
*ptr++ = ':';
*ptr++ = ' ';
for ( dv = dr->attrVals; dv != NULL; dv = dv->next ) {
if ( dv->vals != NULL ) {
int j;
for ( j = 0; dv->vals[ j ].bv_val != NULL; j++ ) {
int k = ldif_is_not_printable( dv->vals[ j ].bv_val, dv->vals[ j ].bv_len );
*ptr++ = '<';
ptr = lutil_strcopy( ptr, dv->type );
if ( k ) {
*ptr++ = ':';
}
*ptr++ = '=';
if ( k ) {
k = lutil_b64_ntop(
(unsigned char *) dv->vals[ j ].bv_val,
dv->vals[ j ].bv_len,
ptr, buf + len - ptr );
assert( k >= 0 );
ptr += k;
} else {
ptr = lutil_memcopy( ptr, dv->vals[ j ].bv_val, dv->vals[ j ].bv_len );
}
*ptr++ = '>';
*ptr++ = ';';
}
}
}
ptr = lutil_strncopy( ptr, dr->derefVal.bv_val, dr->derefVal.bv_len );
*ptr = '\0';
assert( ptr <= buf + len );
tool_write_ldif( LDIF_PUT_COMMENT, NULL, buf, ptr - buf);
ldap_memfree( buf );
}
rc = LDAP_SUCCESS;
done:;
ldap_derefresponse_free( drhead );
return rc;
}
#endif
#ifdef LDAP_CONTROL_X_WHATFAILED
static int
print_whatfailed( LDAP *ld, LDAPControl *ctrl )
{
BerElement *ber;
ber_tag_t tag;
ber_len_t siz;
BerVarray bva = NULL;
/* Create a BerElement from the berval returned in the control. */
ber = ber_init( &ctrl->ldctl_value );
if ( ber == NULL ) {
return LDAP_NO_MEMORY;
}
siz = sizeof(struct berval);
tag = ber_scanf( ber, "[M]", &bva, &siz, 0 );
if ( tag != LBER_ERROR ) {
int i;
tool_write_ldif( LDIF_PUT_COMMENT, " what failed:", NULL, 0 );
for ( i = 0; bva[i].bv_val != NULL; i++ ) {
tool_write_ldif( LDIF_PUT_COMMENT, NULL, bva[i].bv_val, bva[i].bv_len );
}
ldap_memfree( bva );
}
ber_free( ber, 1 );
return 0;
}
#endif
static int
print_syncstate( LDAP *ld, LDAPControl *ctrl )
{
struct berval syncUUID, syncCookie = BER_BVNULL;
char buf[LDAP_LUTIL_UUIDSTR_BUFSIZE], *uuidstr = "(UUID malformed)";
BerElement *ber;
ber_tag_t tag;
ber_int_t state;
int rc;
if ( ldif ) {
return 0;
}
/* Create a BerElement from the berval returned in the control. */
ber = ber_init( &ctrl->ldctl_value );
if ( ber == NULL ) {
return LDAP_NO_MEMORY;
}
if ( ber_scanf( ber, "{em", &state, &syncUUID ) == LBER_ERROR ) {
ber_free( ber, 1 );
return 1;
}
tag = ber_get_stringbv( ber, &syncCookie, 0 );
rc = lutil_uuidstr_from_normalized(
syncUUID.bv_val, syncUUID.bv_len,
buf, LDAP_LUTIL_UUIDSTR_BUFSIZE );
if ( rc > 0 && rc < LDAP_LUTIL_UUIDSTR_BUFSIZE ) {
uuidstr = buf;
}
switch ( state ) {
case LDAP_SYNC_PRESENT:
printf(_("# SyncState control, UUID %s present\n"), uuidstr);
break;
case LDAP_SYNC_ADD:
printf(_("# SyncState control, UUID %s added\n"), uuidstr);
break;
case LDAP_SYNC_MODIFY:
printf(_("# SyncState control, UUID %s modified\n"), uuidstr);
break;
case LDAP_SYNC_DELETE:
printf(_("# SyncState control, UUID %s deleted\n"), uuidstr);
break;
default:
ber_free( ber, 1 );
return 1;
}
if ( tag != LBER_ERROR ) {
if ( ldif_is_not_printable( syncCookie.bv_val, syncCookie.bv_len ) ) {
struct berval bv;
bv.bv_len = LUTIL_BASE64_ENCODE_LEN( syncCookie.bv_len ) + 1;
bv.bv_val = ber_memalloc( bv.bv_len + 1 );
bv.bv_len = lutil_b64_ntop(
(unsigned char *) syncCookie.bv_val, syncCookie.bv_len,
bv.bv_val, bv.bv_len );
printf(_("# cookie:: %s\n"), bv.bv_val );
ber_memfree( bv.bv_val );
} else {
printf(_("# cookie: %s\n"), syncCookie.bv_val );
}
}
ber_free( ber, 1 );
return 0;
}
static int
print_syncdone( LDAP *ld, LDAPControl *ctrl )
{
BerElement *ber;
struct berval cookie = BER_BVNULL;
ber_len_t len;
ber_int_t refreshDeletes = 0;
if ( ldif ) {
return 0;
}
/* Create a BerElement from the berval returned in the control. */
ber = ber_init( &ctrl->ldctl_value );
if ( ber == NULL ) {
return LDAP_NO_MEMORY;
}
ber_skip_tag( ber, &len );
if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
ber_scanf( ber, "m", &cookie );
}
if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) {
ber_scanf( ber, "b", &refreshDeletes );
}
printf(_("# SyncDone control refreshDeletes=%d\n"), refreshDeletes ? 1 : 0 );
if ( !BER_BVISNULL( &cookie ) ) {
if ( ldif_is_not_printable( cookie.bv_val, cookie.bv_len ) ) {
struct berval bv;
bv.bv_len = LUTIL_BASE64_ENCODE_LEN( cookie.bv_len ) + 1;
bv.bv_val = ber_memalloc( bv.bv_len + 1 );
bv.bv_len = lutil_b64_ntop(
(unsigned char *) cookie.bv_val, cookie.bv_len,
bv.bv_val, bv.bv_len );
printf(_("# cookie:: %s\n"), bv.bv_val );
ber_memfree( bv.bv_val );
} else {
printf(_("# cookie: %s\n"), cookie.bv_val );
}
}
ber_free( ber, 1 );
return 0;
}
#ifdef LDAP_CONTROL_X_DIRSYNC
static int
print_dirsync( LDAP *ld, LDAPControl *ctrl )
{
int rc, continueFlag;
struct berval cookie;
rc = ldap_parse_dirsync_control( ld, ctrl,
&continueFlag, &cookie );
if ( rc == LDAP_SUCCESS ) {
printf(_("# DirSync control continueFlag=%d\n"), continueFlag );
if ( !BER_BVISNULL( &cookie )) {
if ( ldif_is_not_printable( cookie.bv_val, cookie.bv_len ) ) {
struct berval bv;
bv.bv_len = LUTIL_BASE64_ENCODE_LEN( cookie.bv_len ) + 1;
bv.bv_val = ber_memalloc( bv.bv_len + 1 );
bv.bv_len = lutil_b64_ntop(
(unsigned char *) cookie.bv_val, cookie.bv_len,
bv.bv_val, bv.bv_len );
printf(_("# cookie:: %s\n"), bv.bv_val );
ber_memfree( bv.bv_val );
} else {
printf(_("# cookie: %s\n"), cookie.bv_val );
}
}
}
return rc;
}
#endif
#ifdef LDAP_CONTROL_AUTHZID_RESPONSE
static int
print_authzid( LDAP *ld, LDAPControl *ctrl )
{
if ( ctrl->ldctl_value.bv_len ) {
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "authzid: " : "authzid",
ctrl->ldctl_value.bv_val, ctrl->ldctl_value.bv_len );
} else {
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "authzid: " : "authzid",
"anonymous", STRLENOF("anonymous") );
}
return 0;
}
#endif
#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
static int
print_ppolicy( LDAP *ld, LDAPControl *ctrl )
{
int expire = 0, grace = 0, rc;
LDAPPasswordPolicyError pperr;
rc = ldap_parse_passwordpolicy_control( ld, ctrl,
&expire, &grace, &pperr );
if ( rc == LDAP_SUCCESS ) {
char buf[ BUFSIZ ], *ptr = buf;
if ( expire != -1 ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"expire=%d", expire );
}
if ( grace != -1 ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"%sgrace=%d", ptr == buf ? "" : " ", grace );
}
if ( pperr != PP_noError ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"%serror=%d (%s)", ptr == buf ? "" : " ",
pperr,
ldap_passwordpolicy_err2txt( pperr ) );
}
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "ppolicy: " : "ppolicy", buf, ptr - buf );
}
return rc;
}
#endif
#ifdef LDAP_CONTROL_X_PASSWORD_EXPIRED
static int
print_netscape_pwexpired( LDAP *ld, LDAPControl *ctrl )
{
printf(_("# PasswordExpired control\n") );
return 0;
}
static int
print_netscape_pwexpiring( LDAP *ld, LDAPControl *ctrl )
{
long expiring = 0;
int rc;
rc = ldap_parse_password_expiring_control( ld, ctrl, &expiring );
if ( rc == LDAP_SUCCESS ) {
printf(_("# PasswordExpiring control seconds=%ld\n"), expiring );
}
return rc;
}
#endif
#ifdef LDAP_CONTROL_X_ACCOUNT_USABILITY
static int
print_account_usability( LDAP *ld, LDAPControl *ctrl )
{
LDAPAccountUsability usability;
ber_int_t available = 0;
int rc;
rc = ldap_parse_accountusability_control( ld, ctrl, &available, &usability );
if ( rc == LDAP_SUCCESS ) {
char buf[ BUFSIZ ], *ptr = buf;
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"%savailable", available ? "" : "not " );
if ( available ) {
if ( usability.seconds_remaining == -1 ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
" and does not expire" );
} else {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
" expire=%d", usability.seconds_remaining );
}
} else {
int added = 0;
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
" (" /* ')' */ );
if ( usability.more_info.inactive ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"inactive " );
added++;
}
if ( usability.more_info.reset ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"reset " );
added++;
}
if ( usability.more_info.expired ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"expired " );
added++;
}
if ( added ) {
ptr[-1] = ')';
*ptr++ = ' ';
} else {
*(--ptr) = '\0';
}
if ( usability.more_info.remaining_grace != -1 ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"grace=%d ", usability.more_info.remaining_grace );
}
if ( usability.more_info.seconds_before_unlock != -1 ) {
ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ),
"seconds_before_unlock=%d ", usability.more_info.seconds_before_unlock );
}
*(--ptr) = '\0';
}
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
ldif ? "accountUsability: " : "accountUsability", buf, ptr - buf );
}
return rc;
}
#endif
void tool_print_ctrls(
LDAP *ld,
LDAPControl **ctrls )
{
int i;
char *ptr;
for ( i = 0; ctrls[i] != NULL; i++ ) {
/* control: OID criticality base64value */
struct berval b64 = BER_BVNULL;
ber_len_t len;
char *str;
int j;
/* FIXME: there might be cases where a control has NULL OID;
* this makes little sense, especially when returned by the
* server, but libldap happily allows it */
if ( ctrls[i]->ldctl_oid == NULL ) {
continue;
}
len = ldif ? 2 : 0;
len += strlen( ctrls[i]->ldctl_oid );
/* add enough for space after OID and the critical value itself */
len += ctrls[i]->ldctl_iscritical
? sizeof("true") : sizeof("false");
/* convert to base64 */
if ( !BER_BVISNULL( &ctrls[i]->ldctl_value ) ) {
b64.bv_len = LUTIL_BASE64_ENCODE_LEN(
ctrls[i]->ldctl_value.bv_len ) + 1;
b64.bv_val = ber_memalloc( b64.bv_len + 1 );
b64.bv_len = lutil_b64_ntop(
(unsigned char *) ctrls[i]->ldctl_value.bv_val,
ctrls[i]->ldctl_value.bv_len,
b64.bv_val, b64.bv_len );
}
if ( b64.bv_len ) {
len += 1 + b64.bv_len;
}
ptr = str = malloc( len + 1 );
if ( ldif ) {
ptr = lutil_strcopy( ptr, ": " );
}
ptr = lutil_strcopy( ptr, ctrls[i]->ldctl_oid );
ptr = lutil_strcopy( ptr, ctrls[i]->ldctl_iscritical
? " true" : " false" );
if ( b64.bv_len ) {
ptr = lutil_strcopy( ptr, " " );
ptr = lutil_strcopy( ptr, b64.bv_val );
}
if ( ldif < 2 ) {
tool_write_ldif( ldif ? LDIF_PUT_COMMENT : LDIF_PUT_VALUE,
"control", str, len );
}
free( str );
if ( b64.bv_len ) {
ber_memfree( b64.bv_val );
}
/* known controls */
for ( j = 0; tool_ctrl_response[j].oid != NULL; j++ ) {
if ( strcmp( tool_ctrl_response[j].oid, ctrls[i]->ldctl_oid ) == 0 ) {
if ( !(tool_ctrl_response[j].mask & tool_type )) {
/* this control should not appear
* with this tool; warning? */
}
break;
}
}
if ( tool_ctrl_response[j].oid != NULL && tool_ctrl_response[j].func ) {
(void)tool_ctrl_response[j].func( ld, ctrls[i] );
}
}
}
int
tool_write_ldif( int type, char *name, char *value, ber_len_t vallen )
{
char *ldif;
if (( ldif = ldif_put_wrap( type, name, value, vallen, ldif_wrap )) == NULL ) {
return( -1 );
}
fputs( ldif, stdout );
ber_memfree( ldif );
return( 0 );
}
int
tool_is_oid( const char *s )
{
int first = 1;
if ( !isdigit( (unsigned char) s[ 0 ] ) ) {
return 0;
}
for ( ; s[ 0 ]; s++ ) {
if ( s[ 0 ] == '.' ) {
if ( s[ 1 ] == '\0' ) {
return 0;
}
first = 1;
continue;
}
if ( !isdigit( (unsigned char) s[ 0 ] ) ) {
return 0;
}
if ( first == 1 && s[ 0 ] == '0' && s[ 1 ] != '.' ) {
return 0;
}
first = 0;
}
return 1;
}