/* -*- show-trailing-whitespace: t; indent-tabs: t -*-
* Copyright (c) 2003,2004,2005,2006 David Lichteblau
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define _XOPEN_SOURCE
#include <unistd.h>
#include "common.h"
#define fast_g_string_append_c(gstring, c) \
do { \
if ((gstring)->len + 1 >= (gstring)->allocated_len) \
g_string_append_c((gstring), (c)); \
else { \
(gstring)->str[(gstring)->len++] = (c); \
(gstring)->str[(gstring)->len] = 0; \
} \
} while (0)
static int
read_lhs(FILE *s, GString *lhs)
{
int c;
for (;;) {
switch ( c = getc_unlocked(s)) {
case ' ':
if (ferror(s)) syserr();
return 0;
case EOF:
fputs("Error: Unexpected EOF.\n", stderr);
return -1;
case '\n':
fputs("Error: Unexpected EOL.\n", stderr);
return -1;
case 0:
fputs("Error: Null byte not allowed.\n", stderr);
return -1;
default:
fast_g_string_append_c(lhs, c);
}
}
}
static int
read_backslashed(FILE *s, GString *data)
{
int c;
for (;;) {
switch ( c = getc_unlocked(s)) {
case '\n':
if (ferror(s)) syserr();
return 0;
case EOF:
goto error;
case '\\':
if ( (c = fgetc(s)) == EOF) goto error;
/* fall through */
default:
fast_g_string_append_c(data, c);
}
}
error:
fputs("Error: Unexpected EOF.\n", stderr);
return -1;
}
static int
read_ldif_attrval(FILE *s, GString *data)
{
int c;
for (;;)
switch ( c = getc_unlocked(s)) {
case '\n':
if ( (c = fgetc(s)) == ' ') /* folded line */ break;
ungetc(c, s);
if (ferror(s)) syserr();
return 0;
case EOF:
fputs("Error: Unexpected EOF.\n", stderr);
return -1;
default:
fast_g_string_append_c(data, c);
}
}
static int
read_from_file(GString *data, char *name)
{
int fd, n;
if ( (fd = open(name, O_RDONLY)) == -1) {
perror("open");
return -1;
}
data->len = 0;
n = 1024;
do {
int olen = data->len;
g_string_set_size(data, data->len + n);
if ( (n = read(fd, data->str + olen, n)) == -1) syserr();
data->len = olen + n;
} while (n > 0);
if (close(fd) == -1) syserr();
return 0;
}
static int
skip_comment(FILE *s)
{
int c;
for (;;)
switch ( c = fgetc(s)) {
case EOF:
fputs("Error: Unexpected EOF.\n", stderr);
return -1;
case '\n':
if ( (c = fgetc(s)) == ' ') /* folded line */ break;
ungetc(c, s);
if (ferror(s)) syserr();
return 0;
}
}
static char *saltbag
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890./";
static char *
cryptdes(char *key)
{
unsigned char salt[2];
int fd = open("/dev/random", 2);
if (fd == -1) {
puts("Sorry, crypt not available: Cannot open /dev/random.");
return 0;
}
if (read(fd, salt, 2) != 2) syserr();
close(fd);
salt[0] = saltbag[salt[0] & 63];
salt[1] = saltbag[salt[1] & 63];
return crypt(key, (char *) salt);
}
static char *
cryptmd5(char *key)
{
char *result;
unsigned char salt[11];
int i;
int fd = open("/dev/random", 2);
if (fd == -1) {
puts("Sorry, MD5 not available: Cannot open /dev/random.");
return 0;
}
salt[0] = '$';
salt[1] = '1';
salt[2] = '$';
if (read(fd, salt + 3, 8) != 8) syserr();
close(fd);
for (i = 3; i < 11; i++)
salt[i] = saltbag[salt[i] & 63];
result = crypt(key, (char *) salt);
if (!result || strlen(result) < 25) {
puts("Sorry, MD5 not available: Are you using the glibc?");
return 0;
}
return result;
}
/*
* Read a line in
* name ' ' (':' encoding)? value '\n'
* syntax, skipping comments. VALUE is parsed according to ENCODING.
* Empty NAME is allowed.
*
* 0: ok
* -1: fatal parse error
* -2: end of file or empty line
*/
static int
read_line1(FILE *s, GString *name, GString *value)
{
int c;
char *encoding;
g_string_truncate(name, 0);
g_string_truncate(value, 0);
/* skip comment lines */
do {
c = fgetc(s);
switch (c) {
case EOF:
if (ferror(s)) syserr();
return -2;
case '\n':
return -2;
case '#':
if (skip_comment(s) == -1) return -1;
break;
default:
ungetc(c, s);
c = -1;
}
} while (c != -1);
if (read_lhs(s, name) == -1) return -1;
if ( encoding = memchr(name->str, ':', name->len)) {
encoding++;
name->len = encoding - name->str - 1;
name->str[name->len] = 0;
}
if (!encoding || !strcmp(encoding, ";")) {
if (read_backslashed(s, value) == -1) return -1;
} else if (!*encoding) {
if (read_ldif_attrval(s, value) == -1) return -1;
} else if (!strcmp(encoding, ":")) {
unsigned char *ustr;
int len;
if (read_ldif_attrval(s, value) == -1) return -1;
ustr = (unsigned char *) value->str;;
if ( (len = read_base64(value->str, ustr, value->len)) == -1) {
fputs("Error: Invalid Base64 string.\n", stderr);
return -1;
}
value->len = len;
} else if (!strcmp(encoding, "<")) {
if (read_ldif_attrval(s, value) == -1) return -1;
if (strncmp(value->str, "file://", 7)) {
fputs("Error: Unknown URL scheme.\n", stderr);
return -1;
}
if (read_from_file(value, value->str + 7) == -1)
return -1;
} else if (!strcasecmp(encoding, "crypt")) {
char *hash;
if (read_ldif_attrval(s, value) == -1) return -1;
if ( !(hash = cryptdes(value->str))) return -1;
g_string_assign(value, "{CRYPT}");
g_string_append(value, hash);
} else if (!strcasecmp(encoding, "cryptmd5")) {
char *hash;
if (read_ldif_attrval(s, value) == -1) return -1;
if ( !(hash = cryptmd5(value->str))) return -1;
g_string_assign(value, "{CRYPT}");
g_string_append(value, hash);
} else if (!strcasecmp(encoding, "sha")) {
if (read_ldif_attrval(s, value) == -1) return -1;
g_string_assign(value, "{SHA}");
if (!g_string_append_sha(value, value->str)) return -1;
} else if (!strcasecmp(encoding, "ssha")) {
if (read_ldif_attrval(s, value) == -1) return -1;
g_string_assign(value, "{SSHA}");
if (!g_string_append_ssha(value, value->str)) return -1;
} else if (!strcasecmp(encoding, "md5")) {
if (read_ldif_attrval(s, value) == -1) return -1;
g_string_assign(value, "{MD5}");
if (!g_string_append_md5(value, value->str)) return -1;
} else if (!strcasecmp(encoding, "smd5")) {
if (read_ldif_attrval(s, value) == -1) return -1;
g_string_assign(value, "{SMD5}");
if (!g_string_append_smd5(value, value->str)) return -1;
} else {
char *ptr;
int n = strtol(encoding, &ptr, 10);
if (*ptr) {
fputs("Error: Unknown value encoding.\n", stderr);
return -1;
}
g_string_set_size(value, n);
if (fread(value->str, 1, n, s) != n) syserr();
}
return 0;
}
/*
* Read a line in
* name ' ' (':' encoding)? value '\n'
* syntax, skipping comments. VALUE is parsed according to ENCODING.
* Empty NAME is a parse error.
*
* 0: ok if name->len != 0
* 0: end of file or empty line if name->len == 0
* -1: parse error
*/
static int
read_line(FILE *s, GString *name, GString *value)
{
int rc = read_line1(s, name, value);
switch (rc) {
case -2:
return 0;
case -1:
return -1;
case 0:
if (!name->len) {
fputs("Error: Space at beginning of line.\n", stderr);
return -1;
}
return 0;
default:
abort();
}
}
static char *
read_rename_body(FILE *s, GString *tmp1, GString *tmp2, int *deleteoldrdn)
{
char *dn;
if (read_line(s, tmp1, tmp2) == -1)
return 0;
if (!tmp1->len) {
fputs("Error: Rename record lacks dn line.\n", stderr);
return 0;
}
*deleteoldrdn = !strcmp(tmp1->str, "replace");
if (!*deleteoldrdn && strcmp(tmp1->str, "add")) {
fputs("Error: Expected 'add' or 'replace' in rename record.\n",
stderr);
return 0;
}
dn = xdup(tmp2->str);
if (read_line(s, tmp1, tmp2) == -1) {
free(dn);
return 0;
}
if (tmp1->len) {
free(dn);
fputs("Error: Garbage at end of rename record.\n", stderr);
return 0;
}
return dn;
}
static int
read_nothing(FILE *s, GString *tmp1, GString *tmp2)
{
if (read_line(s, tmp1, tmp2) == -1)
return -1;
if (tmp1->len) {
fputs("Error: Garbage at end of record.\n", stderr);
return -1;
}
return 0;
}
static LDAPMod *
ldapmod4line(char *action, char *ad)
{
LDAPMod *m;
int op;
if (!strcmp(action, "add"))
op = LDAP_MOD_ADD;
else if (!strcmp(action, "delete"))
op = LDAP_MOD_DELETE;
else if (!strcmp(action, "replace"))
op = LDAP_MOD_REPLACE;
else {
fputs("Error: Invalid change marker.\n", stderr);
return 0;
}
m = xalloc(sizeof(LDAPMod));
m->mod_op = op | LDAP_MOD_BVALUES;
m->mod_type = xdup(ad);
return m;
}
static LDAPMod **
read_modify_body(FILE *s, GString *tmp1, GString *tmp2)
{
LDAPMod **result;
GPtrArray *mods = g_ptr_array_new();
GPtrArray *values;
LDAPMod *m = 0;
for (;;) {
switch (read_line1(s, tmp1, tmp2)) {
case 0:
break;
case -1:
goto error;
case -2:
if (m) {
g_ptr_array_add(values, 0);
m->mod_bvalues = (void *) values->pdata;
g_ptr_array_free(values, 0);
values = 0;
}
goto done;
default:
abort();
}
if (tmp1->len) {
if (m) {
g_ptr_array_add(values, 0);
m->mod_bvalues = (void *) values->pdata;
g_ptr_array_free(values, 0);
values = 0;
}
values = g_ptr_array_new();
if ( !(m = ldapmod4line(tmp1->str, tmp2->str)))
goto error;
g_ptr_array_add(mods, m);
} else
g_ptr_array_add(values, gstring2berval(tmp2));
}
done:
g_ptr_array_add(mods, 0);
result = (LDAPMod **) mods->pdata;
g_ptr_array_free(mods, 0);
return result;
error:
g_ptr_array_free(mods, 1);
if (values) {
int i;
for (i = 0; i < values->len; i++)
xfree_berval(values->pdata[i]);
g_ptr_array_free(values, 0);
}
return 0;
}
/*
* Lies die erste Zeile eines beliebigen Records nach position `offset' in `s'.
* Setze *pos (falls pos != 0).
* Liefere 0 bei Erfolg, -1 sonst.
* Bei Erfolg:
* - pos ist die exakte Anfangsposition.
* - Setze *key auf den Schluessel (falls key != 0).
* - Setze *dn auf den Distinguished Name (falls dn != 0).
* EOF ist kein Fehler und liefert *key = 0 (falls key != 0);
*/
static int
read_header(GString *tmp1, GString *tmp2,
FILE *s, long offset, char **key, char **dn, long *pos)
{
char **rdns = 0;
if (offset != -1)
if (fseek(s, offset, SEEK_SET) == -1) syserr();
do {
if (pos)
if ( (*pos = ftell(s)) == -1) syserr();
if (read_line(s, tmp1, tmp2) == -1) return -1;
if (tmp1->len == 0 && feof(s)) {
if (key) *key = 0;
return 0;
}
if (!strcmp(tmp1->str, "version")) {
if (strcmp(tmp2->str, "ldapvi")) {
fputs("Error: Invalid file format.\n", stderr);
return -1;
}
tmp1->len = 0;
}
} while (!tmp1->len);
rdns = ldap_explode_dn(tmp2->str, 0);
if (!rdns) {
fputs("Error: Invalid distinguished name string.\n", stderr);
return -1;
}
if (key) *key = xdup(tmp1->str);
if (dn) *dn = xdup(tmp2->str);
ldap_value_free(rdns);
return 0;
}
static int
read_attrval_body(GString *tmp1, GString *tmp2, FILE *s, tentry *entry)
{
for (;;) {
tattribute *attribute;
if (read_line(s, tmp1, tmp2) == -1)
return -1;
if (!tmp1->len)
break;
attribute = entry_find_attribute(entry, tmp1->str, 1);
attribute_append_value(attribute, tmp2->str, tmp2->len);
}
return 0;
}
/*
* Lies ein attrval-record nach position `offset' in `s'.
* Setze *pos (falls pos != 0).
* Liefere 0 bei Erfolg, -1 sonst.
* Bei Erfolg:
* - pos ist die exakte Anfangsposition.
* - Setze *entry auf den gelesenen Eintrag (falls entry != 0).
* - Setze *key auf den Schluessel (falls key != 0).
* EOF ist kein Fehler und liefert *key = 0 (falls key != 0);
*/
int
read_entry(FILE *s, long offset, char **key, tentry **entry, long *pos)
{
GString *tmp1 = g_string_new("");
GString *tmp2 = g_string_new("");
char *dn;
char *k = 0;
tentry *e = 0;
int rc = read_header(tmp1, tmp2, s, offset, &k, &dn, pos);
if (rc || !k) goto cleanup;
e = entry_new(dn);
rc = read_attrval_body(tmp1, tmp2, s, e);
if (!rc) {
if (entry) {
*entry = e;
e = 0;
}
if (key) {
*key = k;
k = 0;
}
}
cleanup:
if (k) free(k);
if (e) entry_free(e);
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
return rc;
}
/*
* Lies die erste Zeile eines beliebigen Records nach position `offset' in `s'.
* Setze *pos (falls pos != 0).
* Liefere 0 bei Erfolg, -1 sonst.
* Bei Erfolg:
* - pos ist die exakte Anfangsposition.
* - Setze *key auf den Schluessel (falls key != 0).
*/
int
peek_entry(FILE *s, long offset, char **key, long *pos)
{
GString *tmp1 = g_string_new("");
GString *tmp2 = g_string_new("");
int rc = read_header(tmp1, tmp2, s, offset, key, 0, pos);
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
return rc;
}
/*
* Lies ein rename-record nach position `offset' in `s'.
* Liefere 0 bei Erfolg, -1 sonst.
* Bei Erfolg:
* - Setze *dn1 auf den alten DN.
* - Setze *dn2 auf den neuen DN.
* - *deleteoldrdn auf 1 oder 0;
*/
int
read_rename(FILE *s, long offset, char **dn1, char **dn2, int *deleteoldrdn)
{
GString *tmp1 = g_string_new("");
GString *tmp2 = g_string_new("");
char *olddn;
char *newdn;
int rc = read_header(tmp1, tmp2, s, offset, 0, &olddn, 0);
if (rc) {
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
return rc;
}
newdn = read_rename_body(s, tmp1, tmp2, deleteoldrdn);
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
if (!newdn) {
free(olddn);
return -1;
}
if (dn1) *dn1 = olddn; else free(olddn);
if (dn2) *dn2 = newdn; else free(newdn);
return 0;
}
int
read_delete(FILE *s, long offset, char **dn)
{
GString *tmp1 = g_string_new("");
GString *tmp2 = g_string_new("");
char *str;
int rc = read_header(tmp1, tmp2, s, offset, 0, &str, 0);
if (rc) {
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
return rc;
}
rc = read_nothing(s, tmp1, tmp2);
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
if (rc == -1)
free(str);
else
*dn = str;
return rc;
}
/*
* Lies ein modify-record nach position `offset' in `s'.
* Liefere 0 bei Erfolg, -1 sonst.
* Bei Erfolg:
* - Setze *dn auf den DN.
* - Setze *mods auf die Aenderungen.
*/
int
read_modify(FILE *s, long offset, char **dn, LDAPMod ***mods)
{
GString *tmp1 = g_string_new("");
GString *tmp2 = g_string_new("");
char *d;
LDAPMod **m;
int rc = read_header(tmp1, tmp2, s, offset, 0, &d, 0);
if (rc) {
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
return rc;
}
m = read_modify_body(s, tmp1, tmp2);
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
if (!m) {
free(d);
return -1;
}
if (dn) *dn = d; else free(d);
if (mods) *mods = m; else ldap_mods_free(m, 1);
return 0;
}
/*
* Parse a complete entry or changerecord and ignore it. Set *key accordingly.
* Leave the stream positioned after the entry.
*
* Treat EOF as success and set *key to NULL.
*
* return value:
* 0 on success
* -1 on parse error
*/
/*
* FIXME: Warum lesen wir hier nicht einfach bis zur naechsten leeren Zeile?
*/
int
skip_entry(FILE *s, long offset, char **key)
{
GString *tmp1 = g_string_new("");
GString *tmp2 = g_string_new("");
char *k = 0;
int rc = read_header(tmp1, tmp2, s, offset, &k, 0, 0);
if (rc || !k)
;
else if (!strcmp(k, "modify")) {
LDAPMod **mods = read_modify_body(s, tmp1, tmp2);
if (mods)
ldap_mods_free(mods, 1);
else
rc = -1;
} else if (!strcmp(k, "rename")) {
int dor;
char *newdn = read_rename_body(s, tmp1, tmp2, &dor);
if (newdn)
free(newdn);
else
rc = -1;
} else if (!strcmp(k, "delete"))
rc = read_nothing(s, tmp1, tmp2);
else {
tentry *e = entry_new(xdup(""));
rc = read_attrval_body(tmp1, tmp2, s, e);
entry_free(e);
}
if (key) *key = k; else free(k);
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
return rc;
}
static int
read_profile_header(GString *tmp1, GString *tmp2, FILE *s, char **name)
{
do {
if (read_line(s, tmp1, tmp2) == -1) return -1;
if (tmp1->len == 0 && feof(s)) {
*name = 0;
return 0;
}
} while (!tmp1->len);
if (strcmp(tmp1->str, "profile")) {
fprintf(stderr,
"Error: Expected 'profile' in configuration,"
" found '%s' instead.\n",
tmp1->str);
return -1;
}
*name = xdup(tmp2->str);
return 0;
}
int
read_profile(FILE *s, tentry **entry)
{
GString *tmp1 = g_string_new("");
GString *tmp2 = g_string_new("");
char *name;
tentry *e = 0;
int rc = read_profile_header(tmp1, tmp2, s, &name);
if (rc || !name) goto cleanup;
e = entry_new(name);
rc = read_attrval_body(tmp1, tmp2, s, e);
if (!rc) {
*entry = e;
e = 0;
}
cleanup:
if (e) entry_free(e);
g_string_free(tmp1, 1);
g_string_free(tmp2, 1);
return rc;
}
tparser ldapvi_parser = {
read_entry,
peek_entry,
skip_entry,
read_rename,
read_delete,
read_modify,
print_ldapvi_entry
};
syntax highlighted by Code2HTML, v. 0.9.1