/*
Auth trigger program for Perforce using Active Directory credentials.
To use this program, add triggers of the form:
Triggers:
ldap_passwd auth-check auth "/home/perforce/triggers/p4ldap %user%"
ldap_set_passwd auth-set auth "/bin/echo Change your password in Windows."
To compile this program:
gcc -o p4ldap p4ldap.c -lldap
This program expects a username as an argument and will read a password on
stdin. It will bind to the Global Catalogue using the credentials of the
svc-UnixLDAPAuth account and search for the DN of the user whose username is
specified. If successful it will try to bind as that DN using the provided
password. This will work if the password is correct.
Because usernames are globally unique and because we query the Global
Catalogue on a local domain controller, this program will authenticate users
in any domain.
*/
#include <ldap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PROG "p4ldap"
/* Bind credentials for service account. */
#define BINDDN "cn=svc-UnixLDAPAuth,ou=Service Accounts,ou=Users,ou=UK,dc=example,dc=com"
#define BINDPW "letmein"
/* LDAP server details. */
#define OUR_URI "ldaps://rootdc01.example.com:3269"
#define OUR_BASE "dc=com"
#define OUR_FILTER "(sAMAccountName=%s)"
int main(int argc, char **argv) {
LDAP *ld;
LDAPMessage *result, *entry;
char *dn;
char *filter, *s, *username;
char password[256];
unsigned int protocol;
int exitasap, ret;
size_t len;
/* Check args. */
if (argc == 1) {
fprintf(stderr, "Usage: echo $PASSWORD | %s <username>\n", PROG);
exit(100);
}
username = argv[1];
/* Get password. */
if (! fgets(password, sizeof(password), stdin)) {
fprintf(stderr, "%s: Can't get password from stdin!\n", PROG);
exit(1);
}
/* Chomp. */
for (s = password; *s; s++) {
if (*s == '\r' || *s == '\n') {
*s = 0;
break;
}
}
/* Construct LDAP filter. */
len = strlen(username) + strlen(OUR_FILTER) - 1;
filter = (char *) malloc(len);
if (! filter) {
fprintf(stderr, "%s: Out of memory for filter!\n", PROG);
exit(111);
}
if (snprintf(filter, len, OUR_FILTER, username) < 0) {
fprintf(stderr, "%s: Error constructing filter: ", PROG);
perror("snprintf");
exit(111);
}
/* Initialise LDAP. */
protocol = 3;
ldap_initialize(&ld, OUR_URI);
ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol);
/* Bind as the service account. */
ret = ldap_simple_bind_s(ld, BINDDN, BINDPW);
if (ret != LDAP_SUCCESS) {
fprintf(stderr, "%s: Binding as service account: ", PROG);
ldap_perror(ld, "ldap_simple_bind_s");
exit(2);
}
/* Search for DN of the given username. */
ret = ldap_search_s(ld, OUR_BASE, LDAP_SCOPE_SUBTREE, filter, 0, 0, &result);
if (ret != LDAP_SUCCESS) {
fprintf(stderr, "%s: Searching for %s: ", PROG, username);
ldap_perror(ld, "ldap_search_s");
exit(3);
}
exitasap = 0;
if (ldap_count_entries(ld, result)) {
entry = ldap_first_entry(ld, result);
if (! entry) {
fprintf(stderr, "%s: %s: ", PROG, username);
ldap_perror(ld, "Retrieving search results");
exit(4);
}
/* Retrieve the DN. */
dn = ldap_get_dn(ld, entry);
if (! dn) {
fprintf(stderr, "%s: No DN for %s. This is not possible.\n", PROG, username);
exit(5);
}
}
else {
fprintf(stderr, "%s: No such user: %s\n", PROG, username);
exitasap = 1;
}
ret = ldap_unbind(ld);
if (ret != LDAP_SUCCESS) {
fprintf(stderr, "%s: Unbinding as service account: ", PROG);
ldap_perror(ld, "ldap_unbind_s");
exit(6);
}
/* Exit if we didn't find the user. */
if (exitasap) exit(100);
/* Now rebind as the user. */
ldap_initialize(&ld, OUR_URI);
ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol);
ret = ldap_simple_bind_s(ld, dn, password);
if (ret == LDAP_SUCCESS) {
/*
If no password is given, Active Directory will allow the bind.
Check that the user actually has a blank password by performing a search.
*/
if (! *password) {
ret = ldap_search_s(ld, OUR_BASE, LDAP_SCOPE_SUBTREE, filter, 0, 0, &result);
if (ret != LDAP_SUCCESS) exitasap = 1;
}
}
else exitasap = 1;
/* Unbind again. */
ret = ldap_unbind(ld);
if (ret != LDAP_SUCCESS) {
fprintf(stderr, "%s: Unbinding as %s: ", PROG, username);
ldap_perror(ld, "ldap_unbind_s");
exit(4);
}
/* Exit based on result of bind. */
if (exitasap) exit(1);
exit(0);
}