PAM(Pluggable Autehntication Module) 의 대화 함수 API 를 사용하는 법을 샘플 프로그램과 함께 살펴 봅니다.

순서

소개

시리즈의 첫번째 글에서 우리는 패스워드-기반의 유저 인증의 기본을 다루었습니다. 인증(authentication)인가(authorisation) 의 뜻을 정의 했고, 로컬파일 기반의 패스워드 저장소와 암호화 알고리즘을 다루었고 패스워드를 읽거나 암호화 하기 위해 솔라리스가 제공하는 API 들을 살펴 보았습니다. 마지막으로 패스워드를 물어 보고 이것을 로그인 패스워드와 비교하는 예제 프로그램을 살펴 보았습니다.

시리즈의 두번째 글에서는 PAM 의 대략적인 모습과 함께 PAM 프레임워크의 다양한 부분들을 살펴 보았습니다. PAM 서비스 모듈들에 대해 이야기 하였고, PAM 설정 파일, /etc/pam.conf, 과 어떻게 서비스 모듈이 쌓여(stack) 질 수 있는지 살펴 보았습니다. 그 후에 PAM API 내에 존재하는 중요한 함수들을 살펴 보았고 마지막으로 첫번째 글에서 작성했었던 샘플 프로그램을 PAM 기반의 프로그램으로 다시 한번 짜 보았습니다.

이 글에서 우리는 대화 함수들에 대해 살펴 보고 또한 PAM API 가 제공하는 몇몇 다른 함수 들에 대해서도 간단히 살펴 볼 것입니다.

대화 함수

이름 그대로 대화 함수(conversation functions) 는 유저와의 대화를 다룹니다. 즉 유저, 서비스 혹은 디바이스로 메세지를 전달하거나 입력을 받습니다. 대화는 여러가지 형태로 이루어질 수 있는데 예를 들어 텍스트 터미널의 "Login: " 프롬프트, 요즘에 아주 일반적인 GUI 로그인 매니저, 혹은 지문 인식기 등도 그 예가 될 수 있습니다.

인증을 위해 PAM 을 사용하는 어플리케이션(PAM consumer 라고 불림) 은 이전 글에서 살펴 보았듯이 pam_start 함수를 호출하여 PAM 세션을 처음 초기화 할때 대화 함수를 등록합니다. PAM 서비스 모듈에 의해 호출되는 대화 함수는 다음의 프로토타입을 가지고 있습니다:

int conv_func (int num_msg, struct pam_message **msg,
struct pam_response **resp, void *app_data);

여기서:

  • num_msg 매개변수는 함수에 전달되는 메세지의 총 갯수를 나타 냅니다 (반드시 0 과 PAM_MAX_NUM_MSG 사이의 값이 되어야 함)
  • msg 는 유저에게 전달할 메세지를 저장하고 있는 버퍼의 포인터를 나타냅니다 (예를 들어 패스워드를 묻는 프롬프트)
  • resp 는 유저에게서 입력받은 메세지를 저장하고 있는 포인터를 나타냅니다 (예를 들어 입력받은 패스워드)
  • app_data 는 어플리케이션-특수한 데이타를 저장하고 있는 버퍼의 포인터를 나타냅니다

PAM 서비스 모듈은 msg 에 의해 사용되는 메모리를 할당하거나 할당해지 하는데 책임이 있고 그에 반해 resp 는 어플리케이션에 의해 할당 되고 서비스 모듈에 의해 할당해지 됩니다.

우리가 작성하는 대화 함수들은 반드시 PAM 이 유저와 어떤식으로 대화할지에 대한 가정을 해서는 안됩니다. 대신 대화 함수는 반드시 작업이 완료 될때 까지 메세지를 주고 받아야 합니다. 또한 PAM 모듈의 어떠한 메세지도 반드시 어떠한 수정도 없이 그대로 내보내 져야 합니다. (서비스 모듈은 그들의 고유 메세지 로컬라이제이션에 책임이 있음) 개개인의 메세지는 자유 형식이고 복수개의 라인, 공백 그리고 컨트롤 문자들을 포함할 수 있습니다.

메세지들은 pam_message 구조체에 저장되고 다음과 같은 멤버를 가지고 있습니다:

struct pam_message {
int msg_style;
char *msg;
};

msg 멤버는 실제 메세지를 가르키고 있습니다. 메세지의 타입은 msg_style 에 의해 지정되고 다음의 4가지 값중 하나가 될 수 있습니다:

  • PAM_PROMPT_ECHO_OFF: 유저에게 프롬프트를 나타내고 응답의 에코를 비활성화 함.
  • PAM_PROMPT_ECHO_ON: 유저에게 프롬프트를 나타내고 응답을 에코시킴.
  • PAM_ERROR_MSG: 에러메세지를 출력함.
  • PAM_TEXT_INFO: 일반적인 정보 메세지를 출력함.

유사하게 인증 모듈에서의 응답은 pam_response 구조체에 저장되고 다음과 같은 멤버를 가지고 있습니다:

struct pam_response {
     char *resp;
int resp_retcode;
};

resp 멤버는 실제 응답을 포함하고 있고 resp_retcode 는 리턴 코드를 포함하고 있습니다. 현재 후자는 사용되고 있지 않고 반드시 0 으로 설정되어야 합니다. 만약 대화 함수가 에러를 리턴하였다면 response 포인터는 반드시 NULL 로 설정되어야 합니다.

대화 함수는 또 다른 책임을 가지고 있습니다: PAM_PROMPT_ECHO_OFFPAM_PROMPT_ECHO_ON 메세지들은 종료를 뜻하는 newline 문자들을 떼어내야 하고, PAM_ERROR_MSGPAM_TEXT_INFO 메세지에 적절하게 newline 캐릭터를 추가시켜야 합니다.

대화 함수의 예제

이제까지 대화 함수와 그들이 반드시 해야할 작업에 대해 살펴 보았습니다. 이제 예제를 살펴 봅시다. 소스 파일에는 두개의 함수가 존재 합니다. 첫번째는 도우미 함수로 free_resp 라는 이름을 가지고 있고 에러 이벤트시에 응답을 할당해제 합니다 그리고 두번째 함수는 대화 함수 그 자체 입니다.

소스 파일의 처음 몇줄의 라인에는 우리가 사용하는 다양한 헤더 파일들이 포함됩니다:

1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <pwd.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <strings.h>
8 #include <security/pam_appl.h>

free_resp 의 소스 코드 입니다:

 9 static void free_resp (int num_msg, struct pam_response *resp)
10 {
11 int i;
12 struct pam_response *r;

13 if (resp == NULL)
14 return;

15 r = resp;

16 for (i = 0; i < num_msg; i++, r++) {
17 if (r->resp) {
18 bzero (r->resp, strlen (r->resp));
19 free (r->resp);
20 r->resp = NULL;
21 }
22 }

23 free (resp);
24 }

이 16줄 짜리 프로그램을 살펴 봅시다.

13-14: 만약 resp 포인터가 NULL 이면 어떠한 작업도 할 필요가 없고 그러므로 리턴합니다.

16-22: 각 메세지를 순환합니다. 만약 메세지가 NULL 이 아니면 메모리를 0 으로 설정하고 할당해제 합니다. 할당해지 하기 전에 메모리를 청소 하는데 이것은 패스워드 데이타 같은 민감한 데이타를 포함했을 수도 있기 때문입니다.

23: 최종적으로 첫번째 응답을 할당해제합니다.

아래는 우리의 대화 함수의 소스 코드 입니다. 시리즈의 이전 글의 함수의 좀더 향상된 버전입니다.

25 int check_conv (int num_msg, struct pam_message **msg,
26 struct pam_response **resp, void *app_data)
27 {
28 int i;
29 struct pam_message *m;
30 struct pam_response *r;
31 char *ct_passwd;

32 m = *msg;

33 if ((num_msg <= 0) || (num_msg >= PAM_MAX_NUM_MSG)) {
34 fprintf (stderr, "Invalid number of messages\n");
35 *resp = NULL;
36 return (PAM_CONV_ERR);
37 }

38 if ((*resp = r = calloc (num_msg, sizeof (struct pam_response))) == NULL)
39 return (PAM_BUF_ERR);

40 for (i = 0; i < num_msg; i++, m++, r++) {
41 if (m->msg == NULL) {
42 fprintf (stderr, "Message %d: %d/NULL\n", i, m->msg_style);
43 goto err;
44 }

45 if (m->msg[strlen (m->msg)] == '\n')
46 m->msg[strlen (m->msg)] = '\0';

47 r->resp = NULL;
48 r->resp_retcode = 0;

49 switch (m->msg_style) {
50 case PAM_PROMPT_ECHO_OFF:
51 ct_passwd = getpassphrase (m->msg);
52 r->resp = strdup (ct_passwd);
53 break;

54 case PAM_PROMPT_ECHO_ON:
55 printf ("%s", m->msg);
56 break;

57 case PAM_ERROR_MSG:
58 fprintf (stderr, "%s\n", m->msg);
59 break;

60 case PAM_TEXT_INFO:
61 printf ("%s\n", m->msg);
62 break;
63 }
64 }

65 return (PAM_SUCCESS);

66 err:
67 free_resp (i, r);
68 *resp = NULL;

69 return (PAM_CONV_ERR);
70 }

46줄의 프로그램을 좀 더 자세히 살펴 봅시다.

33-37: 유요한 메세지의 갯수가 제공되었는지 (이것은 0과 PAM_MAX_NUM_MSG 사이의 숫자) 검증합니다.

38-39: 응답을 위한 버퍼를 할당합니다.

41-44: 우리가 전달한 모든 메세지에 메세지 포인터가 NULL 일 경우 에러를 플래깅 합니다.

45-46: 최후의 newline 캐릭터를 수정합니다: 텍스트가 프롬프트라는 가정하에서 제거 됩니다. 만약 텍스트가 메시지라면 newline 캐릭터가 텍스트가 디스플레이 될 때 추가 됩니다.

47-48: response 구조체를 초기화 합니다.

49-63: 만약 메세지 스타일이 PAM_PROMPT_ECHO_OFF 이라면 getpassphrase 를 호출하여 m->msg 에 저장된 프롬프트를 디스플레이하고 유저로부터 패스워드를 에코하지 않고 수집합니다. 만약 메세지 스타일이 PAM_PROMPT_ECHO_ON 이면 단순히 m->msg 에 저장된 메세지를 출력합니다. (알아둘 점으로 실제 대화 함수는 이 작업 후에 유저의 응답을 읽어들일 것입니다. 그러나 우리의 간단한 예제에서는 이것을 무시합니다) 만약 메세지 스타일이 PAM_ERROR_MSG 혹은 PAM_TEXT_INFO 라면 m->msg 에 저장된 메세지를 newline 캐릭터와 함께 출력할 것입니다. 전자의 경우에는 출력이 stderr 로 전달 되고 후자의 경우에는 stdout 으로 전달됩니다.

65: 모든 메세지를 성공적으로 다루었으므로 성공을 리턴합니다.

66-69: 에러가 발생하였으므로 청소 작업을 한 후에 실패를 리턴합니다.

시리즈의 첫번째 글에서 사용했던 버전과 출력을 완전히 동일하게 만들기 위해서 Part 2의 프로그램에서 다음의 라인을 수정하였습니다:


ct_passwd = getpassphrase ("Enter password ");

다른 PAM API 함수들

이제까지 대화형 함수에 대해 살펴 보았습니다. PAM API 가 제공하는 몇몇 다른 함수들도 간단하게 살펴 봅시다:

  • pam_acct_mgmt
  • pam_open_session
  • pam_close_session
  • pam_setcred
  • pam_set_item
  • pam_get_item
pam_acct_mgmt 함수

pam_acct_mgmt 함수는 계정 검증(account validation)절차를 수행 하고 다음의 프로토타입을 가지고 있습니다:

#include <security/pam_appl.h>
int pam_acct_mgmt (pam_handle_t *pamh, int flags);

이 함수는 계정의 유효성을 검사 하고(예를 들어 패스워드와 어카운트가 만료되지는 않았는지 그리고 접근 시간 제한일 위반하지는 않았는지) 일반적으로 유저가 인증된 후에 호출 됩니다. ( pam_authenticate 를 호출함으로써)

pam_open_session 과 pam_close_session 함수

세션을 시작하거나 종료하는 PAM consumer 는 반드시 이 함수들 중에 하나를 호출해야 합니다.

#include <security/pam_appl.h>
int pam_open_session (pam_handle_t *pamh, int flags);
int pam_close_session (pam_handle_t *pamh, int flags);

유저가 성공적으로 pam_authenticatepam_acct_mgmt 를 사용해서 유저가 성공적으로 인증된 다음에는 pam_open_session 이 프로그램이 새로운 세션을 만들때 호출되어야 합니다. 이 작업으로 인해 세션 모듈에 새로운 세션에 관해서 통보가 됩니다. 반대로 세션이 종료 되면 pam_close_session 이 반드시 호출 되어서 세션 모듈이 통보를 받을 수 있어야 합니다.

pam_setcred 함수

인증 서비스를 위한 유저의 암호(credential) 은 pam_setcred 함수에 의해 수정 됩니다.

#include <security/pam_appl.h>
int pam_setcred (pam_handle_t *pamh, int flags);

유저가 인증되고 세션이 열린 다음에 pam_setcred 함수는 그들의 암호를 생성하고 수정하고 삭제 하는데 사용 됩니다.

pam_set_itempam_get_item 함수

PAM consumer 와 서비스 모듈은 PAM 정보를 pam_set_itempam_get_item 를 사용해서 다룰 수 있습니다.

#include <security/pam_appl.h>
int pam_set_item (pam_handle_t *pamh, int item_type, const void *item);
int pam_get_item (const pam_handle_t *pamh, int item_type, void **item);

어플리케이션과 서비스 모듈은 AM 정보를 pam_set_item 을 이용해서 수정할 수 있습니다. 수정할 정보의 타입은 item_type 매개변수를 통해 지정 되고 복수개의 타입이 될 수도 있습니다. 타입 예제의 종류는 PAM 서비스 이름, 유저 이름, tty 이름, 그리고 유저 인증 토큰등을 포함합니다. 전체 아이템 타입의 리스트는 pam_set_item 멘페이제에서 확인 하실 수 있습니다, pam_set_item(3PAM).

각각의 정보 타입의 값들은 pam_get_item 을 이용해서 접근할 수 있습니다.

공간의 제약으로 인해 이러한 함수의 예제들을 보여 드릴 수는 없지만 관심이 있는 독자들은 오픈솔라리스 소스 코드 에서 적절한 함수 이름을 검색하여 찾아보실 수 있습니다.

요약

이 글에서 우리는 PAM 대화 함수들에 대해 살펴 보았고 그들이 무엇을 해야 하는지도 살펴 보았습니다. (즉 대화 함수에 기대되는 것이 어떤 것인지) 우리는 대화 함수는 메세지를 다룬다는 것을 알았고 다양한 메세지 타입에 관해서도 알아 보았습니다.

그 다음에 우리는 꽤 간단한 범용 대화 함수의 예제를 살펴 보았고 이것은 Part 2에서 다루었던 것 보다 좀 더 향상된 버전의 예제였습니다.

마지막으로 PAM PI 가 제공하는 몇몇 다른 함수들에 대해 살펴 보았습니다: pam_acct_mgmt, pam_open_session, pam_close_session, pam_setcred, pam_set_item, 그리고 pam_get_item.

이 시리즈의 다음 글(이자 마지막 글)에서는 PAM 서비스 모듈을 작성하는 방법을 다룰 것입니다.

감사의 인사

이 글을 리뷰해준 Glenn Brunette 에게 감사 드립니다.



참고자료 및 추천 글
저자에 관하여

Rich Teer 는 My Online Home Inventory 의 CEO 이며 독립 솔라리스 컨설턴트로 솔라리스 커뮤니티의 10년 이상 활동한 멤버 입니다. 썬의 베스트 셀러인 Solaris Systems Programming 의 저자이고 다양한 글을 작성했습니다. 오픈솔라리스 파일럿 프로그램의 멤버였고 현재는 오픈솔라리스 Governing Board(OGB) 중에 한명입니다. Rich 는 현재 Britich Columbia, Kelowna 에 그의 와이프 Jenny 와 살고 있습니다. 그의 웹 사이트는 www.rite-group.com/rich 입니다.



이 아티클의 영문 원본은
http://developers.sun.com/solaris/articles/user_auth_solaris3.html?cid=e4149
에서 볼수 있습니다.

크리에이티브 커먼즈 라이센스
Creative Commons License

트랙백 주소 :: http://sdnkorea.com/blog/trackback/461

댓글을 달아 주세요

댓글을 쓰시려면 로그인해주세요.

블로그 이미지
Statistics Graph
회사소개  |   문의  |   사용약관  |   개인정보보호정책  |   등록상표