]> git.sur5r.net Git - openldap/blobdiff - clients/tools/ldapmodify.c
Notice/Acknowledge updates
[openldap] / clients / tools / ldapmodify.c
index d9985939fb2f0f3e240a8af4f80463170e3b0a02..d323970e7dff3c50523e60978e93d7dc1c3db2ee 100644 (file)
@@ -1,22 +1,50 @@
+/* ldapmodify.c - generic program to modify or add entries using LDAP */
 /* $OpenLDAP$ */
-/*
- * Copyright 1998-1999 The OpenLDAP Foundation, All Rights Reserved.
- * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2003 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * Portions Copyright 1998-2001 Net Boolean Incorporated.
+ * Portions Copyright 2001-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>.
+ */
+/* Portions Copyright (c) 1992-1996 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.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by the University of Michigan
+ * (as part of U-MICH LDAP).  Additional significant contributors
+ * include:
+ *   Kurt D. Zeilenga
  */
-/* ldapmodify.c - generic program to modify or add entries using LDAP */
 
 #include "portable.h"
 
 #include <stdio.h>
 
 #include <ac/stdlib.h>
-
 #include <ac/ctype.h>
-#include <ac/signal.h>
 #include <ac/string.h>
 #include <ac/unistd.h>
 
+#ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
+#endif
 
 #ifdef HAVE_SYS_FILE_H
 #include <sys/file.h>
 #include <fcntl.h>
 #endif
 
-#include <lber.h>
 #include <ldap.h>
 
+#include "lutil.h"
+#include "lutil_ldap.h"
 #include "ldif.h"
 #include "ldap_defaults.h"
+#include "ldap_log.h"
+#include "ldap_pvt.h"
+
+#include "common.h"
 
-static char    *prog;
-static char    *binddn = NULL;
-static char    *passwd = NULL;
-static char    *ldaphost = NULL;
-static int     ldapport = 0;
-static int     new, replace, not, verbose, contoper, force, valsfromfiles;
-static LDAP    *ld;
+
+static int     ldapadd, force = 0;
+static char *rejfile = NULL;
+static LDAP    *ld = NULL;
 
 #define LDAPMOD_MAXLINE                4096
 
@@ -45,6 +75,7 @@ static LDAP   *ld;
 #define T_VERSION_STR          "version"
 #define T_REPLICA_STR          "replica"
 #define T_DN_STR               "dn"
+#define T_CONTROL_STR          "control"
 #define T_CHANGETYPESTR         "changetype"
 #define T_ADDCTSTR             "add"
 #define T_MODIFYCTSTR          "modify"
@@ -55,329 +86,253 @@ static LDAP       *ld;
 #define T_MODOPADDSTR          "add"
 #define T_MODOPREPLACESTR      "replace"
 #define T_MODOPDELETESTR       "delete"
+#define T_MODOPINCREMENTSTR    "increment"
 #define T_MODSEPSTR            "-"
 #define T_NEWRDNSTR            "newrdn"
 #define T_DELETEOLDRDNSTR      "deleteoldrdn"
 #define T_NEWSUPSTR            "newsuperior"
 
 
-static void usage LDAP_P(( const char *prog )) LDAP_GCCATTR((noreturn));
-static int process_ldapmod_rec LDAP_P(( char *rbuf ));
 static int process_ldif_rec LDAP_P(( char *rbuf, int count ));
-static void addmodifyop LDAP_P(( LDAPMod ***pmodsp, int modop, char *attr,
-       char *value, int vlen ));
-static int domodify LDAP_P(( char *dn, LDAPMod **pmods, int newentry ));
-static int dodelete LDAP_P(( char *dn ));
-static int domodrdn LDAP_P(( char *dn, char *newrdn, int deleteoldrdn ));
-static int fromfile LDAP_P(( char *path, struct berval *bv ));
+static int parse_ldif_control LDAP_P(( char *line, LDAPControl ***pctrls ));
+static void addmodifyop LDAP_P((
+       LDAPMod ***pmodsp, int modop,
+       const char *attr,
+       struct berval *value ));
+static int domodify LDAP_P((
+       const char *dn,
+       LDAPMod **pmods,
+    LDAPControl **pctrls,
+       int newentry ));
+static int dodelete LDAP_P((
+       const char *dn,
+    LDAPControl **pctrls ));
+static int dorename LDAP_P((
+       const char *dn,
+       const char *newrdn,
+       const char *newsup,
+       int deleteoldrdn,
+    LDAPControl **pctrls ));
 static char *read_one_record LDAP_P(( FILE *fp ));
 
-static void
-usage( const char *prog )
+void
+usage( void )
 {
-    fprintf( stderr,
-                "Add or modify entries from an LDAP server\n\n"
-            "usage: %s [-abcknrvF] [-M[M]] [-d debug-level] [-P version] [-h ldaphost]\n"
-            "            [-p ldapport] [-D binddn] [-w passwd] [ -f file | < entryfile ]\n"
-            "       a    - add values (default%s)\n"
-            "       b    - read values from files (for binary attributes)\n"
-            "       c    - continuous operation\n"
-            "       D    - bind DN\n"
-                "       M    - enable Manage DSA IT control (-MM for critical)\n"
-            "       d    - debug level\n"
-            "       f    - read from file\n"
-            "       F    - force all changes records to be used\n"
-            "       h    - ldap host\n"
-            "       n    - print adds, don't actually do them\n"
-            "       p    - LDAP port\n"
-            "       r    - replace values\n"
-            "       v    - verbose mode\n"
-            "       w    - password\n"
-            , prog, (strcmp( prog, "ldapadd" ) ? " is to replace" : "") );
+    fprintf( stderr, _("Add or modify entries from an LDAP server\n\n"));
+    fprintf( stderr, _("usage: %s [options]\n"), prog);
+    fprintf( stderr, _("       The list of desired operations are read from stdin or from the file\n"));
+    fprintf( stderr, _("       specified by \"-f file\".\n"));
+    fprintf( stderr, _("Add or modify options:\n"));
+    fprintf( stderr, _("  -a         add values (%s)\n"),
+               (ldapadd ? _("default") : _("default is to replace")));
+    fprintf( stderr, _("  -F         force all changes records to be used\n"));
+    fprintf( stderr, _("  -S file    write skipped modifications to `file'\n"));
+    tool_common_usage();
     exit( EXIT_FAILURE );
 }
 
 
+const char options[] = "aFrS:"
+       "cCd:D:e:f:h:H:IkKMnO:p:P:QR:U:vVw:WxX:y:Y:Z";
+
 int
-main( int argc, char **argv )
+handle_private_option( int i )
 {
-    char               *infile, *rbuf, *start, *p, *q;
-    FILE               *fp;
-       int             rc, i, use_ldif, authmethod, version, want_bindpw, debug, manageDSAit;
-       int count;
-
-    if (( prog = strrchr( argv[ 0 ], *LDAP_DIRSEP )) == NULL ) {
-       prog = argv[ 0 ];
-    } else {
-       ++prog;
-    }
+       switch ( i ) {
+#if 0
+               char    *control, *cvalue;
+               int             crit;
+       case 'E': /* modify controls */
+               if( protocol == LDAP_VERSION2 ) {
+                       fprintf( stderr, _("%s: -E incompatible with LDAPv%d\n"),
+                               prog, protocol );
+                       exit( EXIT_FAILURE );
+               }
 
-    /* Print usage when no parameters */
-    if( argc < 2 )
-       usage( prog );
+               /* should be extended to support comma separated list of
+                *      [!]key[=value] parameters, e.g.  -E !foo,bar=567
+                */
 
-    new = ( strcmp( prog, "ldapadd" ) == 0 );
+               crit = 0;
+               cvalue = NULL;
+               if( optarg[0] == '!' ) {
+                       crit = 1;
+                       optarg++;
+               }
 
-    infile = NULL;
-    not = verbose = valsfromfiles = want_bindpw = debug = manageDSAit = 0;
-    authmethod = LDAP_AUTH_SIMPLE;
-       version = -1;
+               control = ber_strdup( optarg );
+               if ( (cvalue = strchr( control, '=' )) != NULL ) {
+                       *cvalue++ = '\0';
+               }
+               fprintf( stderr, _("Invalid modify control name: %s\n"), control );
+               usage();
+#endif
 
-    while (( i = getopt( argc, argv, "WFMabckKnrtvh:p:D:w:d:f:P:" )) != EOF ) {
-       switch( i ) {
        case 'a':       /* add */
-           new = 1;
-           break;
-       case 'b':       /* read values from files (for binary attributes) */
-           valsfromfiles = 1;
-           break;
-       case 'c':       /* continuous operation */
-           contoper = 1;
-           break;
-       case 'r':       /* default is to replace rather than add values */
-           replace = 1;
-           break;
-       case 'k':       /* kerberos bind */
-#ifdef LDAP_API_FEATURE_X_OPENLDAP_V2_KBIND
-               authmethod = LDAP_AUTH_KRBV4;
-#else
-               fprintf (stderr, "%s was not compiled with Kerberos support\n", argv[0]);
-               usage( argv[0] );
-               return( EXIT_FAILURE );
-#endif
-           break;
-       case 'K':       /* kerberos bind, part 1 only */
-#ifdef LDAP_API_FEATURE_X_OPENLDAP_V2_KBIND
-               authmethod = LDAP_AUTH_KRBV41;
-#else
-               fprintf (stderr, "%s was not compiled with Kerberos support\n", argv[0]);
-               usage( argv[0] );
-               return( EXIT_FAILURE );
-#endif
+           ldapadd = 1;
            break;
+
        case 'F':       /* force all changes records to be used */
            force = 1;
            break;
-       case 'h':       /* ldap host */
-           ldaphost = strdup( optarg );
-           break;
-       case 'D':       /* bind DN */
-           binddn = strdup( optarg );
-           break;
-       case 'w':       /* password */
-           passwd = strdup( optarg );
-               {
-                       char* p;
 
-                       for( p = optarg; *p == '\0'; p++ ) {
-                               *p = '*';
-                       }
-               }
-           break;
-       case 'd':
-           debug |= atoi( optarg );
-           break;
-       case 'f':       /* read from file */
-           infile = strdup( optarg );
-           break;
-       case 'p':
-           ldapport = atoi( optarg );
-           break;
-       case 'n':       /* print adds, don't actually do them */
-           ++not;
-           break;
-       case 'v':       /* verbose mode */
-           verbose++;
-           break;
-       case 'M':
-               /* enable Manage DSA IT */
-               manageDSAit++;
+       case 'r':       /* replace (obsolete) */
                break;
-       case 'W':
-               want_bindpw++;
-               break;
-       case 'P':
-               switch( atoi(optarg) )
-               {
-               case 2:
-                       version = LDAP_VERSION2;
-                       break;
-               case 3:
-                       version = LDAP_VERSION3;
-                       break;
-               default:
-                       fprintf( stderr, "protocol version should be 2 or 3\n" );
-                       usage( argv[0] );
+
+       case 'S':       /* skipped modifications to file */
+               if( rejfile != NULL ) {
+                       fprintf( stderr, _("%s: -S previously specified\n"), prog );
+                       exit( EXIT_FAILURE );
                }
+               rejfile = ber_strdup( optarg );
                break;
+
        default:
-           usage( prog );
+               return 0;
        }
-    }
+       return 1;
+}
 
-    if ( argc != optind )
-       usage( prog );
 
-       if( authmethod != LDAP_AUTH_SIMPLE ) {
-               if( version == LDAP_VERSION3 ) {
-                       fprintf(stderr, "Kerberos requires LDAPv2\n");
-                       return EXIT_FAILURE;
-               }
-               version = LDAP_VERSION2;
-       }
+int
+main( int argc, char **argv )
+{
+       char            *rbuf, *start, *rejbuf = NULL;
+       FILE            *fp, *rejfp;
+       char            *matched_msg, *error_msg;
+       int             rc, retval;
+       int count, len;
 
-       if( manageDSAit ) {
-               if( version == LDAP_VERSION2 ) {
-                       fprintf(stderr, "manage DSA control requires LDAPv3\n");
-                       return EXIT_FAILURE;
-               }
-               version = LDAP_VERSION3;
-       }
+       tool_init();
+       prog = lutil_progname( "ldapmodify", argc, argv );
 
-    if ( infile != NULL ) {
-       if (( fp = fopen( infile, "r" )) == NULL ) {
-           perror( infile );
-           return( EXIT_FAILURE );
-       }
-    } else {
-       fp = stdin;
-    }
+       /* strncmp instead of strcmp since NT binaries carry .exe extension */
+       ldapadd = ( strncasecmp( prog, "ldapadd", sizeof("ldapadd")-1 ) == 0 );
 
-       if ( 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 );
-               }
-               ldif_debug = debug;
-       }
+       /* Print usage when no parameters */
+       if( argc < 2 ) usage();
 
-#ifdef SIGPIPE
-       (void) SIGNAL( SIGPIPE, SIG_IGN );
-#endif
+       tool_args( argc, argv );
 
-    if ( !not ) {
-       if (( ld = ldap_init( ldaphost, ldapport )) == NULL ) {
-           perror( "ldap_init" );
-           return( EXIT_FAILURE );
-       }
+       if ( argc != optind ) usage();
 
-       /* this seems prudent */
-       {
-               int deref = LDAP_DEREF_NEVER;
-               ldap_set_option( ld, LDAP_OPT_DEREF, &deref);
+       if ( rejfile != NULL ) {
+               if (( rejfp = fopen( rejfile, "w" )) == NULL ) {
+                       perror( rejfile );
+                       return( EXIT_FAILURE );
+               }
+       } else {
+               rejfp = NULL;
        }
-       /* don't chase referrals */
-       ldap_set_option( ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF );
 
-       if (version != -1 &&
-               ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version ) != LDAP_OPT_SUCCESS)
-       {
-               fprintf( stderr, "Could not set LDAP_OPT_PROTOCOL_VERSION to %d\n", version );
+       if ( infile != NULL ) {
+               if (( fp = fopen( infile, "r" )) == NULL ) {
+                       perror( infile );
+                       return( EXIT_FAILURE );
+               }
+       } else {
+               fp = stdin;
        }
 
-       if (want_bindpw)
-               passwd = getpass("Enter LDAP Password: ");
+       if ( debug ) ldif_debug = debug;
+
+       ld = tool_conn_setup( not, 0 );
 
-       if ( ldap_bind_s( ld, binddn, passwd, authmethod ) != LDAP_SUCCESS ) {
-           ldap_perror( ld, "ldap_bind" );
-           return( EXIT_FAILURE );
+       if ( !not ) {
+               if ( pw_file || want_bindpw ) {
+                       if ( pw_file ) {
+                               rc = lutil_get_filed_password( pw_file, &passwd );
+                               if( rc ) return EXIT_FAILURE;
+                       } else {
+                               passwd.bv_val = getpassphrase( _("Enter LDAP Password: ") );
+                               passwd.bv_len = passwd.bv_val ? strlen( passwd.bv_val ) : 0;
+                       }
+               }
+               tool_bind( ld );
        }
-    }
 
     rc = 0;
 
-       if ( manageDSAit ) {
-               int err;
-               LDAPControl c;
-               LDAPControl *ctrls[2];
-               ctrls[0] = &c;
-               ctrls[1] = NULL;
+       if ( assertion || authzid || manageDSAit || noop || preread || postread ) {
+               tool_server_controls( ld, NULL, 0 );
+       }
 
-               c.ldctl_oid = LDAP_CONTROL_MANAGEDSAIT;
-               c.ldctl_value.bv_val = NULL;
-               c.ldctl_value.bv_len = 0;
-               c.ldctl_iscritical = manageDSAit > 1;
+       count = 0;
+       retval = 0;
+       while (( rc == 0 || contoper ) &&
+               ( rbuf = read_one_record( fp )) != NULL )
+       {
+               count++;
 
-               err = ldap_set_option( ld, LDAP_OPT_SERVER_CONTROLS, &ctrls );
+               start = rbuf;
 
-               if( err != LDAP_OPT_SUCCESS ) {
-                       fprintf( stderr, "Could not set Manage DSA IT Control\n" );
-                       if( c.ldctl_iscritical ) {
+               if ( rejfp ) {
+                       len = strlen( rbuf );
+                       if (( rejbuf = (char *)ber_memalloc( len+1 )) == NULL ) {
+                               perror( "malloc" );
                                exit( EXIT_FAILURE );
                        }
+                       memcpy( rejbuf, rbuf, len+1 );
                }
-       }
 
-       count = 0;
-    while (( rc == 0 || contoper ) &&
-               ( rbuf = read_one_record( fp )) != NULL ) {
-       count++;
-       /*
-        * we assume record is ldif/slapd.replog if the first line
-        * has a colon that appears to the left of any equal signs, OR
-        * if the first line consists entirely of digits (an entry id)
-        */
-#ifdef LDAP_LDIF
-       use_ldif = 1;
-#else
-       use_ldif = ( *rbuf == '#' ) ||
-               (( p = strchr( rbuf, ':' )) != NULL &&
-               (  q = strchr( rbuf, '\n' )) != NULL && p < q &&
-               (( q = strchr( rbuf, '=' )) == NULL || p < q ));
-#endif
+               rc = process_ldif_rec( start, count );
 
-       start = rbuf;
+               if ( rc ) retval = rc;
+               if ( rc && rejfp ) {
+                       fprintf(rejfp, _("# Error: %s (%d)"), ldap_err2string(rc), rc);
 
-       if ( !use_ldif && ( q = strchr( rbuf, '\n' )) != NULL ) {
-           for ( p = rbuf; p < q; ++p ) {
-               if ( !isdigit( (unsigned char) *p )) {
-                   break;
+                       matched_msg = NULL;
+                       ldap_get_option(ld, LDAP_OPT_MATCHED_DN, &matched_msg);
+                       if ( matched_msg != NULL ) {
+                               if ( *matched_msg != '\0' ) {
+                                       fprintf( rejfp, _(", matched DN: %s"), matched_msg );
+                               }
+                               ldap_memfree( matched_msg );
+                       }
+
+                       error_msg = NULL;
+                       ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &error_msg);
+                       if ( error_msg != NULL ) {
+                               if ( *error_msg != '\0' ) {
+                                       fprintf( rejfp, _(", additional info: %s"), error_msg );
+                               }
+                               ldap_memfree( error_msg );
+                       }
+                       fprintf( rejfp, "\n%s\n", rejbuf );
                }
-           }
-           if ( p >= q ) {
-               use_ldif = 1;
-               start = q + 1;
-           }
-       }
 
-       if ( use_ldif ) {
-           rc = process_ldif_rec( start, count );
-       } else {
-           rc = process_ldapmod_rec( start );
+               if (rejfp) free( rejbuf );
+               free( rbuf );
        }
 
-       if( rc )
-           fprintf( stderr, "%s() = %d\n",
-                    use_ldif ? "ldif_rec" : "ldapmod_rec" , rc );
-
-       free( rbuf );
-    }
+       if ( !not ) {
+               ldap_unbind( ld );
+       }
 
-    if ( !not ) {
-       ldap_unbind( ld );
-    }
+       if ( rejfp != NULL ) {
+               fclose( rejfp );
+       }
 
-       return( rc );
+       return( retval );
 }
 
 
 static int
 process_ldif_rec( char *rbuf, int count )
 {
-    char       *line, *dn, *type, *value, *newrdn, *newsup, *p;
+    char       *line, *dn, *type, *newrdn, *newsup, *p;
     int                rc, linenum, modop, replicaport;
-       ber_len_t vlen;
     int                expect_modop, expect_sep, expect_ct, expect_newrdn, expect_newsup;
     int                expect_deleteoldrdn, deleteoldrdn;
     int                saw_replica, use_record, new_entry, delete_entry, got_all;
     LDAPMod    **pmods;
        int version;
+       struct berval val;
+    LDAPControl **pctrls;
 
-    new_entry = new;
+    new_entry = ldapadd;
 
-    rc = got_all = saw_replica = delete_entry = expect_modop = 0;
+    rc = got_all = saw_replica = delete_entry = modop = expect_modop = 0;
     expect_deleteoldrdn = expect_newrdn = expect_newsup = 0;
        expect_sep = expect_ct = 0;
     linenum = 0;
@@ -385,6 +340,7 @@ process_ldif_rec( char *rbuf, int count )
     deleteoldrdn = 1;
     use_record = force;
     pmods = NULL;
+    pctrls = NULL;
     dn = newrdn = newsup = NULL;
 
     while ( rc == 0 && ( line = ldif_getline( &rbuf )) != NULL ) {
@@ -396,8 +352,8 @@ process_ldif_rec( char *rbuf, int count )
            continue;
        }
        
-       if ( ldif_parse_line( line, &type, &value, &vlen ) < 0 ) {
-           fprintf( stderr, "%s: invalid format (line %d) entry: \"%s\"\n",
+       if ( ldif_parse_line( line, &type, &val.bv_val, &val.bv_len ) < 0 ) {
+           fprintf( stderr, _("%s: invalid format (line %d) entry: \"%s\"\n"),
                    prog, linenum, dn == NULL ? "" : dn );
            rc = LDAP_PARAM_ERROR;
            break;
@@ -406,27 +362,27 @@ process_ldif_rec( char *rbuf, int count )
        if ( dn == NULL ) {
            if ( !use_record && strcasecmp( type, T_REPLICA_STR ) == 0 ) {
                ++saw_replica;
-               if (( p = strchr( value, ':' )) == NULL ) {
+               if (( p = strchr( val.bv_val, ':' )) == NULL ) {
                    replicaport = 0;
                } else {
                    *p++ = '\0';
                    replicaport = atoi( p );
                }
-               if ( ldaphost != NULL && strcasecmp( value, ldaphost ) == 0 &&
+               if ( ldaphost != NULL && strcasecmp( val.bv_val, ldaphost ) == 0 &&
                        replicaport == ldapport ) {
                    use_record = 1;
                }
            } else if ( count == 1 && linenum == 1 && 
                        strcasecmp( type, T_VERSION_STR ) == 0 )
                {
-                       if( vlen == 0 || atoi(value) != 1 ) {
-                       fprintf( stderr, "%s: invalid version %s, line %d (ignored)\n",
-                               prog, value == NULL ? "(null)" : value, linenum );
+                       if( val.bv_len == 0 || atoi(val.bv_val) != 1 ) {
+                       fprintf( stderr, _("%s: invalid version %s, line %d (ignored)\n"),
+                               prog, val.bv_val, linenum );
                        }
                        version++;
 
            } else if ( strcasecmp( type, T_DN_STR ) == 0 ) {
-               if (( dn = strdup( value ? value : "" )) == NULL ) {
+               if (( dn = ber_strdup( val.bv_val )) == NULL ) {
                    perror( "strdup" );
                    exit( EXIT_FAILURE );
                }
@@ -436,38 +392,66 @@ process_ldif_rec( char *rbuf, int count )
        }
 
        if ( expect_ct ) {
+        
+        /* Check for "control" tag after dn and before changetype. */
+        if (strcasecmp(type, T_CONTROL_STR) == 0) {
+            /* Parse and add it to the list of controls */
+            rc = parse_ldif_control( line, &pctrls );
+            if (rc != 0) {
+                       fprintf( stderr, _("%s: Error processing %s line, line %d: %s\n"),
+                               prog, T_CONTROL_STR, linenum, ldap_err2string(rc) );
+            }
+            goto end_line;
+        }
+        
            expect_ct = 0;
            if ( !use_record && saw_replica ) {
-               printf( "%s: skipping change record for entry: %s\n"
-                       "\t(LDAP host/port does not match replica: lines)\n",
-                       prog, dn );
+               printf(_("%s: skipping change record for entry: %s\n"), prog, dn);
+               printf(_("\t(LDAP host/port does not match replica: lines)\n"));
                free( dn );
                ber_memfree( type );
-               ber_memfree( value );
+               ber_memfree( val.bv_val );
                return( 0 );
            }
 
            if ( strcasecmp( type, T_CHANGETYPESTR ) == 0 ) {
-               if ( strcasecmp( value, T_MODIFYCTSTR ) == 0 ) {
+#ifdef LIBERAL_CHANGETYPE_MODOP
+               /* trim trailing spaces (and log warning ...) */
+
+               int icnt;
+               for ( icnt = val.bv_len; --icnt > 0; ) {
+                   if ( !isspace( (unsigned char) val.bv_val[icnt] ) ) {
+                       break;
+                   }
+               }
+
+               if ( ++icnt != val.bv_len ) {
+                   fprintf( stderr, _("%s: illegal trailing space after \"%s: %s\" trimmed (line %d of entry \"%s\")\n"),
+                           prog, T_CHANGETYPESTR, val.bv_val, linenum, dn );
+                   val.bv_val[icnt] = '\0';
+               }
+#endif /* LIBERAL_CHANGETYPE_MODOP */
+
+               if ( strcasecmp( val.bv_val, T_MODIFYCTSTR ) == 0 ) {
                        new_entry = 0;
                        expect_modop = 1;
-               } else if ( strcasecmp( value, T_ADDCTSTR ) == 0 ) {
+               } else if ( strcasecmp( val.bv_val, T_ADDCTSTR ) == 0 ) {
                        new_entry = 1;
-               } else if ( strcasecmp( value, T_MODRDNCTSTR ) == 0
-                       || strcasecmp( value, T_MODDNCTSTR ) == 0
-                       || strcasecmp( value, T_RENAMECTSTR ) == 0)
+               } else if ( strcasecmp( val.bv_val, T_MODRDNCTSTR ) == 0
+                       || strcasecmp( val.bv_val, T_MODDNCTSTR ) == 0
+                       || strcasecmp( val.bv_val, T_RENAMECTSTR ) == 0)
                {
                    expect_newrdn = 1;
-               } else if ( strcasecmp( value, T_DELETECTSTR ) == 0 ) {
+               } else if ( strcasecmp( val.bv_val, T_DELETECTSTR ) == 0 ) {
                    got_all = delete_entry = 1;
                } else {
                    fprintf( stderr,
-                           "%s:  unknown %s \"%s\" (line %d of entry \"%s\")\n",
-                           prog, T_CHANGETYPESTR, value, linenum, dn );
+                           _("%s:  unknown %s \"%s\" (line %d of entry \"%s\")\n"),
+                           prog, T_CHANGETYPESTR, val.bv_val, linenum, dn );
                    rc = LDAP_PARAM_ERROR;
                }
                goto end_line;
-           } else if ( new ) {         /*  missing changetype => add */
+           } else if ( ldapadd ) {             /*  missing changetype => add */
                new_entry = 1;
                modop = LDAP_MOD_ADD;
            } else {
@@ -476,6 +460,23 @@ process_ldif_rec( char *rbuf, int count )
        }
 
        if ( expect_modop ) {
+#ifdef LIBERAL_CHANGETYPE_MODOP
+           /* trim trailing spaces (and log warning ...) */
+           
+           int icnt;
+           for ( icnt = val.bv_len; --icnt > 0; ) {
+               if ( !isspace( (unsigned char) val.bv_val[icnt] ) ) {
+                   break;
+               }
+           }
+           
+           if ( ++icnt != val.bv_len ) {
+               fprintf( stderr, _("%s: illegal trailing space after \"%s: %s\" trimmed (line %d of entry \"%s\")\n"),
+                       prog, type, val.bv_val, linenum, dn );
+               val.bv_val[icnt] = '\0';
+           }
+#endif /* LIBERAL_CHANGETYPE_MODOP */
+
            expect_modop = 0;
            expect_sep = 1;
            if ( strcasecmp( type, T_MODOPADDSTR ) == 0 ) {
@@ -483,65 +484,69 @@ process_ldif_rec( char *rbuf, int count )
                goto end_line;
            } else if ( strcasecmp( type, T_MODOPREPLACESTR ) == 0 ) {
                modop = LDAP_MOD_REPLACE;
-               addmodifyop( &pmods, modop, value, NULL, 0 );
+               addmodifyop( &pmods, modop, val.bv_val, NULL );
                goto end_line;
            } else if ( strcasecmp( type, T_MODOPDELETESTR ) == 0 ) {
                modop = LDAP_MOD_DELETE;
-               addmodifyop( &pmods, modop, value, NULL, 0 );
+               addmodifyop( &pmods, modop, val.bv_val, NULL );
+               goto end_line;
+           } else if ( strcasecmp( type, T_MODOPINCREMENTSTR ) == 0 ) {
+               modop = LDAP_MOD_INCREMENT;
+               addmodifyop( &pmods, modop, val.bv_val, NULL );
                goto end_line;
            } else {    /* no modify op:  use default */
-               modop = replace ? LDAP_MOD_REPLACE : LDAP_MOD_ADD;
+               modop = ldapadd ? LDAP_MOD_ADD : LDAP_MOD_REPLACE;
            }
        }
 
        if ( expect_newrdn ) {
            if ( strcasecmp( type, T_NEWRDNSTR ) == 0 ) {
-               if (( newrdn = strdup( value )) == NULL ) {
+                       if (( newrdn = ber_strdup( val.bv_val )) == NULL ) {
                    perror( "strdup" );
                    exit( EXIT_FAILURE );
                }
                expect_deleteoldrdn = 1;
                expect_newrdn = 0;
            } else {
-               fprintf( stderr, "%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry \"%s\")\n",
+               fprintf( stderr, _("%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry \"%s\")\n"),
                        prog, T_NEWRDNSTR, type, linenum, dn );
                rc = LDAP_PARAM_ERROR;
            }
        } else if ( expect_deleteoldrdn ) {
            if ( strcasecmp( type, T_DELETEOLDRDNSTR ) == 0 ) {
-               deleteoldrdn = ( *value == '0' ) ? 0 : 1;
+               deleteoldrdn = ( *val.bv_val == '0' ) ? 0 : 1;
                expect_deleteoldrdn = 0;
                expect_newsup = 1;
                got_all = 1;
            } else {
-               fprintf( stderr, "%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry \"%s\")\n",
+               fprintf( stderr, _("%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry \"%s\")\n"),
                        prog, T_DELETEOLDRDNSTR, type, linenum, dn );
                rc = LDAP_PARAM_ERROR;
            }
        } else if ( expect_newsup ) {
            if ( strcasecmp( type, T_NEWSUPSTR ) == 0 ) {
-               if (( newsup = strdup( value )) == NULL ) {
+               if (( newsup = ber_strdup( val.bv_val )) == NULL ) {
                    perror( "strdup" );
                    exit( EXIT_FAILURE );
                }
                expect_newsup = 0;
            } else {
-               fprintf( stderr, "%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry \"%s\")\n",
+               fprintf( stderr, _("%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry \"%s\")\n"),
                        prog, T_NEWSUPSTR, type, linenum, dn );
                rc = LDAP_PARAM_ERROR;
            }
        } else if ( got_all ) {
            fprintf( stderr,
-                   "%s: extra lines at end (line %d of entry \"%s\")\n",
+                   _("%s: extra lines at end (line %d of entry \"%s\")\n"),
                    prog, linenum, dn );
            rc = LDAP_PARAM_ERROR;
        } else {
-           addmodifyop( &pmods, modop, type, value, vlen );
+               addmodifyop( &pmods, modop, type, &val );
        }
 
 end_line:
        ber_memfree( type );
-       ber_memfree( value );
+       ber_memfree( val.bv_val );
     }
 
        if( linenum == 0 ) {
@@ -552,13 +557,50 @@ end_line:
                return 0;
        }
 
+    /* If default controls are set (as with -M option) and controls are
+       specified in the LDIF file, we must add the default controls to
+       the list of controls sent with the ldap operation.
+    */
+    if ( rc == 0 ) {
+        if (pctrls) {
+            LDAPControl **defctrls = NULL;   /* Default server controls */
+            LDAPControl **newctrls = NULL;
+            ldap_get_option(ld, LDAP_OPT_SERVER_CONTROLS, &defctrls);
+            if (defctrls) {
+                int npc=0;                  /* Number of LDIF controls */
+                int ndefc=0;                /* Number of default controls */
+                while (pctrls[npc])         /* Count LDIF controls */
+                    npc++; 
+                while (defctrls[ndefc])     /* Count default controls */
+                    ndefc++;
+                newctrls = ber_memrealloc(pctrls, (npc+ndefc+1)*sizeof(LDAPControl*));
+                if (newctrls == NULL)
+                    rc = LDAP_NO_MEMORY;
+                else {
+                    int i;
+                    pctrls = newctrls;
+                    for (i=npc; i<npc+ndefc; i++) {
+                        pctrls[i] = ldap_control_dup(defctrls[i-npc]);
+                        if (pctrls[i] == NULL) {
+                            rc = LDAP_NO_MEMORY;
+                            break;
+                        }
+                    }
+                    pctrls[npc+ndefc] = NULL;
+                }
+                ldap_controls_free(defctrls);  /* Must be freed by library */
+            }
+        }
+    }
+
+
     if ( rc == 0 ) {
        if ( delete_entry ) {
-           rc = dodelete( dn );
+           rc = dodelete( dn, pctrls );
        } else if ( newrdn != NULL ) {
-           rc = domodrdn( dn, newrdn, deleteoldrdn );
+           rc = dorename( dn, newrdn, newsup, deleteoldrdn, pctrls );
        } else {
-           rc = domodify( dn, pmods, new_entry );
+           rc = domodify( dn, pmods, pctrls, new_entry );
        }
 
        if ( rc == LDAP_SUCCESS ) {
@@ -576,215 +618,261 @@ end_line:
        ldap_mods_free( pmods, 1 );
     }
 
+    if (pctrls != NULL) {
+        ldap_controls_free( pctrls );
+    }
+
     return( rc );
 }
 
-
+/* Parse an LDIF control line of the form
+      control:  oid  [true/false]  [: value]              or
+      control:  oid  [true/false]  [:: base64-value]      or
+      control:  oid  [true/false]  [:< url]
+   The control is added to the list of controls in *ppctrls.
+*/      
 static int
-process_ldapmod_rec( char *rbuf )
+parse_ldif_control( char *line, 
+                    LDAPControl ***ppctrls )
 {
-    char       *line, *dn, *p, *q, *attr, *value;
-    int                rc, linenum, modop;
-    LDAPMod    **pmods;
-
-    pmods = NULL;
-    dn = NULL;
-    linenum = 0;
-    line = rbuf;
-    rc = 0;
-
-    while ( rc == 0 && rbuf != NULL && *rbuf != '\0' ) {
-       ++linenum;
-       if (( p = strchr( rbuf, '\n' )) == NULL ) {
-           rbuf = NULL;
-       } else {
-           if ( *(p-1) == '\\' ) {     /* lines ending in '\' are continued */
-               SAFEMEMCPY( p - 1, p, strlen( p ) + 1 );
-               rbuf = p;
-               continue;
-           }
-           *p++ = '\0';
-           rbuf = p;
-       }
-
-       if ( dn == NULL ) {     /* first line contains DN */
-           if (( dn = strdup( line )) == NULL ) {
-               perror( "strdup" );
-               exit( EXIT_FAILURE );
-           }
-       } else {
-           if (( p = strchr( line, '=' )) == NULL ) {
-               value = NULL;
-               p = line + strlen( line );
-           } else {
-               *p++ = '\0';
-               value = p;
-           }
-
-           for ( attr = line;
-                 *attr != '\0' && isspace( (unsigned char) *attr ); ++attr ) {
-               ;       /* skip attribute leading white space */
-           }
-
-           for ( q = p - 1; q > attr && isspace( (unsigned char) *q ); --q ) {
-               *q = '\0';      /* remove attribute trailing white space */
-           }
-
-           if ( value != NULL ) {
-               while ( isspace( (unsigned char) *value )) {
-                   ++value;            /* skip value leading white space */
-               }
-               for ( q = value + strlen( value ) - 1; q > value &&
-                       isspace( (unsigned char) *q ); --q ) {
-                   *q = '\0';  /* remove value trailing white space */
-               }
-               if ( *value == '\0' ) {
-                   value = NULL;
-               }
-
-           }
-
-           if ( value == NULL && new ) {
-               fprintf( stderr, "%s: missing value on line %d (attr=\"%s\")\n",
-                       prog, linenum, attr );
-               rc = LDAP_PARAM_ERROR;
-           } else {
-                switch ( *attr ) {
-               case '-':
-                   modop = LDAP_MOD_DELETE;
-                   ++attr;
-                   break;
-               case '+':
-                   modop = LDAP_MOD_ADD;
-                   ++attr;
-                   break;
-               default:
-                   modop = replace ? LDAP_MOD_REPLACE : LDAP_MOD_ADD;
-               }
-
-               addmodifyop( &pmods, modop, attr, value,
-                       ( value == NULL ) ? 0 : strlen( value ));
-           }
-       }
+    char *oid = NULL;
+    int criticality = 0;   /* Default is false if not present */
+    char *type=NULL;
+    char *val = NULL;
+    ber_len_t value_len = 0;
+    int i, rc=0;
+    char *s, *oidStart, *pcolon;
+    LDAPControl *newctrl = NULL;
+    LDAPControl **pctrls = NULL;
+
+    if (ppctrls) {
+        pctrls = *ppctrls;
+    }
+    s = line + strlen(T_CONTROL_STR);  /* Skip over "control" */
+    pcolon = s;                        /* Save this position for later */
+    if (*s++ != ':')                   /* Make sure colon follows */
+        return ( LDAP_PARAM_ERROR );
+    while (*s && isspace((unsigned char)*s))
+               s++;                           /* Skip white space before OID */
+
+    /* OID should come next. Validate and extract it. */
+    if (*s == 0)
+        return ( LDAP_PARAM_ERROR );
+    oidStart = s;
+    while (isdigit((unsigned char)*s) || *s == '.')
+               s++;                           /* OID should be digits or . */
+    if (s == oidStart) 
+        return ( LDAP_PARAM_ERROR );   /* OID was not present */
+    if (*s) {                          /* End of OID should be space or NULL */
+        if (!isspace((unsigned char)*s))
+            return ( LDAP_PARAM_ERROR ); /* else OID contained invalid chars */
+        *s++ = 0;                    /* Replace space with null to terminate */
+    }
 
-       line = rbuf;
+    
+    oid = ber_strdup(oidStart);
+    if (oid == NULL)
+        return ( LDAP_NO_MEMORY );
+
+    /* Optional Criticality field is next. */
+    while (*s && isspace((unsigned char)*s))
+               s++;                         /* Skip white space before criticality */
+    if (strncasecmp(s, "true", 4) == 0) {
+        criticality = 1;
+        s += 4;
+    } 
+    else if (strncasecmp(s, "false", 5) == 0) {
+        criticality = 0;
+        s += 5;
     }
 
-    if ( rc == 0 ) {
-       if ( dn == NULL ) {
-           rc = LDAP_PARAM_ERROR;
-       } else if (( rc = domodify( dn, pmods, new )) == LDAP_SUCCESS ) {
-           rc = 0;
-       }
+    /* Optional value field is next */
+    while (*s && isspace((unsigned char)*s))
+               s++;                         /* Skip white space before value */
+    if (*s) {
+        if (*s != ':') {           /* If value is present, must start with : */
+            rc = LDAP_PARAM_ERROR;
+            goto cleanup;
+        }
+
+        /* Shift value down over OID and criticality so it's in the form
+             control: value
+             control:: base64-value
+             control:< url
+           Then we can use ldif_parse_line to extract and decode the value
+        */
+        while ( (*pcolon++ = *s++) != 0)     /* Shift value */
+            ;
+        rc = ldif_parse_line(line, &type, &val, &value_len);
+        if (type)  ber_memfree(type);   /* Don't need this field*/
+        if (rc < 0) {
+            rc = LDAP_PARAM_ERROR;
+            goto cleanup;
+        }
     }
 
-    if ( pmods != NULL ) {
-       ldap_mods_free( pmods, 1 );
+    /* Create a new LDAPControl structure. */
+    newctrl = (LDAPControl *)ber_memalloc(sizeof(LDAPControl));
+    if ( newctrl == NULL ) {
+        rc = LDAP_NO_MEMORY;
+        goto cleanup;
     }
-    if ( dn != NULL ) {
-       free( dn );
+    newctrl->ldctl_oid = oid;
+    oid = NULL;
+    newctrl->ldctl_iscritical = criticality;
+    newctrl->ldctl_value.bv_len = value_len;
+    newctrl->ldctl_value.bv_val = val;
+    val = NULL;
+
+    /* Add the new control to the passed-in list of controls. */
+    i = 0;
+    if (pctrls) {
+        while ( pctrls[i] )      /* Count the # of controls passed in */
+            i++;
     }
+    /* Allocate 1 more slot for the new control and 1 for the NULL. */
+    pctrls = (LDAPControl **)ber_memrealloc(pctrls, (i+2)*(sizeof(LDAPControl *)));
+    if (pctrls == NULL) {
+        rc = LDAP_NO_MEMORY;
+        goto cleanup;
+    }
+    pctrls[i] = newctrl;
+    newctrl = NULL;
+    pctrls[i+1] = NULL;
+    *ppctrls = pctrls;
+
+cleanup:
+    if (newctrl) {
+        if (newctrl->ldctl_oid)
+            ber_memfree(newctrl->ldctl_oid);
+        if (newctrl->ldctl_value.bv_val)
+            ber_memfree(newctrl->ldctl_value.bv_val);
+        ber_memfree(newctrl);
+    }
+    if (val)
+        ber_memfree(val);
+    if (oid)
+        ber_memfree(oid);
 
     return( rc );
 }
 
 
 static void
-addmodifyop( LDAPMod ***pmodsp, int modop, char *attr, char *value, int vlen )
+addmodifyop(
+       LDAPMod ***pmodsp,
+       int modop,
+       const char *attr,
+       struct berval *val )
 {
-    LDAPMod            **pmods;
-    int                        i, j;
-    struct berval      *bvp;
+       LDAPMod         **pmods;
+       int                     i, j;
+
+       pmods = *pmodsp;
+       modop |= LDAP_MOD_BVALUES;
+
+       i = 0;
+       if ( pmods != NULL ) {
+               for ( ; pmods[ i ] != NULL; ++i ) {
+                       if ( strcasecmp( pmods[ i ]->mod_type, attr ) == 0 &&
+                               pmods[ i ]->mod_op == modop )
+                       {
+                               break;
+                       }
+               }
+       }
 
-    pmods = *pmodsp;
-    modop |= LDAP_MOD_BVALUES;
+       if ( pmods == NULL || pmods[ i ] == NULL ) {
+               if (( pmods = (LDAPMod **)ber_memrealloc( pmods, (i + 2) *
+                       sizeof( LDAPMod * ))) == NULL )
+               {
+                       perror( "realloc" );
+                       exit( EXIT_FAILURE );
+               }
 
-    i = 0;
-    if ( pmods != NULL ) {
-       for ( ; pmods[ i ] != NULL; ++i ) {
-           if ( strcasecmp( pmods[ i ]->mod_type, attr ) == 0 &&
-                   pmods[ i ]->mod_op == modop ) {
-               break;
-           }
-       }
-    }
+               *pmodsp = pmods;
+               pmods[ i + 1 ] = NULL;
 
-    if ( pmods == NULL || pmods[ i ] == NULL ) {
-       if (( pmods = (LDAPMod **)ber_memrealloc( pmods, (i + 2) *
-               sizeof( LDAPMod * ))) == NULL ) {
-           perror( "realloc" );
-           exit( EXIT_FAILURE );
-       }
-       *pmodsp = pmods;
-       pmods[ i + 1 ] = NULL;
-       if (( pmods[ i ] = (LDAPMod *)ber_memcalloc( 1, sizeof( LDAPMod )))
-               == NULL ) {
-           perror( "calloc" );
-           exit( EXIT_FAILURE );
-       }
-       pmods[ i ]->mod_op = modop;
-       if (( pmods[ i ]->mod_type = ber_strdup( attr )) == NULL ) {
-           perror( "strdup" );
-           exit( EXIT_FAILURE );
-       }
-    }
+               pmods[ i ] = (LDAPMod *)ber_memcalloc( 1, sizeof( LDAPMod ));
+               if ( pmods[ i ] == NULL ) {
+                       perror( "calloc" );
+                       exit( EXIT_FAILURE );
+               }
 
-    if ( value != NULL ) {
-       j = 0;
-       if ( pmods[ i ]->mod_bvalues != NULL ) {
-           for ( ; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
-               ;
-           }
-       }
-       if (( pmods[ i ]->mod_bvalues =
-               (struct berval **)ber_memrealloc( pmods[ i ]->mod_bvalues,
-               (j + 2) * sizeof( struct berval * ))) == NULL ) {
-           perror( "ber_realloc" );
-           exit( EXIT_FAILURE );
-       }
-       pmods[ i ]->mod_bvalues[ j + 1 ] = NULL;
-       if (( bvp = (struct berval *)ber_memalloc( sizeof( struct berval )))
-               == NULL ) {
-           perror( "ber_memalloc" );
-           exit( EXIT_FAILURE );
+               pmods[ i ]->mod_op = modop;
+               pmods[ i ]->mod_type = ber_strdup( attr );
+               if ( pmods[ i ]->mod_type == NULL ) {
+                       perror( "strdup" );
+                       exit( EXIT_FAILURE );
+               }
        }
-       pmods[ i ]->mod_bvalues[ j ] = bvp;
 
-       if ( valsfromfiles && *value == '/' ) { /* get value from file */
-           if ( fromfile( value, bvp ) < 0 ) {
-               exit( EXIT_FAILURE );
-           }
-       } else {
-           bvp->bv_len = vlen;
-           if (( bvp->bv_val = (char *)ber_memalloc( vlen + 1 )) == NULL ) {
-               perror( "malloc" );
-               exit( EXIT_FAILURE );
-           }
-           SAFEMEMCPY( bvp->bv_val, value, vlen );
-           bvp->bv_val[ vlen ] = '\0';
+       if ( val != NULL ) {
+               j = 0;
+               if ( pmods[ i ]->mod_bvalues != NULL ) {
+                       for ( ; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
+                               /* Empty */;
+                       }
+               }
+
+               pmods[ i ]->mod_bvalues = (struct berval **) ber_memrealloc(
+                       pmods[ i ]->mod_bvalues, (j + 2) * sizeof( struct berval * ));
+               if ( pmods[ i ]->mod_bvalues == NULL ) {
+                       perror( "ber_realloc" );
+                       exit( EXIT_FAILURE );
+               }
+
+               pmods[ i ]->mod_bvalues[ j + 1 ] = NULL;
+               pmods[ i ]->mod_bvalues[ j ] = ber_bvdup( val );
+               if ( pmods[ i ]->mod_bvalues[ j ] == NULL ) {
+                       perror( "ber_bvdup" );
+                       exit( EXIT_FAILURE );
+               }
        }
-    }
 }
 
 
 static int
-domodify( char *dn, LDAPMod **pmods, int newentry )
+domodify(
+       const char *dn,
+       LDAPMod **pmods,
+    LDAPControl **pctrls,
+       int newentry )
 {
     int                        i, j, k, notascii, op;
     struct berval      *bvp;
 
+       if ( dn == NULL ) {
+       fprintf( stderr, _("%s: no DN specified\n"), prog );
+       return( LDAP_PARAM_ERROR );
+       }
+
     if ( pmods == NULL ) {
-       fprintf( stderr, "%s: no attributes to change or add (entry=\"%s\")\n",
+       fprintf( stderr, _("%s: no attributes to change or add (entry=\"%s\")\n"),
                prog, dn );
        return( LDAP_PARAM_ERROR );
+    } 
+
+    for ( i = 0; pmods[ i ] != NULL; ++i ) {
+       op = pmods[ i ]->mod_op & ~LDAP_MOD_BVALUES;
+       if( op == LDAP_MOD_ADD && ( pmods[i]->mod_bvalues == NULL )) {
+               fprintf( stderr,
+                       _("%s: attribute \"%s\" has no values (entry=\"%s\")\n"),
+                       prog, pmods[i]->mod_type, dn );
+               return LDAP_PARAM_ERROR;
+       }
     }
 
     if ( verbose ) {
        for ( i = 0; pmods[ i ] != NULL; ++i ) {
            op = pmods[ i ]->mod_op & ~LDAP_MOD_BVALUES;
-           printf( "%s %s:\n", op == LDAP_MOD_REPLACE ?
-                   "replace" : op == LDAP_MOD_ADD ?
-                   "add" : "delete", pmods[ i ]->mod_type );
+           printf( "%s %s:\n",
+                       op == LDAP_MOD_REPLACE ? _("replace") :
+                               op == LDAP_MOD_ADD ?  _("add") :
+                                       op == LDAP_MOD_INCREMENT ?  _("increment") :
+                                               op == LDAP_MOD_DELETE ?  _("delete") :
+                                                       _("unknown"),
+                       pmods[ i ]->mod_type );
            if ( pmods[ i ]->mod_bvalues != NULL ) {
                for ( j = 0; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
                    bvp = pmods[ i ]->mod_bvalues[ j ];
@@ -796,7 +884,7 @@ domodify( char *dn, LDAPMod **pmods, int newentry )
                        }
                    }
                    if ( notascii ) {
-                       printf( "\tNOT ASCII (%ld bytes)\n", bvp->bv_len );
+                       printf( _("\tNOT ASCII (%ld bytes)\n"), bvp->bv_len );
                    } else {
                        printf( "\t%s\n", bvp->bv_val );
                    }
@@ -813,14 +901,16 @@ domodify( char *dn, LDAPMod **pmods, int newentry )
 
     if ( !not ) {
        if ( newentry ) {
-           i = ldap_add_s( ld, dn, pmods );
+           i = ldap_add_ext_s( ld, dn, pmods, pctrls, NULL );
        } else {
-           i = ldap_modify_s( ld, dn, pmods );
+           i = ldap_modify_ext_s( ld, dn, pmods, pctrls, NULL );
        }
        if ( i != LDAP_SUCCESS ) {
-           ldap_perror( ld, newentry ? "ldap_add" : "ldap_modify" );
+               /* print error message about failed update including DN */
+               fprintf( stderr, _("%s: update failed: %s\n"), prog, dn );
+               ldap_perror( ld, newentry ? "ldap_add" : "ldap_modify" );
        } else if ( verbose ) {
-           printf( "modify complete\n" );
+           printf( _("modify complete\n") );
        }
     } else {
        i = LDAP_SUCCESS;
@@ -833,16 +923,19 @@ domodify( char *dn, LDAPMod **pmods, int newentry )
 
 
 static int
-dodelete( char *dn )
+dodelete(
+       const char *dn,
+    LDAPControl **pctrls )
 {
     int        rc;
 
-    printf( "%sdeleting entry \"%s\"\n", not ? "!" : "", dn );
+    printf( _("%sdeleting entry \"%s\"\n"), not ? "!" : "", dn );
     if ( !not ) {
-       if (( rc = ldap_delete_s( ld, dn )) != LDAP_SUCCESS ) {
-           ldap_perror( ld, "ldap_delete" );
+       if (( rc = ldap_delete_ext_s( ld, dn, pctrls, NULL )) != LDAP_SUCCESS ) {
+               fprintf( stderr, _("%s: delete failed: %s\n"), prog, dn );
+               ldap_perror( ld, "ldap_delete" );
        } else if ( verbose ) {
-           printf( "delete complete" );
+           printf( _("delete complete") );
        }
     } else {
        rc = LDAP_SUCCESS;
@@ -855,22 +948,28 @@ dodelete( char *dn )
 
 
 static int
-domodrdn( char *dn, char *newrdn, int deleteoldrdn )
+dorename(
+       const char *dn,
+       const char *newrdn,
+       const char* newsup,
+       int deleteoldrdn,
+    LDAPControl **pctrls )
 {
     int        rc;
 
 
-    printf( "%smodifying rdn of entry \"%s\"\n", not ? "!" : "", dn );
+    printf( _("%smodifying rdn of entry \"%s\"\n"), not ? "!" : "", dn );
     if ( verbose ) {
-       printf( "\tnew RDN: \"%s\" (%skeep existing values)\n",
-               newrdn, deleteoldrdn ? "do not " : "" );
+       printf( _("\tnew RDN: \"%s\" (%skeep existing values)\n"),
+               newrdn, deleteoldrdn ? _("do not ") : "" );
     }
     if ( !not ) {
-       if (( rc = ldap_modrdn2_s( ld, dn, newrdn, deleteoldrdn ))
+       if (( rc = ldap_rename_s( ld, dn, newrdn, newsup, deleteoldrdn, pctrls, NULL ))
                != LDAP_SUCCESS ) {
-           ldap_perror( ld, "ldap_modrdn" );
+               fprintf( stderr, _("%s: rename failed: %s\n"), prog, dn );
+               ldap_perror( ld, "ldap_modrdn" );
        } else {
-           printf( "modrdn completed\n" );
+           printf( _("modrdn completed\n") );
        }
     } else {
        rc = LDAP_SUCCESS;
@@ -882,55 +981,6 @@ domodrdn( char *dn, char *newrdn, int deleteoldrdn )
 }
 
 
-static int
-fromfile( char *path, struct berval *bv )
-{
-       FILE            *fp;
-       long            rlen;
-       int             eof;
-
-       if (( fp = fopen( path, "r" )) == NULL ) {
-               perror( path );
-               return( -1 );
-       }
-
-       if ( fseek( fp, 0L, SEEK_END ) != 0 ) {
-               perror( path );
-               fclose( fp );
-               return( -1 );
-       }
-
-       bv->bv_len = ftell( fp );
-
-       if (( bv->bv_val = (char *)ber_memalloc( bv->bv_len )) == NULL ) {
-               perror( "malloc" );
-               fclose( fp );
-               return( -1 );
-       }
-
-       if ( fseek( fp, 0L, SEEK_SET ) != 0 ) {
-               perror( path );
-               fclose( fp );
-               ber_memfree( bv->bv_val );
-               bv->bv_val = NULL;
-               return( -1 );
-       }
-
-       rlen = fread( bv->bv_val, 1, bv->bv_len, fp );
-       eof = feof( fp );
-       fclose( fp );
-
-       if ( (unsigned long) rlen != bv->bv_len ) {
-               perror( path );
-               ber_memfree( bv->bv_val );
-               bv->bv_val = NULL;
-               return( -1 );
-       }
-
-       return( bv->bv_len );
-}
-
-
 static char *
 read_one_record( FILE *fp )
 {
@@ -943,7 +993,7 @@ read_one_record( FILE *fp )
     while ( fgets( line, sizeof(line), fp ) != NULL ) {
        int len = strlen( line );
 
-               if( len < 2 ) {
+               if( len < 2 || ( len == 2 && *line == '\r' )) {
                        if( buf == NULL ) {
                                continue;
                        } else {
@@ -955,7 +1005,7 @@ read_one_record( FILE *fp )
                        lmax = LDAPMOD_MAXLINE
                                * (( lcur + len + 1 ) / LDAPMOD_MAXLINE + 1 );
 
-                       if (( buf = (char *)realloc( buf, lmax )) == NULL ) {
+                       if (( buf = (char *)ber_memrealloc( buf, lmax )) == NULL ) {
                                perror( "realloc" );
                                exit( EXIT_FAILURE );
                        }
@@ -967,3 +1017,5 @@ read_one_record( FILE *fp )
 
     return( buf );
 }
+
+