Есть система состоящая из почтового сервера и сервера оракл
Написал протой pam модуль который висит на mail сервере и является socket клиентом для приложения - socket сервера, которое в свою очередь висит на oracle сервере.
Pam модуль осуществляет аутентификацию пользователя на оракл сервере.Есть две проблемы
1) Для работы моего модуля почему-то необходимо, чтобы в файле /etc/passwd и /etc/master.passwd существовала запись с аккаунтом пользователя. Не пойму как сделать так, чтобы был чистый Pam без создания записей в /etc/passwd
2) Поскольку я на си никогда не писал, то в моих программах возможно есть куча ошибок. Предлагаю переработать исходный код совместноТеперь подробности реализации -
сервер oracle 8.1.7.4 на базе linux redhat 7.3
для доступа к серверу используется библиотека
libsqlora8-2.2.10Со стороны почтового сервера -
FreeBSD 5.0
imap-uw (ipop3d и imapd) собранные с WITHOUT_SSL=yes (с ssl тоже работает, я проверял)Вот листинги программ (прошу не бить ногами, я даже не знаю, как Makefile нарисовать поэтому покажу как компилировал ниже)
----Oracle кусок----
oracle_connector.c
----Oracle кусок----#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <stdarg.h>
#include <alloca.h>
#include <string.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>#include <sqlora.h>
#define DEBUG
#define ERR -1
#define SUCCESS 1
#define BUFSZ 128struct optionstruct {
char where[257];
char connectString[65];
char table[30];
char usercolumn[17];
char passwdcolumn[30];};
struct optionstruct options = {
"uss_sl_type='MAIL'",
"user/password@servername",
"abs.ct_user_services",
"uss_logname",
"uss_password"
};/* Global Variables */
int dbh = -1;int db_connect (void);
void db_close( void );void db_close ( void )
{
if (dbh < 0)
{
return; /* closed already ? */
}
sqlo_finish(dbh);
dbh = -1;
}int db_connect ( void )
{
int retvalue = ERR;
char* conn_string;
int conn_count;
retvalue = -1;
if ( dbh >= 0 )
return SUCCESS;
sqlo_init(0);
if (0 <= (dbh = sqlo_connect(&dbh, options.connectString)))
{
retvalue = SUCCESS;
}
if ( retvalue != SUCCESS )
{
syslog(LOG_INFO, "oracle_connector: Oracle err %s\n", sqlo_geterror(dbh));
}
return retvalue;
}
char *db_getpasswd (const char *user)
{
char *p;
char *sql;
char *escapeUser; // User provided stuff MUST be escaped
int ncount;
const char *retvalue = NULL;
int sth;
const char **v;sql = (char *) malloc (110 + strlen(user) + strlen(options.where));
if ( !sql )
return "ERR";escapeUser = malloc(sizeof(char) * (strlen(user) * 2) + 1);
if ( escapeUser == NULL )
{
syslog(LOG_ERR, "oracle_connector: Insufficient memory to allocate user or password escape strings");
syslog(LOG_ERR, "oracle_connector: UNABLE TO AUTHENTICATE");
return "ERR";
}
escapeUser = (char*) user;
// D (("options.crypt=%d|\n", options.crypt));
sprintf(sql, "select %s from %s where %s='%s' ",
options.passwdcolumn,options.table,
options.usercolumn,escapeUser,escapeUser);
if ( strlen(options.where) > 0 )
{
sprintf(sql, "%s and %s", sql, options.where);
}printf("%s\n",sql);
//ncount = sqlo_run(dbh, sql, 0 , NULL);
//if (sth == SQLO_ERROR)
if (0> (sth = sqlo_open(dbh, sql, 0, NULL)))
{
syslog(LOG_ERR, sqlo_geterror (dbh));
syslog(LOG_ERR, "Help1 hoi=%s|\n", sqlo_geterror(dbh));
return "ERR";
}
while (0==sqlo_fetch(sth,1)) {
v=sqlo_values(sth,NULL,0);
p=v[0];
//return "%s",v[0];
return p;
}if (0 != sqlo_close(sth)) {
printf("select failed to close cursor: %s\n",sqlo_geterror(dbh));
return "ERR";
}
}int main() {
int ret;
char *pass;
char *pw;
char key[2];int s, c, sz,i;
struct sockaddr_in ssa, csa;
struct sockaddr *sp, *cp;
struct hostent *rhost;
char *host, *tstr;
char buf[BUFSZ];
char *userget;
char username;
const char sss;
time_t itime;sp=(struct sockaddr *)&ssa;
cp=(struct sockaddr *)&csa;
sz=sizeof(ssa);// Создаём сокет
s=socket(AF_INET, SOCK_STREAM, 0);
if(s == -1){
perror("Невозможно создать сокет");
exit(1);
}// Резервируем порт 1500
ssa.sin_family = AF_INET;
ssa.sin_port = htons(1500);
ssa.sin_addr.s_addr = INADDR_ANY;if(bind(s, sp, sz) == -1){
perror("Невозможно занять порт");
exit(1);
}
// Переводим сокет в режим ожидания соединения
if(listen(s, 0) == -1){
perror("Невозможно перейти в режим ожидания");
exit(1);
}//Установим соединение с ораклом
ret=db_connect();
printf("Oracle Connected: %d\n",ret);while(1){
write(1,"i'm here\n",10);
// Принимаем соединение
if((c = accept(s, cp, &sz)) == -1) {
perror("Ошибка при выполнении accept");
exit(1);
}//Получим от клиента имя пользователя
while((i=recv(c,buf,BUFSZ,0)) > 0) {
//write(1,buf,i);
userget = (char*)malloc((i)*sizeof(char));
strncpy(userget,buf,i);
userget[i]='\0';break;
}// Преобразуем адрес хоста отправителя в его имя
rhost=gethostbyaddr((char*)(&csa.sin_addr),
sizeof(csa.sin_addr), AF_INET);
if(h_errno){
printf("gethostbyaddr error: %d\n", h_errno);
host=inet_ntoa(csa.sin_addr);
} else {
host=rhost->h_name;
}// Получаем строку, содержащую дату и время
if((itime = time(NULL)) < 0){
perror("Не удалось получить время");
exit(1);
}
tstr = ctime(&itime);
// Выводим время поступления запроса,
// адрес и порт отправителя
printf("%s request from %s:%d, user name %s\n", tstr, host, htons(csa.sin_port),userget);pass =(char*)malloc(30*sizeof(char));
pass =NULL;pass = db_getpasswd(userget);
if (pass != NULL) {
pass[strlen(pass)]='\0';
printf("Password for user %s is %s\n",userget,pass);
send(c, pass, strlen(pass), 0);
}
else
{
send(c, "error", 5, 0);
}
free(userget);
free(pass);// Закрываем соединение
close(c);
}close(c);
close(s);
db_close();return 1;
}---------------------========================---------------------
Компилирую так
rm oracle_connector
gcc -o oracle_connector oracle_connector.c -lsqlora8 -lcrypt
---------------------========================---------------------
---------------------========================---------------------
---------------------========================---------------------
Вот кусок который pam модуль
---------------------========================---------------------
#include <security/pam_modules.h>
#include <stdarg.h>
#include <time.h>
#include <pwd.h>
#include <security/pam_misc.h>#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>//Это определит тип нашего модуля
#define PAM_SM_AUTH
#define BUFSZ 128
#define MAX_V 30
#define LOG_DEBUG 1
#define PLEASE_ENTER_PASSWORD "Password:"int converse (pam_handle_t * pamh, int nargs, struct pam_message **message, struct pam_response **response);
int askForPassword(pam_handle_t *pamh);
int askForPassword(pam_handle_t *pamh)
{
struct pam_message msg[1], *mesg[1];
struct pam_response *resp=NULL;
char *prompt=NULL;
int i=0;
int retval;
prompt = malloc(strlen(PLEASE_ENTER_PASSWORD));
if (prompt == NULL)
{
syslog(LOG_DEBUG,"pam_test: askForPassword(), out of memory!?");
return PAM_BUF_ERR;
}
else
{
sprintf(prompt,PLEASE_ENTER_PASSWORD);
msg[i].msg = prompt;
}
msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
mesg[i] = &msg[i];retval = converse(pamh, ++i, mesg, &resp);
if (retval != PAM_SUCCESS) {
return retval;
}
return pam_set_item(pamh, PAM_AUTHTOK, resp->resp);
}int converse(pam_handle_t *pamh, int nargs
, struct pam_message **message
, struct pam_response **response)
{
int retval;
struct pam_conv *conv;retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv );
if (retval == PAM_SUCCESS)
{
retval = conv->conv(nargs, (const struct pam_message **) message
, response, conv->appdata_ptr);
if ((retval != PAM_SUCCESS)) // && (retval != PAM_CONV_AGAIN)
{
syslog(LOG_DEBUG, "pam_test: conversation failure [%s]"
, pam_strerror(pamh, retval));
}
}
else
{
syslog(LOG_DEBUG, "pam_test: couldn't obtain conversation function [%s]"
, pam_strerror(pamh, retval));
}
return retval;
}PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags
,int argc, const char **argv)
{
int retval;
const char *user;
char *passwd = NULL;int s, sz, i;
struct sockaddr_in ssa;
struct sockaddr *sp;
struct in_addr sip;
char buf[BUFSZ];
char *str;
char key[2];
char *pw;
sp=(struct sockaddr *)&ssa;
sz=sizeof(ssa);if(argc!=1){
// Помощь по использованию команды
printf("Использование: pam_модуль.so ip-адрес\n");
exit(1);
}
if(inet_aton(argv[0], &sip) != 1){
printf("Неправильно задан адрес сервера\n");
exit(1);
}syslog(1,"!!!!!!!!!!!!!!!!!!-----1-----!!!!!!!!!!!!!!!!!!!!!");
retval = pam_get_user(pamh, &user, NULL);
if (retval != PAM_SUCCESS || user == NULL) {
syslog(LOG_DEBUG, "pam_test: no user specified");
return PAM_USER_UNKNOWN;
}
syslog(1,"!!!!!!!!!!!!!!!!!!-----2-----!!!!!!!!!!!!!!!!!!!!!");
retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &passwd);
if (passwd == NULL) {
askForPassword(pamh);
}
retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &passwd);
syslog(1,"!!!!!!!!!!!!!!!!!!-----3-----!!!!!!!!!!!!!!!!!!!!!");
if (passwd == NULL)
return PAM_AUTHINFO_UNAVAIL;const char *host;
int pam_err;
pam_err = pam_get_item(pamh, PAM_RHOST, (const void **) &host);
if (host==NULL) {
host="localhost";
}
syslog(LOG_DEBUG,"pam_test: HOST: %s",host);
syslog(LOG_DEBUG,"pam_test: USER: %s",user);
syslog(LOG_DEBUG,"pam_test: PASS: %s",passwd);// Создаём сокет
s=socket(AF_INET, SOCK_STREAM, 0);
if(s == -1){
perror("Невозможно создать сокет");
exit(1);
}// Задаём адрес сервера
ssa.sin_family = AF_INET;
ssa.sin_port = htons(1500);
ssa.sin_addr = sip;// Устанавливаем соединение
if(connect(s, sp, sz) == -1){
perror("Не удалось установить соединение");
exit(1);
}
// Посылаем login на сервер
str = user;
send(s,str,strlen(str),0);
// Получаем данные от сервера
while((i=recv(s, buf, BUFSZ, 0)) > 0) {
//pw = (char*)malloc(i*sizeof(char));
pw = (char*)malloc(13*sizeof(char));
//strncpy(pw,buf,i);
strncpy(pw,buf,13);
//pw[strlen(pw)]='\0';
pw[13]='\0';
}
strncpy(key,pw,2);
key[2] = '\0';
syslog(1,"key is %s",key);
char *cryptpass;
cryptpass = (char*)malloc(i*sizeof(char));
cryptpass=crypt(passwd,key);
cryptpass[strlen(cryptpass)]='\0';
syslog(1,"%s, %s, %d, %d",pw,cryptpass,strlen(pw),strlen(cryptpass));
retval = PAM_BUF_ERR;
if (!strcmp(pw,cryptpass)) {
syslog(1,"ПАРОЛИ СОВПАДАЮТ");
retval = PAM_SUCCESS;
}
else{
syslog(1,"ПАРОЛИ РАЗНЫЕ");
}
free(pw);
//free(cryptpass);
return retval;
}
PAM_EXTERN int pam_sm_setcred(pam_handle_t * pamh, int flags
,int argc, const char **argv)
{
unsigned int ctrl;
int retval;
retval = PAM_SUCCESS;
return retval;
}// Это определение необходимо для статической линковки модулей PAM в приложениях.
#ifdef PAM_STATIC
struct pam_module _pam_unix_auth_modstruct = {
"pam_test",
pam_sm_authenticate,
pam_sm_setcred,
NULL,
NULL,
NULL,
NULL,
};
#endif--------------------========================---------------------
Компилирую так:
rm *.o
rm *.so
gcc -fPIC -c pam_test.c
gcc -shared -o pam_test.so pam_test.o -lcrypt
cp pam_test.so /usr/lib/pam_test.so
--------------------========================---------------------
Далее, в /etc/pam.d/pop3
auth required pam_test.so XX.XX.XX.XX
где XX.XX.XX.XX - это ip адрес сервера с oracle
--------------------========================---------------------ПАМАГИТЕ ! давайте вместе доделаем всю эту байду ?
icq: 304000
mail: chepil@surgut.ru
PAM это только возможность использование shadow
узера это другая вещь (password)nsswitch.conf
passwd:
shadow:
group:
PAM только для SHADOW
и в итоге все получается правильно, что Сервер POP3 требует узера в PASSWD файле.
Тут надо другой подход.
PAM это не сделает.
хорошо, допустим что необходимо держать пустышки в passwd и shadow
для того чтобы пам работал,
а что такое nsswitch.conf ?
Какой другой подход возможен ?
задача эта-же. Есть FreeBSD mail сервер без оракл клиента, есть linux c ораклом на борту
Как заставить почту авторизоваться в оракле при этом выполнять всякие проверки в базе, а не просто проверять имя и пароль.
Как такое сделать ?
В Shadow держать пыстушки при PAM_ORACLE не надо.
а вот в PASSWD надо (при твоей схеме)
Тут надо править сам POP3
к примеру возьми popa3d (там проще всего, если конечно у тебя не IMAP)
сам сервер(POP) будет работать от одного узера MAIL:MAIL
а логины и пароли он смотри в своем файлике (при условии что popa3d собран для работы с VIRTUAL DOMAIN), улавливаешь суть ?
в итоге сервер не запрашивет узера из файла паролей системы.
что тебе по сути и надо.
Да, и самое главное и порой трудно выполнимое.
Тебе надо еще и SendMail учить что бы он смотрел наличие узера в Oarcle
это задача не тривиальная.
В свое время Я переписал для этих целей mail.local, и отвязал поиск узера в passwd системы.
Это дало мне возможность использовать сендмейл для виртуальных доменов, как это принято понимать по человечески, а не так как это в FAQ для сендмейла.
> Тут надо править сам POP3
> к примеру возьми popa3d (там проще всего, если конечно у тебя не IMAP)
> сам сервер(POP) будет работать от одного узера MAIL:MAIL
> а логины и пароли он смотри в своем файлике (при условии что popa3d собран
> для работы с VIRTUAL DOMAIN), улавливаешь суть ?
> в итоге сервер не запрашивет узера из файла паролей системы.
> что тебе по сути и надо.Улавливаю с этим разобрались, это не такая большая проблема, создавать пустышки в фале /etc/passwd
а как насчет общего подхода. Ктонить делал авторизацию как я, когда разнесены pam модуль и сервер авторизации на разных машинах ? Может есть что-то готовое, а я велосипед изобретаю ? Все было бы проще, если бы на freebsd можно было установить oracle client и библиотеку для доступа к ораклу из под си. но я таких не знаю. тогда был бы простой пам модуль.
Что касается почтового сервера. Хочется комплексное решение. В настоящее время используется sendmail + ipop3d + imap-uw
sendmail возможно поменяем на postfix, но хорошее ли это решение ? нет ли комплексных систем ? Сегодня вот наткнулся на http://www.stalker.com/CommuniGatePro/
Никто не пробовал ? С чем его едят ? Использует ли он pam ?
Смотри...
Если тебя устраивает использовать passwd то твоя схема будет работать нормально.
Пароли в ORACLE, узера в passwd, в итоге ни чего не надо делать ни с postfix ни с сендмейлом.
если ты отвяжешь POP от passwd тебе надо также отвязать и postfix (sendmail) от того же passwd, они то тоже туда смотрят ища наличие узера, иначе сам понимаешь, почта не дойдет до адресата :)
(no such user).
все понял, спасибо
>PAM это только возможность использование shadow
>узера это другая вещь (password)
>
>и в итоге все получается правильно, что Сервер POP3 требует узера в
>PASSWD файле.
>Тут надо другой подход.
>PAM это не сделает.В общем, исходники я поправил, теперь байда описанная выше не вылетает, кому надо, обращайтесь по адресу chepil собака surgut точка ru
И ещё. Я замучил popa3d ! Теперь эта собака не хочет лезть в файл /etc/passwd или /etc/master.passwd. Ей (собаке) теперь пофигу, есть такие файлы или нет. Теперь моя попа хавает только чистую pam авторизацию ! Что и требовалось доказать.
Интересно, а как заставить postfix или (courier или exim) хавать юзеров, если их нет в файле /etc/passwd ... ? Кто нить поборол ? Неужели прийдется использовать все тотже пам и ковырять исходники postfix ?
:)
Править уже сами исходники к Postfix (верней мейлер от Postfix)
для sendmail это mail.local (если хочешь то могу его выслать, он переписан с учетом POPA3D virtual domain))
Эх... Такую ветку пропустил. Жаль :)Загляни вот сюда:
http://www.opennet.me/cgi-bin/openforum/vsluhboard.cgi?az=sh...
Я там расписал как чего делается для случая с MySQL. С таким подходом вообще ничего пересобирать не надо и всё начинает работать "с места" и с любой программой - только nsswitch.conf нужно поправить.
PS. Sendmail и Postfix не используют PAM. Оно и правильно - им нужно только лишь проверить наличие пользователя и получить его UID. PAM этим не занимается :)
>Эх... Такую ветку пропустил. Жаль :)
Ну почему пропустил, по моему все только начинается :-)>
>Загляни вот сюда:
>
>http://www.opennet.me/cgi-bin/openforum/vsluhboard.cgi?az=sh...
>
>Я там расписал как чего делается для случая с MySQL. С таким
>подходом вообще ничего пересобирать не надо и всё начинает работать "с
>места" и с любой программой - только nsswitch.conf нужно поправить.
>
Интересно, сегодня изучу подробнее.>PS. Sendmail и Postfix не используют PAM. Оно и правильно - им
>нужно только лишь проверить наличие пользователя и получить его UID. PAM
>этим не занимается :)
Да, я понимаю теперь уже, но можно заставить postfix проверять не наличие юзера в файле паролей, а например, наличие файла /var/mail/USERNAME, переписав чуть-чуть исходники postfix, как я предполагаю, postfix использует что-то типа getpwname в общем, пока не разбирался. Но собираюсь. Все что касается распределенной авторизации для связки popa3d + pam + oracle, у меня все заработало. popa3d тоже пришлось подправить (в исходниках), если интересно, выложу код.
Вот то-то и оно, что править нужно кучу всего.Я долго разбирался - каким макаром PAM получает хранящиеся пароли пользователей для проверки. Как оказалось - сам он их не получает. Он использует те же getpwnam и т.д. А они находятся в glibc. А glibc выбирает куда смотреть в соответствии с /etc/nsswitch.conf.
В результате получаем только один путь для всех приложений сразу - механизм NSS. При этом, никто, кроме NSS-модуля незнает, откуда берутся данные. Да оно им не нужно :)
Это всё к тому, что не нужно править popa3d и postfix (в твоем случае) - нужно только еще один модуль к glibc подключить. Могу заслать свой будущий клиент/сервер - сам посмотришь. Он еще не отлажен (я его капитально переделывал когда на потоки переводил), работа кэша еще не проверена, но в принципе уже работает :)
Огромное спасибо за информацию ! Действительно ценный совет на тему nss
А вот исходники высылай, поучусь как это все делается :-)
А у тебя мыльце так и начинается со знака "!" ?
Если хочешь отпиши мне с нормального email я на него реплаем отвечу и прицеплю сорци переписанного мейлера, также раскажу как пользоваться сим чудом природы :) (мало ли захочешь пользоваться сендмейлом)
Я что то не понял,
для nsswitch.conf нужна установка NIS ?
Нет, необязательно
NIS это всего лиз сервер для хранеия UID и прорчего что с ним может быть связано.
там есть разные вырианты это и nis и ldap и files
надо внимательней глянуть что нужно и тот модуль подключить
Нет. Это для работы клиента с NIS нужен файл nsswitch.conf, дабы glibc знала, что имеется NIS и оттуда можно брать данные :)PS. Архив сейчас зашлю.
Используй vpopmail. Изначально он для qmail, но вообще вроде работал и с postfix-ом.www.inter7.com/vpopmail
>
>Используй vpopmail. Изначально он для qmail, но вообще вроде работал и с
> postfix-ом.
>
>www.inter7.com/vpopmailНе уверен что он мне поможет. Проблема в том что
1) На машине где стоит mail сервер нет и не может быть Oracle Client'а
тоесть авторизация должна быть распределенной
2) Нужна именно pam авторизация, хотя, как я полагаю vpopmail можно переписать до нужного мне уровня, руководство требует, чтобы на оракле проверялся не просто пароль, а чтобы проверялся ещё и ip адрес клиента. Если он не из нашей сети, то за забор почты с нашего домена, клиент должен заплатить деньги.
Извините за задержку, я был заслатый на объект.
Сегодня постараюсь выложить новые версии
на сайт http://chepil.surgut.ru
Сайта пока тоже нет, сегодня к обеду будетВсем огромное спасибо, буду развивать проект по мере возможностей
Окончательно работающий вариант выложен на сайте chepil.surgut.ru
Система протестирована в течении 1 недели на реальном сервере с реально работающими 1000 почтовых клиентов.
Запросы идут с частотой 2 шт. в секундуПомогите завершить ? Не знаю как писать Makefile нормальный, нет ли у кого доки на тему создания дистрибутивов на си ?