[LDAP] C Programming : Connect / Search / Login

kafkaaaa·2024년 6월 4일
0

LDAP

목록 보기
5/8

#0. LDAP 개발 라이브러리 설치

# Debian/Ubuntu
sudo apt-get install libldap2-dev

# CentOS/RHEL
sudo yum install openldap-devel

#1. conf 파일 작성

/etc/test/ldap_test.conf

## LDAP 서버의 주소 및 포트 지정
LDAP_URI = "ldap://192.168.1.152:389"

## Base DN
LDAP_BASE_DN = "dc=example,dc=com"

## Bind DN
LDAP_BIND_DN = "cn=admin,dc=example,dc=com"

## Base DN for Users
LDAP_USER_BASE_DN = "ou=People,dc=example,dc=com"

BaseDN vs BindDN

📌 Base DN
: LDAP Directory Tree의 검색 시작점을 나타냄
: 모든 LDAP 항목은 이 Base DN 아래에 위치함
: 일반적으로 조직의 도메인 이름을 역순으로 표현함
: e.g. "dc=company,dc=com"

📌 Bind DN
: LDAP 서버에 연결할 때 사용하는 사용자의 DN (Distinguished Name)
: 사용자나 관리자 계정의 전체 경로를 나타냄
: e.g. "cn=admin,dc=company,dc=com"
: e.g. "cn=username,ou=People,dc=company,dc=com"


#2. [Connect] LDAP 서버 연결 프로그램

LDAP 서버 연결 테스트하기

ldap_connect.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <ldap.h>

#define CONFIG_FILE "/etc/test/ldap_test.conf"
#define MAX_LINE 256

int main() {
    FILE *f = fopen(CONFIG_FILE, "r");
    if (!f) {
        perror("Failed to open config file");
        return EXIT_FAILURE;
    }

    char ldap_uri[MAX_LINE] = {0};
    char bind_dn[MAX_LINE] = {0};
    char line[MAX_LINE];

    while (fgets(line, sizeof(line), f)) {
        if (sscanf(line, "LDAP_URI = \"%255[^\"]\"", ldap_uri) == 1) continue;
        if (sscanf(line, "LDAP_BIND_DN = \"%255[^\"]\"", bind_dn) == 1) continue;
    }
    fclose(f);

    if (!*ldap_uri || !*bind_dn) {
        fprintf(stderr, "Required configuration not found in [%s] file\n", CONFIG_FILE);
        return EXIT_FAILURE;
    }

    printf("LDAP URI: %s\n", ldap_uri);
    printf("LDAP Bind DN: %s\n", bind_dn);

    // LDAP handle pointer
    LDAP* ld = NULL;

    // ldap_initialize() -> LDAP 라이브러리 초기화 & 서버 연결
    // LDAP 핸들 생성 + 지정된 URI의 서버에 연결 시도
    // 반환 값 = LDAP 결과 코드. LDAP_SUCCESS = 성공.
    int rc = ldap_initialize(&ld, ldap_uri);

    // 연결 시도 결과 확인
    if (rc != LDAP_SUCCESS) {
        // 연결 실패
        // ldap_err2string(): LDAP 결과 코드를 읽을 수 있는 문자열로 변환
        fprintf(stderr, "ldap_initialize failed: %s\n", ldap_err2string(rc));

        return 1;
    }

    // LDAP 프로토콜 버전 설정 (v3)
    int version = LDAP_VERSION3;
    rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "Failed to set LDAP protocol version: %s\n", ldap_err2string(rc));
        ldap_unbind_ext_s(ld, NULL, NULL);  // LDAP 연결 종료

        return 1;
    }

    // 비밀번호 입력 받기
    char* password = getpass("Enter LDAP password: ");
    if (!password) {
        fprintf(stderr, "Failed to read password\n");
        ldap_unbind_ext_s(ld, NULL, NULL);

        return 1;
    }

    // LDAP 바인딩에 사용할 자격 증명 설정
    struct berval cred;
    cred.bv_val = password;
    cred.bv_len = strlen(password);

    // SIMPLE 메커니즘을 사용하여 LDAP 서버에 바인딩(인증)
    // !주의: SIMPLE 메커니즘은 비밀번호를 평문으로 전송하므로, 실제 환경에서는 TLS와 함께 사용해야함.
    rc = ldap_sasl_bind_s(ld, bind_dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
    memset(password, 0, strlen(password));
    
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "LDAP bind failed: %s\n", ldap_err2string(rc));
        ldap_unbind_ext_s(ld, NULL, NULL);
        return 1;
    }

    // 연결 성공
    printf("Successfully connected to LDAP server: %s\n", ldap_uri);
    printf("Authenticated as: [%s]\n", bind_dn);

    // LDAP 연결 종료
    ldap_unbind_ext_s(ld, NULL, NULL);

    return 0;
}

컴파일 및 실행

gcc -o ldap_connect ldap_connect.c -lldap
./ldap_connect

결과 예시


namingContexts
LDAP DIT(Directory Information Tree)에서 특정 서버가 관리하는 루트나 루트들에 해당하는 디렉터리 경로를 나타내는 속성. LDAP 서버는 여러 개의 namingContext를 가질 수 있으며, 각 namingContext는 DIT의 다른 부분을 루트로 하여 관리한다. LDAP Client는 namingContexts를 조회하여 서버가 제공하는 모든 루트 DN을 통해 디렉터리 구조를 파악할 수 있다.

LDAP 서버에서 namingContexts 조회 방법

ldapsearch -x -H ldap://YOUR_LDAP_IP:389 -s base -b "" "(objectClass=*)" namingContexts
  • -H ldap://YOUR_LDAP_UP : LDAP 서버 URI 지정
  • -x : 단순 인증 사용
  • -s base : 검색 범위를 base로 지정
  • -b "" : baseDN을 루트로 지정
  • "(objectClass=*)" : 모든 객체
  • namingContexts : 반환된 결과에서 namingContexts 속성만 표시

예시

  • 해당 LDAP 서버는 dc=ldap,dc=company,dc=com을 루트로 하는 디렉터리 정보를 관리하고 있음을 나타냄
  • 이 정보를 통해 LDAP 서버가 관리하는 디렉터리의 최상위 구조를 알 수 있음.

dc=example,dc=com 아래의 모든 엔트리 검색 예시

ldapsearch -x -H ldap://192.168.1.152:389 -b "dc=example,dc=com" "(objectClass=*)"

#3. [Search] LDAP 사용자 검색 프로그램

uid로 사용자 검색하기

ldap_user_search.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ldap.h>
#include <lber.h>

#define CONFIG_FILE "/etc/test/ldap_test.conf"
#define MAX_LINE 256

int main() {
    FILE *f = fopen(CONFIG_FILE, "r");
    if (!f) {
        perror("Failed to open config file");
        return EXIT_FAILURE;
    }

    char ldap_uri[MAX_LINE] = {0};
    char base_dn[MAX_LINE] = {0};
    char bind_dn[MAX_LINE] = {0};
    char user_base_dn[MAX_LINE] = {0};
    char line[MAX_LINE] = {0};

    // 설정 파일에서 LDAP_URI와 LDAP_BINDDN 읽어오기
    while (fgets(line, sizeof(line), f)) {
        if (sscanf(line, "LDAP_URI = \"%255[^\"]\"", ldap_uri) == 1) continue;
        if (sscanf(line, "LDAP_BASE_DN = \"%255[^\"]\"", base_dn) == 1) continue;
        if (sscanf(line, "LDAP_BIND_DN = \"%255[^\"]\"", bind_dn) == 1) continue;
        if (sscanf(line, "LDAP_USER_BASE_DN = \"%255[^\"]\"", user_base_dn) == 1) continue;
    }
    fclose(f);

    if (!*ldap_uri || !*base_dn || !*bind_dn || !*user_base_dn) {
        fprintf(stderr, "Required configuration not found in [%s] file\n", CONFIG_FILE);
        return EXIT_FAILURE;
    }

    const char *password = getpass("Password: ");
    if (!password || !*password) {
        fprintf(stderr, "Failed to read password\n");
        return EXIT_FAILURE;
    }

    LDAP *ld;
    int rc = ldap_initialize(&ld, ldap_uri);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "ldap_initialize failed: %s\n", ldap_err2string(rc));
        return EXIT_FAILURE;
    }

    int version = LDAP_VERSION3;
    rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "ldap_set_option failed: %s\n", ldap_err2string(rc));
        ldap_unbind_ext_s(ld, NULL, NULL);
        return EXIT_FAILURE;
    }

    struct berval passwd;
    passwd.bv_val = password;
    passwd.bv_len = strlen(password);

    rc = ldap_sasl_bind_s(ld, bind_dn, LDAP_SASL_SIMPLE, &passwd, NULL, NULL, NULL);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "ldap_sasl_bind_s failed: %s\n", ldap_err2string(rc));
        ldap_unbind_ext_s(ld, NULL, NULL);
        return EXIT_FAILURE;
    }

    printf("Successfully bound to LDAP server\n");


    // 사용자에게 검색할 uid 입력 받기
    char search_uid[MAX_LINE] = {0};
    printf("Enter the [uid] to search: ");
    if (!fgets(search_uid, sizeof(search_uid), stdin)) {
        fprintf(stderr, "Failed to read [uid]\n");
        ldap_unbind_ext_s(ld, NULL, NULL);
        return EXIT_FAILURE;
    }
    search_uid[strcspn(search_uid, "\n")] = 0;  // Remove newline

    // uid를 기반으로 검색 필터 설정
    char filter[MAX_LINE];
    snprintf(filter, sizeof(filter), "uid=%s", search_uid);

    // 검색할 속성들
    char *attrs[] = { "cn", "gidNumber", "uidNumber", "createTimestamp", NULL };

    printf("Search BaseDN: [%s]\n", base_dn);
    printf("Search Filter: [%s]\n", filter);

    LDAPMessage *res;
    rc = ldap_search_ext_s(ld, user_base_dn, LDAP_SCOPE_SUBTREE, filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "ldap_search_ext_s failed: %s\n", ldap_err2string(rc));
    } else {
        int count = 0;
        for (LDAPMessage *entry = ldap_first_entry(ld, res); entry != NULL; entry = ldap_next_entry(ld, entry)) {
            count++;
            char *dn = ldap_get_dn(ld, entry);
            printf("Search Result: [%s]\n", dn);
            ldap_memfree(dn);

            BerElement *ber = NULL;
            for (char *attr = ldap_first_attribute(ld, entry, &ber); attr != NULL; attr = ldap_next_attribute(ld, entry, ber)) {
                struct berval **values = ldap_get_values_len(ld, entry, attr);
                if (values != NULL) {
                    printf("%s: %s\n", attr, values[0]->bv_val);
                    ldap_value_free_len(values);
                }
                ldap_memfree(attr);
            }
            if (ber != NULL) ber_free(ber, 0);
            printf("\n");
        }
        if (count == 0) {
            printf("No results found for uid: [%s] in [%s]\n", search_uid, user_base_dn);
        } else {
            printf("Success to find %d result(s) for [uid=%s]\n", count, search_uid);
        }
        ldap_msgfree(res);
    }

    ldap_unbind_ext_s(ld, NULL, NULL);
    return EXIT_SUCCESS;
}

컴파일 및 실행

gcc -o ldap_user_search ldap_user_search.c -lldap -llber
./ldap_user_search

결과 예시


#4. [Login] LDAP 로그인 프로그램

LDAP 서버에 등록된 user id/pw를 입력받아 로그인하기

ldap_login.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ldap.h>

#define CONFIG_FILE "/etc/test/ldap_test.conf"
#define MAX_LINE 256

// CONFIG_FILE을 읽는 함수
int read_config(char *ldap_uri, char *base_dn, char *bind_dn, char *user_base_dn) {
    FILE *f = fopen(CONFIG_FILE, "r");
    if (!f) {
        perror("Failed to open config file");
        return 0;
    }

    char line[MAX_LINE];
    while (fgets(line, sizeof(line), f)) {
        if (sscanf(line, "LDAP_URI = \"%255[^\"]\"", ldap_uri) == 1) continue;
        if (sscanf(line, "LDAP_BASE_DN = \"%255[^\"]\"", base_dn) == 1) continue;
        if (sscanf(line, "LDAP_BIND_DN = \"%255[^\"]\"", bind_dn) == 1) continue;
        if (sscanf(line, "LDAP_USER_BASE_DN = \"%255[^\"]\"", user_base_dn) == 1) continue;
    }
    fclose(f);

    if (!*ldap_uri || !*base_dn || !*bind_dn || !*user_base_dn) {
        fprintf(stderr, "Required configuration not found in [%s] file\n", CONFIG_FILE);
        return 0;
    }

    return 1;
}


int main() {
    char ldap_uri[MAX_LINE] = {0};
    char base_dn[MAX_LINE] = {0};
    char bind_dn[MAX_LINE] = {0};
    char user_base_dn[MAX_LINE] = {0};

    if (!read_config(ldap_uri, base_dn, bind_dn, user_base_dn)) {
        return EXIT_FAILURE;
    }

    printf("LDAP URI: %s\n", ldap_uri);
    printf("LDAP Base DN: %s\n", base_dn);
    printf("LDAP User Base DN: %s\n", user_base_dn);

    char username[MAX_LINE] = {0};
    printf("Enter username: ");
    if (fgets(username, sizeof(username), stdin) == NULL) {
        fprintf(stderr, "Failed to read username\n");
        return EXIT_FAILURE;
    }
    username[strcspn(username, "\n")] = 0;  // Remove newline

    const char *password = getpass("Enter password: ");
    if (!password || !*password) {
        fprintf(stderr, "Failed to read password\n");
        return EXIT_FAILURE;
    }

    LDAP *ld;
    int rc;

    // ldap_initialize: LDAP 세션 초기화 & LDAP 서버에 연결
    // param: LDAP 구조체의 포인터, LDAP URI
    // return: LDAP_SUCCESS 또는 에러 코드
    rc = ldap_initialize(&ld, ldap_uri);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "ldap_initialize failed: %s\n", ldap_err2string(rc));
        return EXIT_FAILURE;
    }

    int version = LDAP_VERSION3;
    // ldap_set_option: LDAP 세션의 옵션 설정
    // param: LDAP 구조체, 옵션 상수, 옵션 값의 포인터
    // return: LDAP_SUCCESS 또는 에러 코드
    rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "ldap_set_option failed: %s\n", ldap_err2string(rc));
        ldap_unbind_ext_s(ld, NULL, NULL);
        return EXIT_FAILURE;
    }

    char user_dn[MAX_LINE * 2];
    snprintf(user_dn, sizeof(user_dn), "cn=%s,%s", username, user_base_dn);

    struct berval cred;
    cred.bv_val = (char *)password;
    cred.bv_len = strlen(password);

    // ldap_sasl_bind_s: LDAP 서버에 바인딩(인증)
    // param: LDAP 구조체, 바인딩할 DN, SASL 메커니즘, 인증 정보, 서버 컨트롤, 클라이언트 컨트롤, 서버 자격 증명
    // return: LDAP_SUCCESS 또는 에러 코드
    rc = ldap_sasl_bind_s(ld, user_dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
    if (rc != LDAP_SUCCESS) {
        fprintf(stderr, "LDAP bind failed: %s\n", ldap_err2string(rc));
        ldap_unbind_ext_s(ld, NULL, NULL);
        return EXIT_FAILURE;
    }

    printf("Successfully logged in as user: %s\n", username);

    // ldap_unbind_ext_s: LDAP 세션을 종료 & 리소스 해제
    // param: LDAP 구조체, 서버 컨트롤, 클라이언트 컨트롤
    // return: LDAP_SUCCESS 또는 에러 코드
    ldap_unbind_ext_s(ld, NULL, NULL);
    return EXIT_SUCCESS;
}

컴파일 및 실행

gcc -o ldap_login ldap_login.c -lldap -llber
./ldap_login

결과 예시

profile
일모도원

1개의 댓글

comment-user-thumbnail
2024년 8월 27일

안되는데요 syyoo씨

답글 달기