分析pptpd程序中关于执行pptpd和pppd程序的部分源代码

From: http://bbs.chinaunix.net/viewthread.php?tid=695926

 

最近使用pptpd部署 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[MAX_CONNECTIONS][16]; /* localIP数组应该存放了所有本地ip,每个数组元素中存放一个ip, 总的元素个数就是允许的最大连接数 */ extern char remoteIP[MAX_CONNECTIONS][16]; /* 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[16]; char pppLocal[16]; }; ...... /* 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[16]; /* 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[pptpctrl_argc++] = "0"; ctrl_argv[pptpctrl_argc++] = "0"; #else /* specify local & remote addresses for this call */ ctrl_argv[pptpctrl_argc++] = "1"; ctrl_argv[pptpctrl_argc++] = localIP[clientNumber]; ctrl_argv[pptpctrl_argc++] = "1"; ctrl_argv[pptpctrl_argc++] = remoteIP[clientNumber]; ........ /* 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[16]; /* local IP to pass to pppd */ char pppRemote[16]; /* 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[2]; char *pppaddrs[2] = { 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行):

/* * 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[an++] = "local"; /* If a pppd option file is specified, use it * if not, pppd will default to /etc/ppp/options */ if (*pppdxfig) { pppd_argv[an++] = "file"; pppd_argv[an++] = pppdxfig; } /* If a speed has been specified, use it * if not, use "smart" default (defaults.h) */ if (*speed) { pppd_argv[an++] = speed; } else { pppd_argv[an++] = PPP_SPEED_DEFAULT; } if (pptpctrl_debug) { /* 以下两个if语句在系统日志文件里输出localip和remoteip, 在/var/log/messages里可以查到这些输出 */ if (*pppaddrs[0]) syslog(LOG_DEBUG, "CTRL (PPPD Launcher): local address = %s", pppaddrs[0]); if (*pppaddrs[1]) syslog(LOG_DEBUG, "CTRL (PPPD Launcher): remote address = %s", pppaddrs[1]); } if (*pppaddrs[0] || *pppaddrs[1]) { char pppInterfaceIPs[33]; sprintf(pppInterfaceIPs, "%s:%s", pppaddrs[0], pppaddrs[1]); pppd_argv[an++] = pppInterfaceIPs; } #endif ...... if (pptp_logwtmp) { /* logwtmp相关的代码 */ pppd_argv[an++] = "plugin"; pppd_argv[an++] = "/usr/lib/pptpd/pptpd-logwtmp.so"; pppd_argv[an++] = "pptpd-original-ip"; pppd_argv[an++] = inet_ntoa(inetaddrs[1]); } /* argv arrays must always be NULL terminated */ pppd_argv[an++] = NULL; /* make sure SIGCHLD is unblocked, pppd does not expect it */ sigfillset(&sigs); sigprocmask(SIG_UNBLOCK, &sigs, NULL); /* run pppd now */ execvp(pppd_argv[0], pppd_argv); /* 就是这一句调用了pppd程序 */ /* execvp() failed */ syslog(LOG_ERR, "CTRL (PPPD Launcher): Failed to launch PPP daemon. %s", strerror(errno)); }

-------------------------------------

关于上面函数中尾部的execvp(pppd_argv[0], pppd_argv)语句:
launch_pppd()函数里定义:

char *pppd_argv[14];

然后函数中对14个数组元素进行定义,其中第0个元素存放了pppd的执行程序:

pppd_argv[an++] = 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

 

PPPD中pap认证部分的代码

 

upap.h里的结构,感觉客户端用户名和密码应该保存在这里面,但是还要找是谁把用户名/密码保存进去,以及谁调用了这个结构:

/* * 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[256], user[256]; /* 从这两个数组的定义可以猜到pppd可以接收的用户名和密码的最大长度都是256个字节*/ char secret[MAXWORDLEN]; 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] != 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[256]; 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[unit]; 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[i]) != 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[namelen] = 0; script_setenv("PEERNAME", peer_authname, 0); /* Save the authentication method for later. */ auth_done[unit] |= bit; /* * If there is no more authentication still to be done, * proceed to the network (or callback) phase. */ if ((auth_pending[unit] &= ~bit) == 0) network_phase(unit); }

 

main.c里有一个get_input()函数,它用于处理每个来自客户端的数据包,当数据里包含用户名和密码里,它会执行相应的函数来处理用户名和密 码。如果使用PAP认证方式,那么函数会调用upap.c里的upap_input()函数,这个函数再调用同文件下的upap_rauthreq()函 数,这个函数再调用auth.c文件里的check_passwd()函数处理用户名和密码。

main.c的get_input()函数:

u_char inpacket_buf[PPP_MRU+PPP_HDRLEN]; /* 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数据包。


未完待继。。。

你可能感兴趣的:(Network)