分析pptpd程序中關於執行pptpd和pppd程序的部分源代碼
最近使用pptpd部署vpn server,為了更好的了解pptpd程序,我特意看了它的源代碼,並做了一些記錄,這是其中一篇記錄,有不正確的地方請各位指正,謝謝!
我對linux,對c語言的學習都還不夠,寫的東西里會有一些不清楚或者錯誤的地方,希望各位指點,也希望和大家一起討論。
整個pptpd程序的源頭應該是pptpd.c的main()函數。
在pptpd.c的main()函數的420行調用了pptp_manager()函數:
/* manage connections until SIGTERM */
pptp_manager(argc, argv);
這個函數在pptpmanager.c中。
-------------------------------------
pptpmanager.c中第310行開始的代碼,是為處理客戶端連接而生成的子進程調用connectCall()函數:
#ifndef HAVE_FORK
switch (ctrl_pid = vfork()) {
#else
switch (ctrl_pid = fork()) {
#endif
......
case 0: /* child */ /* 第310行 */
close(hostSocket);
if (pptp_debug)
syslog(LOG_DEBUG, "MGR: Launching " PPTP_CTRL_BIN " to handle client");
#if !defined(PPPD_IP_ALLOC)
connectCall(clientSocket, firstOpen);
#else
connectCall(clientSocket, 0);
#endif
_exit(1);
-------------------------------------
在pptpmanager.c的453行開始的代碼,感覺這裡是pptpd里真正為客戶端分配ip地址的地方,這部分代碼屬於connectCall()函數:
#if !defined(PPPD_IP_ALLOC)
extern char localIP;
/* localIP數組應該存放了所有本地ip,每個數組元素中存放一個ip,
總的元素個數就是允許的最大連接數 */
extern char remoteIP;
/* for now, this contains all relavant info about a call
* we are assuming that the remote and local IP's never change
* and are set at startup.
*/
struct callArray {
pid_t pid;
char pppRemote;
char pppLocal;
};
......
/* Author: Kevin Thayer
* this routine sets up the arguments for the call handler and calls it. */
static void connectCall(int clientSocket, int clientNumber)
{
#define NUM2ARRAY(array, num) snprintf(array, sizeof(array), "%d", num)
char *ctrl_argv; /* arguments for launching 'pptpctrl' binary */
int pptpctrl_argc = 0; /* count the number of arguments sent to pptpctrl */
........
#if PPPD_IP_ALLOC /* 第453行 */
/* no local or remote address to specify */
ctrl_argv = "0";
ctrl_argv = "0";
#else
/* specify local & remote addresses for this call */
ctrl_argv = "1";
ctrl_argv = localIP;
ctrl_argv = "1";
ctrl_argv = remoteIP;
........
/* ok, args are setup: invoke the call handler */
execve(PPTP_CTRL_BIN, ctrl_argv, environ);
/*本函數最後,調用pptpd程序,把ctrl_argv和environ做為參數 */
/* defaults.h中定義:#define PPTP_CTRL_BIN SBINDIR "/pptpctrl" */
syslog(LOG_ERR, "MGR: Failed to exec " PPTP_CTRL_BIN "!");
_exit(1);
}
#endif
上面connectCall函數中的execve()實際調用了pptpctrl程序,這個程序的源代碼應該是pptpctrl.c,看來又再次返回到pptpctrl。這個程序接收ctrl_argv和environ兩個參數,其中ctrl_argv數組中包括了localIP和remoteIP兩個變數,這正是我感興趣的部分。
execve()調用成功后不會返回,所以該語句後面跟的是出錯以後的處理。
-------------------------------------
pptp_handle_ctrl_connection()函數被pptpctrl.c的main()函數調用,相關代碼在main()的尾部:
int main(int argc, char **argv) /* main()一開始就定義了很多變數 */
{
char pppLocal; /* local IP to pass to pppd */
char pppRemote; /* remote IP address to pass to pppd */
struct sockaddr_in addr; /* client address */
socklen_t addrlen;
int arg = 1;
int flags;
struct in_addr inetaddrs;
char *pppaddrs = { pppLocal, pppRemote };
......
/* be ready for a grisly death */
sigpipe_create();
sigpipe_assign(SIGTERM);
NOTE_VALUE(PAC, call_id_pair, htons(-1));
NOTE_VALUE(PNS, call_id_pair, htons(-1));
syslog(LOG_INFO, "CTRL: Client %s control connection started", inet_ntoa(addr.sin_addr));
pptp_handle_ctrl_connection(pppaddrs, inetaddrs); /* 調用這個函數,處理來自客戶端的連接 */
/* 上面這個函數里就調用了pppaddrs數組和inetaddrs結構,說明在此以前就有賦值 */
syslog(LOG_DEBUG, "CTRL: Reaping child PPP[%i]", pppfork);
if (pppfork > 0) /* 結束用於處理客戶端連接的子進程 */
waitpid(pppfork, NULL, 0);
syslog(LOG_INFO, "CTRL: Client %s control connection finished", inet_ntoa(addr.sin_addr));
bail(0); /* NORETURN */
return 1; /* make gcc happy */
}
-------------------------------------
在pptpctrl.c的383行,是pptp_handle_ctrl_connection()函數,它調用了startCall()函數,相關代碼:
/*
* pptp_handle_ctrl_connection
*
* 1. read a packet (should be start_ctrl_conn_rqst)
* 2. reply to packet (send a start_ctrl_conn_rply)
* 3. proceed with GRE and CTRL connections
*
* args: pppaddrs - ppp local and remote addresses (strings)
* inetaddrs - local and client socket address
* retn: 0 success, -1 failure
*/
static void pptp_handle_ctrl_connection(char **pppaddrs, struct in_addr *inetaddrs)
{
......
/* start the call, by launching pppd */
syslog(LOG_INFO, "CTRL: Starting call (launching pppd, opening GRE)");
pty_fd = startCall(pppaddrs, inetaddrs);
......
}
-------------------------------------
同一文件下的startCall()函數,pptpd為客戶端連接建立一個子進程,在這個子進程里調用pppd程序(第634行):
<!--more-->
/*
* startCall
*
* Launches PPPD for the call.
*
* args: pppaddrs - local/remote IPs or "" for either/both if none
* retn: pty file descriptor
*
*/
static int startCall(char **pppaddrs, struct in_addr *inetaddrs)
{
......
/* Launch the PPPD */
#ifndef HAVE_FORK
switch(pppfork=vfork()){
#else
switch(pppfork=fork()){
#endif
case -1: /* fork() error */
syslog(LOG_ERR, "CTRL: Error forking to exec pppd");
_exit(1);
case 0: /* child */
/* 子進程中調用pppd */
......
launch_pppd(pppaddrs, inetaddrs); /* launch_pppd函數用於調用pppd */
syslog(LOG_ERR, "CTRL: PPPD launch failed! (launch_pppd did not fork)");
_exit(1);
}
close(tty_fd);
return pty_fd;
}
-------------------------------------
同一文件655行是launch_pppd函數:
/*
* launch_pppd
*
* Launches the PPP daemon. The PPP daemon is responsible for assigning the
* PPTP client its IP address.. These values are assigned via the command
* line.
*
* Add return of connected ppp interface
*
* retn: 0 on success, -1 on failure.
*
*/
static void launch_pppd(char **pppaddrs, struct in_addr *inetaddrs)
{
......
#else
/* 以下這部分應該是pptpd調用pppd的時候執行的代碼 */
/* options for 'normal' pppd */
pppd_argv = "local";
/* If a pppd option file is specified, use it
* if not, pppd will default to /etc/ppp/options
*/
if (*pppdxfig) {
pppd_argv = "file";
pppd_argv = pppdxfig;
}
/* If a speed has been specified, use it
* if not, use "smart" default (defaults.h)
*/
if (*speed) {
pppd_argv = speed;
} else {
pppd_argv = PPP_SPEED_DEFAULT;
}
if (pptpctrl_debug) {
/* 以下兩個if語句在系統日誌文件里輸出localip和remoteip,
在/var/log/messages里可以查到這些輸出 */
if (*pppaddrs)
syslog(LOG_DEBUG, "CTRL (PPPD Launcher): local address = %s", pppaddrs);
if (*pppaddrs)
syslog(LOG_DEBUG, "CTRL (PPPD Launcher): remote address = %s", pppaddrs);
}
if (*pppaddrs || *pppaddrs) {
char pppInterfaceIPs;
sprintf(pppInterfaceIPs, "%s:%s", pppaddrs, pppaddrs);
pppd_argv = pppInterfaceIPs;
}
#endif
......
if (pptp_logwtmp) { /* logwtmp相關的代碼 */
pppd_argv = "plugin";
pppd_argv = "/usr/lib/pptpd/pptpd-logwtmp.so";
pppd_argv = "pptpd-original-ip";
pppd_argv = inet_ntoa(inetaddrs);
}
/* argv arrays must always be NULL terminated */
pppd_argv = NULL;
/* make sure SIGCHLD is unblocked, pppd does not expect it */
sigfillset(&sigs);
sigprocmask(SIG_UNBLOCK, &sigs, NULL);
/* run pppd now */
execvp(pppd_argv, pppd_argv); /* 就是這一句調用了pppd程序 */
/* execvp() failed */
syslog(LOG_ERR,
"CTRL (PPPD Launcher): Failed to launch PPP daemon. %s",
strerror(errno));
}
-------------------------------------
關於上面函數中尾部的execvp(pppd_argv, pppd_argv)語句:
launch_pppd()函數里定義:
char *pppd_argv;
然後函數中對14個數組元素進行定義,其中第0個元素存放了pppd的執行程序:
pppd_argv = ppp_binary;
從程序代碼中還可以看到這個數組的其他元素都被賦了值,這些值應該都是pppd程序的參數。
這裡的ppp_binary在本文的頭部定義:
static char *ppp_binary = PPP_BINARY;
-------------------------------------
PPP_BINARY變數在configure.in里有定義:
if test "$BSDUSER_PPP" = "yes"; then
AC_DEFINE(PPP_BINARY, "/usr/sbin/ppp")
else
if test "$SLIRP" = "yes"; then
AC_DEFINE(PPP_BINARY, "/bin/slirp")
else
AC_DEFINE(PPP_BINARY, "/usr/sbin/pppd")
fi
fi
[ 本帖最後由 sailer_sh 於 2006-2-10 21:51 編輯 ]
《解決方案》
PPPD中pap認證部分的代碼
PPPD中pap認證部分的代碼:
upap.h里的結構,感覺客戶端用戶名和密碼應該保存在這裡面,但是還要找是誰把用戶名/密碼保存進去,以及誰調用了這個結構:
<!--more-->
/*
* Each interface is described by upap structure.
*/
typedef struct upap_state {
int us_unit; /* Interface unit number */
char *us_user; /* User */
int us_userlen; /* User length */
char *us_passwd; /* Password */
int us_passwdlen; /* Password length */
int us_clientstate; /* Client state */
int us_serverstate; /* Server state */
u_char us_id; /* Current id */
int us_timeouttime; /* Timeout (seconds) for auth-req retrans. */
int us_transmits; /* Number of auth-reqs sent */
int us_maxtransmits; /* Maximum number of auth-reqs to send */
int us_reqtimeout; /* Time to wait for auth-req from peer */
} upap_state;
auth.c的check_passwd()函數,實際證明下來客戶端傳來的用戶名和密碼被做為這個函數的參數,即auser和apasswd。
/* check_passwd - Check the user name and passwd against the PAP secrets
* file. If requested, also check against the system password database,
* and login the user if OK.
*
* returns:
* UPAP_AUTHNAK: Authentication failed.
* UPAP_AUTHACK: Authentication succeeded.
* In either case, msg points to an appropriate message. */
int check_passwd(unit, auser, userlen, apasswd, passwdlen, msg)
int unit;
char *auser;
int userlen;
char *apasswd;
int passwdlen;
char **msg;
{
int ret;
char *filename;
FILE *f;
struct wordlist *addrs = NULL, *opts = NULL;
char passwd, user; /* 從這兩個數組的定義可以猜到pppd可以接收的用戶名和密碼的最大長度都是256個位元組*/
char secret;
static int attempts = 0;
/* 下面把用戶名和密碼複製到user/passwd數組 */
/*
* Make copies of apasswd and auser, then null-terminate them.
* If there are unprintable characters in the password, make
* them visible.
*/
slprintf(passwd, sizeof(passwd), "%.*v", passwdlen, apasswd);
slprintf(user, sizeof(user), "%.*v", userlen, auser);
*msg = "";
/* 接下來打開pap認證的密碼文件/etc/ppp/pap-secrets,這個文件在_PATH_UPAPFILE中定義 */
/*
* Open the file of pap secrets and scan for a suitable secret
* for authenticating this user.
*/
filename = _PATH_UPAPFILE;
addrs = opts = NULL;
ret = UPAP_AUTHNAK;
f = fopen(filename, "r");
if (f == NULL) {
error("Can't open PAP password file %s: %m", filename);
} else {
check_access(f, filename); /* 檢查文件是否可讀 */
if (scan_authfile(f, user, our_name, secret, &addrs, &opts, filename, 0) < 0) { /* 查找文件中是否有某用戶的密碼 */
warn("no PAP secret found for %s", user);
} else { /* 如果密碼欄位設置為"@login「,那麼就在login database中檢查密碼,這個login database就是系統的用戶/密碼文件 */
/*
* If the secret is "@login", it means to check
* the password against the login database.
*/
int login_secret = strcmp(secret, "@login") == 0;
ret = UPAP_AUTHACK;
if (uselogin || login_secret) {
/* login option or secret is @login */
if ((ret = plogin(user, passwd, msg)) == UPAP_AUTHACK)
used_login = 1;
}
if (secret != 0 && !login_secret) {
/* password given in pap-secrets - must match */
if ((cryptpap || strcmp(passwd, secret) != 0) /* 如果密碼不匹配,就設置ret的值為UPAP_AUTHNAK */
&& strcmp(crypt(passwd, secret), secret) != 0)
ret = UPAP_AUTHNAK;
}
}
fclose(f);
}
if (ret == UPAP_AUTHNAK) { /* ret的值為UPAP_AUTHNAK表示登錄不成功 */
if (**msg == 0)
*msg = "Login incorrect";
/*
* XXX can we ever get here more than once??
* Frustrate passwd stealer programs.
* Allow 10 tries, but start backing off after 3 (stolen from login).
* On 10'th, drop the connection.
*/
if (attempts++ >= 10) { /* 登錄次數超過10次 */
warn("%d LOGIN FAILURES ON %s, %s", attempts, devnam, user);
lcp_close(unit, "login failed");
}
if (attempts > 3)
sleep((u_int) (attempts - 3) * 5);
if (opts != NULL)
free_wordlist(opts);
} else { /* ret的值不為UPAP_AUTHNAK表示登錄成功 */
attempts = 0; /* Reset count */
if (**msg == 0)
*msg = "Login ok";
set_allowed_addrs(unit, addrs, opts);
}
if (addrs != NULL)
free_wordlist(addrs);
BZERO(passwd, sizeof(passwd)); /* 清空來自客戶端的密碼 */
BZERO(secret, sizeof(secret)); /* 清空來自配置文件的密碼 */
return ret;
}
upap.c的upap_rauthreq()函數調用了上面的check_passwd()函數,客戶端用戶名和密碼存放在指針inp里:
/* upap_rauth - Receive Authenticate. */
static void
upap_rauthreq(u, inp, id, len)
upap_state *u;
u_char *inp;
int id;
int len;
{
u_char ruserlen, rpasswdlen;
char *ruser, *rpasswd;
char rhostname;
int retcode;
char *msg;
int msglen;
......
GETCHAR(ruserlen, inp);
/* 我覺得很奇怪,ruserlen是u_char,即無符號字元型,但它實際存放的是用戶名的長度,為什麼不把它設置為int型呢?不知道有誰能解決我的問題?
GETCHAR()的原型:
#define GETCHAR(c, cp) { \
(c) = *(cp)++; \
}
從程序這裡判斷,給ruserlen賦的值正好是inp指向內存區域的第一個地址,這個地址存放的是一個控制字元,在putty里顯示為"^B", 不知道為什麼這個值就是用戶名長度,我還沒想通。
*/
ruser = (char *) inp; /* 指針ruser指向指針inp指向的地址的第二個位元組開始的部分,就是用戶名 */
INCPTR(ruserlen, inp); /* 指針inp向前移,移的數量就是用戶名的長度,相當於把指針移到用戶名後面一個位元組 */
GETCHAR(rpasswdlen, inp); /* 和ruserlen一樣,給rpasswdlen賦值,這個值就是密碼的長度,也看不懂。。。 */
if (len < rpasswdlen) { /* 如果len < rpasswdlen,那麼這個數據是不正常的 */
UPAPDEBUG(("pap_rauth: rcvd short packet."));
return;
}
rpasswd = (char *) inp; /* rpasswd指向客戶端密碼 */
/* Check the username and password given. */
/* 調用check_passwd函數檢查用戶名和密碼,ruser是用戶名,rpasswd是密碼 */
retcode = check_passwd(u->us_unit, ruser, ruserlen, rpasswd,
rpasswdlen, &msg);
BZERO(rpasswd, rpasswdlen);
調試下來發現參數inp指向的字元串里存放著用戶名和密碼,例如用戶名是st,密碼是abcdef,它的存放方式是:
^Bst^Fabcdef
^B和^F是兩個控制字元,我覺得它們是用來分隔用戶名和密碼的。
參數len是兩個控制字元(各佔一個位元組)加用戶名及密碼的長度的總和。
同文件的upap_input()調用了上面的upap_rauthreq():
/*
* upap_input - Input UPAP packet.
*/
static void
upap_input(unit, inpacket, l)
int unit;
u_char *inpacket;
int l;
{
upap_state *u = &upap;
u_char *inp;
u_char code, id;
int len;
/*
* Parse header (code, id and length).
* If packet too short, drop it.
*/
inp = inpacket; /* 把inpacket賦給inp,用戶名/密碼保存在inpacket中 */
......
switch (code) {
case UPAP_AUTHREQ:
upap_rauthreq(u, inp, id, len); /* 調用upap_rauthreq()函數,並且傳遞了inp參數 */
break;
沒有一個函數顯式的調用upap_input()函數,應該是在某個地方通過下面的結構來調用它。
-----------------------------------------------------------
所有和pap認證相關的函數都放在同文件的結構里:
struct protent pap_protent = {
PPP_PAP,
upap_init,
upap_input, /* 這個函數接收了客戶端用戶名和密碼 */
upap_protrej,
upap_lowerup,
upap_lowerdown,
NULL,
NULL,
upap_printpkt,
NULL,
1,
"PAP",
NULL,
pap_option_list,
NULL,
NULL,
NULL
};
main.c定義了protocols結構數組:
/*
* PPP Data Link Layer "protocol" table.
* One entry per supported protocol.
* The last entry must be NULL.
*/
struct protent *protocols[] = {
&lcp_protent,
&pap_protent, /* pap認證相關結構 */
&chap_protent,
#ifdef CBCP_SUPPORT
&cbcp_protent,
#endif
&ipcp_protent,
#ifdef INET6
&ipv6cp_protent,
#endif
&ccp_protent,
&ecp_protent,
#ifdef IPX_CHANGE
&ipxcp_protent,
#endif
#ifdef AT_CHANGE
&atcp_protent,
#endif
&eap_protent,
NULL
};
接下來,在main()里把protocols結構的值賦給protp結構數組:
/*
* Initialize each protocol.
*/
for (i = 0; (protp = protocols) != NULL; ++i)
(*protp->init)(0);
pppd.h中定義protent結構:
/*
* The following struct gives the addresses of procedures to call
* for a particular protocol.
*/
struct protent {
u_short protocol; /* PPP protocol number */
/* Initialization procedure */
void (*init) __P((int unit));
/* Process a received packet */
void (*input) __P((int unit, u_char *pkt, int len));
/* Process a received protocol-reject */
void (*protrej) __P((int unit));
/* Lower layer has come up */
void (*lowerup) __P((int unit));
/* Lower layer has gone down */
void (*lowerdown) __P((int unit));
/* Open the protocol */
void (*open) __P((int unit));
/* Close the protocol */
void (*close) __P((int unit, char *reason));
/* Print a packet in readable form */
int (*printpkt) __P((u_char *pkt, int len,
void (*printer) __P((void *, char *, ...)),
void *arg));
/* Process a received data packet */
void (*datainput) __P((int unit, u_char *pkt, int len));
bool enabled_flag; /* 0 iff protocol is disabled */
char *name; /* Text name of protocol */
char *data_name; /* Text name of corresponding data protocol */
option_t *options; /* List of command-line options */
/* Check requested options, assign defaults */
void (*check_options) __P((void));
/* Configure interface for demand-dial */
int (*demand_conf) __P((int unit));
/* Say whether to bring up link for this pkt */
int (*active_pkt) __P((u_char *pkt, int len));
};
-----------------------------------------
upap.c的upap_rauthreq()函數做PAP認證,成功以後調用auth_peer_success()函數:
if (retcode == UPAP_AUTHACK) {
u->us_serverstate = UPAPSS_OPEN;
notice("PAP peer authentication succeeded for %q", rhostname);
auth_peer_success(u->us_unit, PPP_PAP, 0, ruser, ruserlen);
} else {
上面函數中的u->us_unit:u是fsm結構,us_unit是第一個元素,在fsm.h中定義:
typedef struct fsm {
int unit; /* Interface unit number */
auth.c文件下auth_peer_success()函數:
/*
* The peer has been successfully authenticated using `protocol'.
*/
void
auth_peer_success(unit, protocol, prot_flavor, name, namelen)
int unit, protocol, prot_flavor;
char *name;
int namelen;
{
switch (protocol) {
......
case PPP_PAP: /* 如果使用pap認證方式 */
bit = PAP_PEER;
break;
......
/*
* Save the authenticated name of the peer for later.
*/
if (namelen > sizeof(peer_authname) - 1)
namelen = sizeof(peer_authname) - 1;
BCOPY(name, peer_authname, namelen); /* 把用戶名保存在peer_authname中,以備將來使用,什麼地方使用這個變數還沒看到 */
peer_authname = 0;
script_setenv("PEERNAME", peer_authname, 0);
/* Save the authentication method for later. */
auth_done |= bit;
/*
* If there is no more authentication still to be done,
* proceed to the network (or callback) phase.
*/
if ((auth_pending &= ~bit) == 0)
network_phase(unit);
}
[ 本帖最後由 sailer_sh 於 2006-2-10 21:48 編輯 ]
《解決方案》
main.c里有一個get_input()函數,它用於處理每個來自客戶端的數據包,當數據里包含用戶名和密碼里,它會執行相應的函數來處理用戶名和密碼。如果使用PAP認證方式,那麼函數會調用upap.c里的upap_input()函數,這個函數再調用同文件下的upap_rauthreq()函數,這個函數再調用auth.c文件里的check_passwd()函數處理用戶名和密碼。
main.c的get_input()函數:
u_char inpacket_buf; /* buffer for incoming packet */
......
len = read_packet(inpacket_buf);
ppp_defs.h里定義:
#define PPP_MRU 1500 /* default MRU = max length of info field */
#define PPP_HDRLEN 4 /* octets for standard ppp header */
所以,ppp數據包的長度是1504個位元組,其實有4個位元組是ppp數據包頭部。
pppd服務端與客戶端之間的前5個數據包是LCP數據包,其中第三個LCP數據包調用了lcp_up()函數。第6個數據包用於客戶端用戶名和密碼的認證,如果是PAP認證方式,那麼lcp調用upap_input()處理這個數據包,upap_input()提取用戶名和密碼,做為參數傳遞給upap_rauthreq()進行認證,認證失敗就斷開連接,認證成功繼續傳輸數據包。
接下來的數據包有LCP和IPCP數據包,整個連接的第14個數據包是IPCP數據包,這個數據包包含VPN分配給客戶端的remoteIP(其實是由pptpd分配的),IPCP先調用了ipcp_up(),接下來ipcp_up()調用了auth_ip_addr(),並把remoteIP傳遞給它,auth_ip_addr()對remoteIP進行認證,認證方式是讀取/etc/ppp/pap-secrets,查看撥入用戶這一行的第四個欄位。認證成功以後繼續發送數據包,第15個數據包是LCP數據包。
未完待繼。。。
[ 本帖最後由 sailer_sh 於 2006-2-16 12:10 編輯 ]
《解決方案》
精品 還有繼續寫嗎? 我也在做linux ppp撥號 很多地方還不懂 想學習下
《解決方案》
好好學習下。現在也正在看這個東西呢。
《解決方案》
精彩!!!!!
《解決方案》
pppd命令怎麼用啊!讓它登陸adsl?急求。。
《解決方案》
我在一個車載項目中,使用GPRS和PPPD 出現一些問題,數據接收有問題,請求有償技術幫助,急急
我的聯繫:
mail:
[email protected] qq:723974437
tel:13502865241