먼저 모듈에서 Linux-PAM
라이브러리 및 Linux-PAM
기반 응용에게 기대할 것들을 다룬다. 핵심은
libpam.*
라이브러리다.
#include <security/pam_modules.h>
int pam_set_data( | pamh, | |
module_data_name, | ||
data, | ||
(*cleanup)(pam_handle_t *pamh, void *data, int error_status)) ; |
pam_handle_t *pamh
;const char *module_data_name
;void *data
;void (*cleanup)(pam_handle_t *pamh, void *data, int error_status)
;
pam_set_data
함수는
pamh 인자로 지정한
PAM 문맥 내에서 (바라건대) 유일한 문자열
module_data_name에
객체 포인터를 연계한다.
PAM 모듈이 동적 적재 오브젝트일 수 있다. 일반적으로 그런 파일들에는
static 변수가 있으면 안 된다.
이 함수와
pam_get_data(3)
함수는 모듈에서 핸들 pamh에
어떤 데이터를 연계시킬 수 있는 메커니즘을 제공한다. 보통 모듈에서
pam_set_data
함수를 호출해서 (바라건대)
유일한 module_data_name에다가
어떤 데이터를 등록한다. 그 데이터를 다른 모듈에서도 이용할 수 있다.
단, 응용에서는 이용할 수 없다.
이 함수는 data에 대한 포인터를
저장할 뿐이므로 모듈에서 그 내용물을 변경하거나 메모리를 해제해선
안 된다.
함수 cleanup()
이
data에 연계되어서,
그 데이터를 덮어 쓸 때나
pam_end(3)
호출 다음에 NULL이 아니면 그 함수를 호출한다.
error_status 인자를 사용해 이 데이터 항목을 정리하기 위해 모듈에서 취해야 할 행동의 종류를 나타낸다. 예를 들어 커베로스에서 인증 과정 중에 티켓 파일을 만드는데, 그 파일을 데이터 항목에 연계해 둘 수 있을 것이다. pam_end(3)가 호출될 때 error_status는 pam_authenticate(3) 또는 해당하는 다른 libpam 함수의 반환 값을 담는다. 그 값에 따라서 커베로스 모듈에서 (인증 실패인 경우) 티켓 파일을 삭제하거나, 그대로 둘 수 있다.
error_status가 다음 두 값 중 어느 쪽과도 논리 OR 되어 있을 수 있다.
데이터 항목을 (두 번째 pam_set_data
호출을 통해) 교체할 때 이 마스크를 쓴다. 이 마스크가 없으면
pam_end(3)로
인한 호출로 보면 된다.
프로세스에서 cleanup()
을 조용하게
수행하기를 원한다는 표시다. 즉 사용자에게 로그/메시지를 찍지
말아야 한다.
#include <security/pam_modules.h>
int pam_get_data( | pamh, | |
module_data_name, | ||
data) ; |
const pam_handle_t *pamh
;const char *module_data_name
;const void **data
;이 함수와 pam_set_data(3) 함수를 함께 사용해서 호출자 PAM 모듈에서만 의미 있는 모듈별 데이터를 관리할 수 있다.
pam_get_data
함수는
pamh 인자로 지정한
PAM 문맥 내에서 (바라건대) 유일한 문자열
module_data_name에
연계된 객체를 찾는다.
pam_get_data
호출이 성공하면
data가 객체를 가리키게 된다.
참고로 그 데이터는 사본이 아니므로
모듈에서 상수처럼 취급해야 한다.
#include <security/pam_modules.h>
int pam_set_item( | pamh, | |
item_type, | ||
item) ; |
pam_handle_t *pamh
;int item_type
;const void *item
;
pam_set_item
함수를 사용해 응용과
PAM 서비스 모듈에서 item_type이
나타내는 PAM 정보에 접근해 값을 갱신할 수 있다. 이를 위해
item 인자가 가리키는 객체의
사본을 만든다. 다음 item_type을
지원한다.
서비스 이름. (PAM 함수들에서 프로그램을 인증하는 데 이용할 PAM 스택을 나타낸다.)
사용자 이름. 그 신원으로 서비스가 제공된다. 즉, 인증 후에 PAM_USER는 서비스를 이용하게 된 로컬 개체를 나타낸다. 참고로 PAM 스택의 모듈에서 이 값을 뭔가(예: "anonymous")에서 다른 뭔가(예: "guest119")로 매핑할 수 있다. 그러므로 응용에선 PAM 함수를 호출한 다음에 PAM_USER의 값을 확인해 봐야 한다.
사용자 이름을 물을 때 보일 문자열. 이 문자열의 기본값은 "login: "의 지역화 버전이다.
터미널 이름. 장치 파일이면 앞에 /dev/
가
붙는다. X 기반의 그래픽 응용에서 이 항목의 값은
$DISPLAY 변수여야 한다.
요청하는 사용자의 이름. 로컬에서 요청하는 사용자면 로컬의 이름이고 원격에서 요청하는 사용자면 원격의 사용자 이름.
일반적으로 응용 내지 모듈에서는 강력하게 인증된 쪽을 (원격 계정보다는 로컬 계정을) 값으로 제공하려 하게 된다. 이 값의 신뢰성은 응용에 결부된 실제 인증 스택에 따라 정해지고, 그래서 궁극적으로 시스템 관리자에게 달려 있다.
PAM_RUSER@PAM_RHOST가 항상 요청하는 사용자를 나타낼 것이다. 일부 경우에는 PAM_RUSER가 NULL일 수 있다. 그 경우는 요청하는 개체가 누구인지 분명하지 않은 것이다.
요청하는 호스트의 이름 (PAM_RUSER 개체가 서비스를 요청하고 있는 머신의 호스트명). 즉 PAM_RUSER@PAM_RHOST가 요청하는 사용자를 나타낸다. 일부 응용에서는 PAM_RHOST가 NULL일 수 있다. 그 경우는 인증 요청이 어디서 오는 것인지 분명하지 않은 것이다.
인증 토큰 (보통은 패스워드). pam_sm_authenticate(3) 및 pam_sm_chauthtok(3)을 제외한 다른 모듈 함수에선 이 토큰을 무시해야 한다. 앞쪽 함수에선 한 모듈에서 다른 모듈로 최신 인증 토큰을 전달하는 데 쓴다. 뒤쪽 함수에선 다른 용도로 쓰는데, 현재 활성인 인증 토큰을 담는다.
이전 인증 토큰. pam_sm_chauthtok(3)을 제외한 다른 모듈 함수에선 이 토큰을 무시해야 한다.
pam_conv 구조체. pam_conv(3) 참고.
다음 추가 항목들은 Linux-PAM 한정이므로 이식 가능한 응용에선 쓰지 말아야 한다.
실패 시 지연 공통 동작 방식을 바꾸기 위한 함수 포인터. pam_fail_delay(3) 참고.
X 디스플레이 이름. X 기반의 그래픽 응용에서 이 항목의 값은 $DISPLAY 변수여야 한다. PAM_TTY와 독립적으로 디스플레이 이름을 전달하는 데 이 값을 쓸 수 있다.
필요시 PAM_XDISPLAY에 지정된 디스플레이로 연결하기 위해 필요한 X 인증 데이터를 담은 구조체에 대한 포인터. pam_xauth_data(3) 참고.
기본적으로 모듈에서 패스워드를 요청할 때 "New UNIX password: " 및 "Retype UNIX passwords: " 프롬프트를 쓴다. 여기서 단어 UNIX를 이 항목으로 바꿀 수 있으며, 기본은 비어 있다. pam_get_authtok(3)에서 이 항목을 사용한다.
PAM_CONV와 PAM_FAIL_DELAY를 제외한 모든 item_type에서
item은 <NUL> 종료
문자열에 대한 포인터다. PAM_CONV에서 item은
초기화된 pam_conv 구조체를 가리킨다.
PAM_FAIL_DELAY의 경우 item은
void (*delay_fn)(int retval, unsigned usec_delay, void *appdata_ptr)
함수 포인터다.
PAM_AUTHOK과 PAM_OLDAUTHTOK 모두 응용으로 반환하기 전에 재설정된다. 즉 응용에서는 인증 토큰에 접근할 수 없다.
#include <security/pam_modules.h>
int pam_get_item( | pamh, | |
item_type, | ||
item) ; |
const pam_handle_t *pamh
;int item_type
;const void **item
;
pam_get_item
함수를 사용해 응용과
PAM 서비스 모듈에서 item_type이
나타내는 PAM 정보에 접근해 값을 가져올 수 있다. 성공 반환 시
item이 해당 항목의 값에 대한
포인터를 담고 있다. 참고로 실제
데이터에 대한 포인터이므로 free()
하거나 덮어 쓰면 안 된다.
item_type으로 다음 값들을 지원한다.
서비스 이름. (PAM 함수들에서 프로그램을 인증하는 데 이용할 PAM 스택을 나타낸다.)
사용자 이름. 그 신원으로 서비스가 제공된다. 즉, 인증 후에 PAM_USER는 서비스를 이용하게 된 로컬 개체를 나타낸다. 참고로 PAM 스택의 모듈에서 이 값을 뭔가(예: "anonymous")에서 다른 뭔가(예: "guest119")로 매핑할 수 있다. 그러므로 응용에선 PAM 함수를 호출한 다음에 PAM_USER의 값을 확인해 봐야 한다.
사용자 이름을 물을 때 보일 문자열. 이 문자열의 기본값은 "login: "의 지역화 버전이다.
터미널 이름. 장치 파일이면 앞에 /dev/
가
붙는다. X 기반의 그래픽 응용에서 이 항목의 값은
$DISPLAY 변수여야 한다.
요청하는 사용자의 이름. 로컬에서 요청하는 사용자면 로컬의 이름이고 원격에서 요청하는 사용자면 원격의 사용자 이름.
일반적으로 응용 내지 모듈에서는 강력하게 인증된 쪽을 (원격 계정보다는 로컬 계정을) 값으로 제공하려 하게 된다. 이 값의 신뢰성은 응용에 결부된 실제 인증 스택에 따라 정해지고, 그래서 궁극적으로 시스템 관리자에게 달려 있다.
PAM_RUSER@PAM_RHOST가 항상 요청하는 사용자를 나타내게 된다. 일부 경우에는 PAM_RUSER가 NULL일 수 있다. 그 경우는 요청하는 개체가 누구인지 분명하지 않은 것이다.
요청하는 호스트의 이름 (PAM_RUSER 개체가 서비스를 요청하고 있는 머신의 호스트명). 즉 PAM_RUSER@PAM_RHOST가 요청하는 사용자를 나타낸다. 일부 응용에서는 PAM_RHOST가 NULL일 수 있다. 그 경우는 인증 요청이 어디서 오는 것인지 분명하지 않은 것이다.
인증 토큰 (보통은 패스워드). pam_sm_authenticate(3) 및 pam_sm_chauthtok(3)을 제외한 다른 모듈 함수에선 이 토큰을 무시해야 한다. 앞쪽 함수에선 한 모듈에서 다른 모듈로 최신 인증 토큰을 전달하는 데 쓴다. 뒤쪽 함수에선 다른 용도로 쓰는데, 현재 활성인 인증 토큰을 담는다.
이전 인증 토큰. pam_sm_chauthtok(3)을 제외한 다른 모듈 함수에선 이 토큰을 무시해야 한다.
pam_conv 구조체. pam_conv(3) 참고.
다음 추가 항목들은 Linux-PAM 한정이므로 이식 가능한 응용에선 쓰지 말아야 한다.
실패 시 지연 공통 동작 방식을 바꾸기 위한 함수 포인터. pam_fail_delay(3) 참고.
X 디스플레이 이름. X 기반의 그래픽 응용에서 이 항목의 값은 $DISPLAY 변수여야 한다. PAM_TTY와 독립적으로 디스플레이 이름을 전달하는 데 이 값을 쓸 수 있다.
필요시 PAM_XDISPLAY에 지정된 디스플레이로 연결하기 위해 필요한 X 인증 데이터를 담은 구조체에 대한 포인터. pam_xauth_data(3) 참고.
기본적으로 모듈에서 패스워드를 요청할 때 "New UNIX password: " 및 "Retype UNIX passwords: " 프롬프트를 쓴다. 여기서 단어 UNIX를 이 항목으로 바꿀 수 있으며, 기본은 비어 있다. pam_get_authtok(3)에서 이 항목을 사용한다.
서비스 모듈에서 사용자의 이름을 얻고 싶으면 이 함수를 쓰지 말고 pam_get_user(3) 호출을 수행해야 한다.
서비스 모듈에서만 PAM_AUTHTOK 및 PAM_OLDAUTHTOK으로 인증 토큰을 읽을 권한이 있다.
#include <security/pam_modules.h>
int pam_get_user( | pamh, | |
user, | ||
prompt) ; |
const pam_handle_t *pamh
;const char **user
;const char *prompt
;
pam_get_user
함수는
pam_start(3)로
지정한 사용자 이름을 반환한다. 사용자를 지정하지 않았으면
pam_get_item (pamh, PAM_USER, ... );
의
반환 값을 반환한다. 그 값이 NULL이면
pam_conv(3)
메커니즘을 통해 사용자 이름을 얻는데, 다음 중 NULL 아닌
첫 문자열로 사용자에게 묻는다.
함수에 준 prompt 인자.
pam_get_item (pamh, PAM_USER_PROMPT, ... );의 반환 값.
기본 프롬프트 "login: "
어떤 방법으로든 얻은 사용자 이름에 대한 포인터가 *user 값으로 반환된다. 참고로 그 메모리를 모듈에서 free() 하거나 변경해선 안 된다.
이 함수에서는 pam_set_item(3) 및 pam_get_item(3) 함수 관련 PAM_USER 항목을 설정한다.
#include <security/pam_appl.h>
struct pam_message { int msg_style; const char *msg; }; struct pam_response { char *resp; int resp_retcode; }; struct pam_conv { int (*conv)(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr); void *appdata_ptr; };
PAM 라이브러리에선 응용에서 정의한 콜백을 이용해 응용과 적재된 모듈이 직접 소통할 수 있게 한다. 트랜잭션 시작 때 pam_start(3)에 주는 struct pam_conv로 콜백을 지정한다.
모듈에서 해당 conv() 함수를 호출할 때 그 구조체의 두 번째 항목을 appdata_ptr 인자에 설정한다.
conv() 호출의 나머지 인자들은 모듈과 응용 사이에 주고받는 정보에 대한 것이다. 말하자면 num_msg는 포인터의 배열인 msg의 길이를 담는다. 성공 반환 시 포인터 resp가 pam_response 구조체의 배열을 가리키는데, 그 구조체들은 응용에서 제공한 텍스트를 담는다. 그 구조체의 resp_retcode 멤버는 쓰지 않으며 0으로 설정해야 한다. 그 배열과 응답들을 free(3)로 해제하는 건 호출자의 책임이다. *resp가 포인터의 배열이 아니라 struct pam_response 배열이라는 점에 유의하자.
응답의 개수는 언제나 대화 함수 인자 num_msg와 같다. 따라서 대화 함수 호출 후마다 응답 배열이 free(3) 돼야 한다. 응답들의 인덱스는 pam_message 배열 프롬프트의 인덱스와 대응한다.
실패 시 대화 함수는 할당한 자원이 있으면 모두 해제한 후 미리 규정된 PAM 오류 코드들 중 하나를 반환해야 한다.
각 메시지는 네 가지 타입 중 하나이며, struct pam_message의 msg_style 멤버에 지정돼 있다.
텍스트 반향 없이 문자열 얻기.
텍스트 반향하면서 문자열 얻기.
오류 메시지 표시하기.
어떤 텍스트 표시하기.
메시지가 배열로 돼 있으므로 모듈에서 한 번의 호출로 여러 가지를 응용으로 전달하는 게 가능해진다. 응용 입장에서도 관련 항목들이 한번에 오는 게 편할 수 있다. 그 경우 창 형태 응용에서 양식 하나로 여러 메시지/프롬프트를 한꺼번에 표시할 수 있다.
여담으로, Linux-PAM에서 대화 함수 인자 const struct pam_message **msg를 다루는 방식이 솔라리스의 PAM과 (또 그에 기반한 HP/UX 등과) 차이가 있다는 점을 언급해 둬야겠다. Linux-PAM은 msg 인자를 프로토타입 const struct pam_message *msg[]와 완전히 동등하게 해석한다. (다들 아는 main() 함수의 argv 인자에 흔히 쓰는 프로토타입이 char **argv와 char *argv[]인 것과 같은 식이다.) 요컨데 Linux-PAM에서는 msg 인자를 읽기 전용 'struct pam_message' 포인터 num_msg 개짜리 배열에 대한 포인터로 해석한다. 솔라리스 PAM 구현에서는 이 인자를 pam_message 구조체 num_msg개짜리 배열에 대한 포인터에 대한 포인터로 해석한다. 대부분 모듈 응용 개발자에게는 (아마도) 다행스럽게도 num_msg의 값이 1일 때 두 정의는 완전히 동등하다. 그러다 별 생각 없이 그 수를 2개로 올리면 예상치 않은 호환성 문제가 발생한다.
어느 쪽이 바람직한가는 제쳐 두고, 모듈 작성자가 두 가지 PAM 구현 모두와 소스 수준 호환성을 유지하기 위한 방법으로 두 가지가 있다.
절대 num_msg를 1보다 크게 해서 대화 함수를 호출하지 않기.
msg를 이중 참조 구조로 만들어서 두 가지 대화 함수 모두 메시지를 찾아낼 수 있게 하기. 즉 다음처럼 만들기:
msg[n] = & (( *msg )[n])
#include <security/pam_appl.h>
int pam_putenv( | pamh, | |
name_value) ; |
pam_handle_t *pamh
;const char *name_value
;
pam_putenv
함수를 이용해
pamh 핸들에 연계된
PAM 환경 변수의 값을 추가하거나 바꾼다.
pamh 인자는 앞선 pam_start() 호출로 얻은 인증 핸들이다. name_value 인자는 단일 NUL 종료 문자열이며, 다음 중 한 형태다.
이 경우 이름이 NAME인 환경 변수를 변수 값으로 설정한다. 이미 있는 변수면 덮어 쓴다. 아니면 PAM 환경에 추가한다.
변수를 빈 값으로 설정한다. 그렇게 설정하려면 이 방식으로 해야 한다는 걸 보이기 위해 따로 나열한다.
'='가 없으면 pam_putenv
()
함수에서 해당 변수를 PAM 환경에서 삭제하게 된다.
pam_putenv
()는
name_value의 사본을
가지고 동작한다. 따라서
putenv(3)와
달리 응용에서 데이터의 메모리를 해제할 책임이 있다.
#include <security/pam_appl.h>
const char *pam_getenv( | pamh, | |
name) ; |
pam_handle_t *pamh
;const char *name
;
pam_getenv
함수는
pamh 핸들에 연계된
PAM 환경 목록에서 name이
가리키는 문자열과 일치하는 항목을 찾아서 그 환경 변수의 값에 대한
포인터를 반환한다. 응용에서 그 데이터의 메모리를 해제해선 안 된다.
#include <security/pam_appl.h>
char **pam_getenvlist( | pamh) ; |
pam_handle_t *pamh
;
pam_getenvlist
함수는
pamh 핸들에 연계된
PAM 환경의 전체 사본을 반환한다. 그 PAM 환경 변수들이
사용자가 인증돼서 서비스가 인가됐을 때의 일반 환경 변수들을
내용을 나타낸다.
메모리 형식은 char 포인터들의 malloc() 한 배열이다. 마지막 항목은 NULL로 설정돼 있다. 그 배열의 NULL 아닌 항목 각각이 "name=value" 형식으로 된 NUL 종료이고 malloc() 된 문자열을 가리킨다.
이 메모리를 libpam에서 절대 free() 하지 않는다는 점에
유의해야 한다. pam_getenvlist
호출로 얻은 후에는 호출한 응용에서 그 메모리를 free() 할
책임이 있다.
반환되는 배열의 형식과 내용이 execle(3) 함수 호출 세 번째 인자의 요건과 일치하는 건 우연이 아니라 의도된 것이다.