2013年1月4日 星期五

[Linux] 把玩 PAM 筆記 (Dovecot、Postfix 與 POP3/IMAP 認證為例) @ Ubuntu 12.04

PAM 全名為 Pluggable Authentication Module,可用在跨系統的帳號認證整合。有幸從同事那邊得到這邊的資訊,所以來筆記一下。更多 PAM 資訊可自行 Google 一下。在此筆記把玩的成果。


使用情境:



  • 一個提供帳號登入的 Web Service (A系統)

  • 一個提供 POP3/IMAP/SMTP 服務的 Mail Server (B系統)


如果想要提供 B 系統用 A 系統的帳號來做認證時,這時候就可以採用 PAM 來處理,例如在帳密登入流程中,加入 A系統認證流程。(註:此例以 Mail Server 舉例不甚理想,因為 PAM 無法解決 Mail Server 收信的問題,也就是無法提供 user list 讓 Mail Server 驗證帳號是否有效,在此僅用在登入帳密範例)


作法:



  • 實作 pam_yychecker.so 模組,用來認證 A 系統帳號

  • 使用 Dovecot POP3/IMAP 服務架構,增加 pam_yychecker.so 作為帳密認證


實作 pam_yychecker.so 模組:


pam_yychecker.c:(此例為任何帳密進來都 pass,而認證方面應修改 pam_sm_authenticate 函數,把取到的帳密到 A 系統認證)


// 參考 http://www.openpam.org/browser/openpam/trunk/modules/pam_unix/pam_unix.c


$ sudo apt-get install gcc libpam-dev make
$ gcc -shared -lpam -fPIC -Wall pam_yychecker.c -o pam_yychecker.so
$ sudo install pam_yychecker.so /lib/x86_64-linux-gnu/security


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


#include <security/pam_modules.h>
#include <security/pam_ext.h>


#include <syslog.h>


#ifndef PAM_EXTERN
#define PAM_EXTERN
#endif


PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
    int i;
   const char *user;
   char *password;


   syslog(LOG_DEBUG, "@ yychecker [pam_sm_authenticate]");
   for (i=0 ; i < argc ; i++)
      syslog(LOG_DEBUG, "argv[%d]=[%s]",i, argv[i]);


   /* identify user */
   if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS)
      return PAM_AUTH_ERR;
   syslog(LOG_DEBUG, "@ yychecker [pam_sm_authenticate] get account:[%s]", user);


   /* get password */
   if (pam_get_authtok(pamh, PAM_AUTHTOK, (const char **)&password, NULL) != PAM_SUCCESS)
      return PAM_AUTH_ERR;
   syslog(LOG_DEBUG, "@ yychecker [pam_sm_authenticate] get password:[%s]", password);

   //
   //  add some checker
   // ...


   return PAM_SUCCESS;
}


PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
   int i;
   syslog(LOG_DEBUG, "@ yychecker [pam_sm_setcred]");
   for (i=0 ; i < argc ; i++)
      syslog(LOG_DEBUG, "argv[%d]=[%s]",i, argv[i]);
   return (PAM_SUCCESS);
}


PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
   int i;
   syslog(LOG_DEBUG, "@ yychecker [pam_sm_acct_mgmt]");
   for (i=0 ; i < argc ; i++)
      syslog(LOG_DEBUG, "argv[%d]=[%s]",i, argv[i]);
   return (PAM_SUCCESS);
}


PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
   int i;
   syslog(LOG_DEBUG, "@ yychecker [pam_sm_open_session");
   for (i=0 ; i < argc ; i++)
      syslog(LOG_DEBUG, "argv[%d]=[%s]",i, argv[i]);
   return (PAM_SUCCESS);
}


PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
   int i;
   syslog(LOG_DEBUG, "@ yychecker [pam_sm_close_session");
   for (i=0 ; i < argc ; i++)
      syslog(LOG_DEBUG, "argv[%d]=[%s]",i, argv[i]);
   return (PAM_SUCCESS);
}


PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
   int i;
   syslog(LOG_DEBUG, "@ yychecker [pam_sm_chauthtok]");
   for (i=0 ; i < argc ; i++)
      syslog(LOG_DEBUG, "argv[%d]=[%s]",i, argv[i]);
   return (PAM_SERVICE_ERR);
}


使用 pam_yychecker.so 模組:


在此挑 Dovecot IMAP/POP3 為例:


@ /etc/pam.d/yychecker


auth required pam_checker.so debug  # "debug" 可讓 pam_yychecker.c 中的 syslog(LOG_DEBUG, "" ) 起作用,在 /var/log/syslog 可看見訊息
account required pam_checker.so debug arg1=val1  # arg1=val1 純粹測試用,可在 pam_yychecker.c 看到參數傳進來


@ /etc/dovecot/conf.d/99-yychecker.conf


auth_mechanisms = plain login
protocols = pop3 imap
mail_location = maildir:/home/vmail/%u/Maildir
auth_debug=yes # 更多豐富的訊息
passdb {
    driver = pam
    args = yychecker   # /etc/pam.d/yychecker 
}
userdb {
    driver = static
    args = uid=vmail gid=vmail home=/home/vmail/%u
}


透過撰寫上述兩個檔案後,將 dovecot 重開後 (/etc/init.d/dovecot restart),可以用 telnet/openssl 訪問 pop3/imap 服務:


IMAP:


$ openssl s_client -connect localhost:993
...
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN AUTH=LOGIN] Dovecot ready.
- login account password
- OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS] Logged in
- logout
* BYE Logging out
- OK Logout completed.
closed


POP3:


$ openssl s_client -connect localhost:995
...
+OK Dovecot ready.
user account
+OK
pass password
+OK Logged in.
quit
+OK Logging out.
closed


上述例子藍色是輸入的指令,其中 - login 後面依序接帳號跟密碼,由於在 pam_yychecker.c 之 pam_sm_authenticate 函數中,都是回傳 PAM_SUCCESS ,因此無論哪組(不存在的)帳密都會因此 PASS 的


此外還可以翻一下 /var/log/syslog 得知一下訊息:(此次帳號為test, 密碼為test)


dovecot: auth: Debug: Loading modules from directory: /usr/lib/dovecot/modules/auth
ovecot: auth: Debug: auth client connected (pid=14779)
dovecot: auth: Debug: client in: AUTH#0111#011PLAIN#011service=pop3#011secured#011lip=127.0.0.1#011rip=127.0.0.1#011lport=995#011rport=44325#011resp=AHRlc3QAdGVzdA==
dovecot: auth-worker: Debug: Loading modules from directory: /usr/lib/dovecot/modules/auth
dovecot: auth-worker: Debug: pam(test,127.0.0.1): lookup service=dovecot
dovecot: auth-worker: Debug: pam(test,127.0.0.1): #1/1 style=1 msg=Password:
dovecot: auth-worker: pam(test,127.0.0.1): pam_authenticate() failed: Authentication failure (password mismatch?) (given password: test)
dovecot: auth-worker: Debug: pam(test,127.0.0.1): lookup service=yychecker
auth: @ yychecker [pam_sm_authenticate]
auth: argv[0]=[debug]
auth: @ yychecker [pam_sm_authenticate] get account:[test]
dovecot: auth-worker: Debug: pam(test,127.0.0.1): #1/1 style=1 msg=Password:
auth: @ yychecker [pam_sm_authenticate] get password:[test]
auth: @ yychecker [pam_sm_acct_mgmt]
auth: argv[0]=[debug]
auth: argv[1]=[arg1=val1]
dovecot: auth: Debug: client out: OK#0111#011user=test
dovecot: auth: Debug: master in: REQUEST#0112190999553#01114779#0111#011a7a91b0fc5002a5eb77113ceb9304c7c
dovecot: auth: Debug: passwd(test,127.0.0.1): lookup
dovecot: auth: passwd(test,127.0.0.1): unknown user
dovecot: auth: Debug: master out: USER#0112190999553#011test#011uid=1002#011gid=1003#011home=/home/vmail/test
dovecot: pop3-login: Login: user=<test>, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=14783, TLS
dovecot: pop3(test): Disconnected: Logged out top=0/0, retr=0/0, del=0/0, size=0


從 syslog 大概可以看到,認證流程一開始是內建的帳密系統(/etc/pam.d/dovecot敘述),後來接著走 (/etc/pam.d/yychecker),接著依序走過 pam_sm_authenticate 和 pam_sm_acct_mgmt 函數。如果未實作 pam_sm_acct_mgmt 或 pam_sm_acct_mgmt 回傳 PAM_AUTH_ERR 時,也看到認證失敗的訊息。因此,對於 dovecot PAM 的使用中,除了一開始在 pam_sm_authenticate 就處理帳號過濾外,也可在 pam_sm_acct_mgmt 進行帳號的過濾。


原先是打算利用 PAM 來提供 Mail server (B系統) 使用 Web service (A系統) 帳號進行收信、送信,但測試的結果,盡管可以讓 POP3/IMAP/SMTP 認證都搞定(SMTP還須額外設定,上述範例設定檔還不夠),但在 Mail Server 之 deliver 上,採用 Dovecot LDA 卻碰到 userdb static 問題,也就是無法提供帳號認證,導致 Mail server 無把將 A 系統帳號視為有效帳號。也有可能是 PAM 寫錯或調教經驗上淺,玩了兩天還沒有好的成果。目前的結論是...還是乖乖用 dovecot userdb 其他要製造 user list 清單的方式吧 :P 在此就先筆記這幾天亂玩的東西。


2012年12月31日 星期一

iOS 開發筆記 - 國曆轉農曆計算 (Chinese Lunar Calendar)

lunar-2013-06


把玩一下 NSCalendar ,發現他可以計算咱們華人常用的農曆日期耶!完整文件 iOS Developer Library - Date and Time Programming Guide


程式碼:


NSString *dateAt = @"2013/06/08";
NSDateFormatter *dateFormatter = [[[NSDateFormatter allocinit] autorelease];
[dateFormatter setDateFormat:@"yyyy/MM/dd"];
NSDateComponents *dateComps = [[[[NSCalendar alloc] initWithCalendarIdentifier:NSChineseCalendar] autorelease] components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:[dateFormatter dateFromString:dateAt]];
NSLog(@"%@ => Chinese calendar date (Lunar): %0.2d/%0.2d", dateAt, [dateComps month], [dateComps day]);


結果: 


2013/06/08 => Chinese calendar date (Lunar): 05/01


註:有的農民曆將 2013/06/08 定為農曆 04/30,故挑這天當做範例提醒算法的不同。


2012年12月30日 星期日

2012年的最後一天

Life & Sea


今年很特別,發生了不少事,體驗過交接離職同事待結案的案子(還一次三案 orz)、同公司換單位、跟同好分道揚鑣、Android、國外自助旅行、找房子、搬家、役畢、換公司、iOS、國內離島旅行、看顧、狂用悠遊卡等,想起來真的經歷不少事,記得役畢前還斤斤計較如何追求工作薪資的 CP 值,很奇妙地一役畢反而不在意,看著周邊親友工作上的來來往往,有的厭膩生態、有的追求夢想都好,都是踏出人生的下一步。難得 12/31 可以在老家休息,回老鄉走走,從火車站步行回家,看看許久未見的街景,可真是熟悉又陌生。


回顧這一年,收獲不少,但開始會想想是不是該多充實生活化的記事,例如遊記、聚餐、拍照、音樂、遊戲、語文呢?說真的我還滿高興自己亂寫的一些旅遊或遊戲筆記的瀏覽次數高於一些技術筆記,新年一年應該可以多想想,學一些語文、文化勝於新的程式語言,多走一些景點勝於多開幾個專案。


只是,年都還沒跨過去,又馬上收到新的一年的工作計畫,看來明年一開始又得忙了,越來越多的新事物、挑戰跟角色,年輕就該多衝刺嘛,這大概就是所謂的人生吧!


總之,新年快樂!


2012年12月26日 星期三

[Linux] 強制使用 SSH Keys (key pair) 登入認證 @ Ubuntu 12.04

use-pubkey-only


最近常建一些 VM 來把玩,想到除了用 key-pair 管理登入認證外,還想了一些資安問題,就參考過去把玩 Amazon EC2 的概念,把登入機制強制使用 public key 的方式,如此一來可以安全一點點吧!?(上圖是用 Putty 透過 account/password 登入時的錯誤訊息)


設定:


$ vim /etc/sshd_config
PubkeyAuthentication yes
PasswordAuthentication no


$ /etc/init.d/ssh restart


其他:


$ ssh-keygen -t rsa -P ''
$ scp ~/.ssh/id_rsa.pub account@TargetServer:~/.ssh/authorized_keys


2012年12月20日 星期四

Android 開發筆記 - 使用 keytool 處理 keystore 匯入、合併、更改密碼、更改 alias name

接近半年前幫公司上架一支 Android app ,結果兩個月前想要更新程式時,卻發現自己想也想不起來 keystore 的密碼 XD 當時起就一直抱著砍掉重練的精神,遲遲未更新程式,沒想到今天再次要更新程式時,竟然矇對 keystore 的密碼了!當下想到的第一件事就是更新密碼,把密碼改簡單一點 :P 此外,以前認為一支 Android app 就要獨立一個 keystore 來管理,反而造成管理上的困擾,因此查了一下果真還有可以合併 keystore 的方式 :-) 真是太讚啦


更改 keystore alias name:


$ keytool -changealias -alias "OldAliasName" -destalias "NewAliasName" -keypass YourAliasKeyPassword -keystore your.keystore -storepass YourKeyStorePassword


更改 alias password:


$ keytool -keystore your.keystore -keypasswd -alias AliasName


更改 keystore password:


$ keytool -storepasswd -keystore your.keystore


合併 keystore:


$ keytool -importkeystore -srckeystore SourceKeyStore.keystore -srcstorepass SourceKeyStorePassword -destkeystore DestinationKeystore.keystore -deststorepass DestinationKeyStorePassword