int main(int argc, char **argv)
{
extern int optind;
extern char *optarg, **environ;
struct group *gr;
register int ch;
register char *p;
int ask, fflag, hflag, pflag, cnt, errsv;
int quietlog, passwd_req;
char *domain, *ttyn;
char tbuf[MAXPATHLEN + 2], tname[sizeof(_PATH_TTY) + 10];
char *termenv;
char *childArgv[10];
char *buff;
int childArgc = 0;
#ifdef HAVE_SECURITY_PAM_MISC_H
int retcode;
pam_handle_t *pamh = NULL;
struct pam_conv conv = { misc_conv, NULL };
pid_t childPid;
#else
char *salt, *pp;
#endif
#ifdef LOGIN_CHOWN_VCS
char vcsn[20], vcsan[20];
#endif
pid = getpid();//得到当前进程的pid号
signal(SIGALRM, timedout);//设置定时信号的处理函数timedout
alarm((unsigned int)timeout);//设置定时器timeout = 60s
signal(SIGQUIT, SIG_IGN);//忽略SIGQUIT信号
signal(SIGINT, SIG_IGN);//忽略SIGINT信号
setlocale(LC_ALL, "");//本函数用来配置地域的信息,设置当前程序使用的本地化信息.使用系统默认的设置
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
//用来设置进程执行优先权为0
setpriority(PRIO_PROCESS, 0, 0);
initproctitle(argc, argv);
gethostname(tbuf, sizeof(tbuf));//得到主机名,存到tbuf中
xstrncpy(thishost, tbuf, sizeof(thishost));//将主机名复制到thishost中
domain = index(tbuf, '.');//得到域名"."隔开,#define index strchr,查找字符串tbuf中首次出现字符‘.’的位置
username = tty_name = hostname = NULL;
fflag = hflag = pflag = 0;
passwd_req = 1;//表示输入的用户名需要进行密码验证,为0则不需密码验证
while ((ch = getopt(argc, argv, "fh:p")) != -1)//分析命令行参数
switch (ch) {
case 'f':
fflag = 1;
break;
case 'h':
if (getuid()) {
fprintf(stderr, _("login: -h for super-user only.\n"));
exit(1);
}
hflag = 1;
if (domain && (p = index(optarg, '.')) && strcasecmp(p, domain) == 0)
*p = 0;
hostname = strdup(optarg); /* strdup: Ambrose C. Li */
{
struct hostent *he = gethostbyname(hostname);/* he points to static storage; copy the part we use */
hostaddress[0] = 0;
if (he && he->h_addr_list && he->h_addr_list[0])
memcpy(hostaddress, he->h_addr_list[0],sizeof(hostaddress));
}
break;
case 'p':
pflag = 1;
break;
case '?':
default:
fprintf(stderr, _("usage: login [-fp] [username]\n"));
exit(1);
}
argc -= optind;
argv += optind;
if (*argv) {
char *p = *argv;
//strdup()在内部调用了malloc()为变量分配内存,不需要使用返回的字符串时,需要用free()释放相应的内存空间,否则会造成内存泄漏
username = strdup(p);//复制字符串给username,得到登录名
ask = 0;
while(*p)
*p++ = ' ';//登录名最后一个字符为空
} else
ask = 1;
for (cnt = getdtablesize(); cnt > 2; cnt--)//返回所在进程的文件描述附表的项数,即该进程打开的文件数目。
close(cnt);//关闭打开的文件
//用ttyname(0) 或 ttyname(1) 或 ttyname(2)是可以得到的,因为0、1、2分别是与具体终端相连的标准输入、标准输出和标准出错输出的文件描述符。所以可以得到。
ttyn = ttyname(0);//得到终端标示号
if (ttyn == NULL || *ttyn == '\0') {
sprintf(tname, "%s??", _PATH_TTY);
ttyn = tname;
}
//检查终端设备
check_ttyname(ttyn);
//将终端设备名赋给tty_name
if (strncmp(ttyn, "/dev/", 5) == 0)
tty_name = ttyn+5;
else
tty_name = ttyn;
//将终端设备号赋给tty_number
if (strncmp(ttyn, "/dev/tty", 8) == 0)
tty_number = ttyn+8;
else {
char *p = ttyn;
while (*p && !isdigit(*p)) p++;
tty_number = p;
}
//将目前进程所属的组识别码设为目前进程的进程识别码。
setpgrp();
//设置并打开终端设备
{
struct termios tt, ttt;
tcgetattr(0, &tt);
ttt = tt;
ttt.c_cflag &= ~HUPCL;//清除关闭设备时挂起标志
/* These can fail, e.g. with ttyn on a read-only filesystem */
chown(ttyn, 0, 0);
chmod(ttyn, TTY_MODE);
/* Kill processes left on this tty */
tcsetattr(0,TCSAFLUSH,&ttt);
signal(SIGHUP, SIG_IGN);//忽略挂起信号,所以vhangup()不会杀死程序
vhangup();//挂起当前终端
signal(SIGHUP, SIG_DFL);//恢复
//打开终端设备,标准输入,标准输出,出错都链接到终端设备上
opentty(ttyn);
tcsetattr(0,TCSAFLUSH,&tt);/* restore tty modes */
}
openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
#ifdef HAVE_SECURITY_PAM_MISC_H
retcode = pam_start("login",username, &conv, &pamh);//调用login的PAM配置文件,pam_handle_t pamh是保存所有pam信息的地方.由pam_start函数创建.这些信息,可以通过pam_set_item与pam_get_item来设置与获取.
if(retcode != PAM_SUCCESS) {
fprintf(stderr, _("login: PAM Failure, aborting: %s\n"),pam_strerror(pamh, retcode));
syslog(LOG_ERR, _("Couldn't initialize PAM: %s"),pam_strerror(pamh, retcode));
exit(99);
}
/* int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item);
int pam_set_item(pam_handle_t *pamh, int item_type, const void **item);
通过该函数,可以得到的值有:
PAM_SERVICE:服务名
PAM_USER:用户名,该用户名,指的是在pam_start的第二个参数所设置的.也可以通过pam_set_item来设置
PAM_USER_PROMPT: 当提示输入用户名时的指示字符串.默认为login:
PAM_TTY: 用户程序对应的tty
PAM_RHOST: 该用户程序使用的 远程主机信息.
PAM_CONV: 该用户程序对应的pam_conv()函数. */
retcode = pam_set_item(pamh, PAM_RHOST, hostname);//设置PAM的远程主机信息.
PAM_FAIL_CHECK;
retcode = pam_set_item(pamh, PAM_TTY, tty_name);//设置用户程序对应的tty、
PAM_FAIL_CHECK;
retcode = pam_set_item(pamh, PAM_USER_PROMPT, _("login: "));//当提示输入用户名时的指示字符串
PAM_FAIL_CHECK;
/* if fflag == 1, then the user has already been authenticated */
if (fflag && (getuid() == 0))
passwd_req = 0;
else
passwd_req = 1;
if(passwd_req == 1) {//需要验证登录用户名密码
int failcount=0;
pam_get_item(pamh, PAM_USER, (const void **) &username);//获得用户名保存在username中
if (!username)
pam_set_item(pamh, PAM_USER, NULL);
retcode = pam_authenticate(pamh, 0);////进行auth类型认证,检查用户输入的密码是否正确
while((failcount++ < PAM_MAX_LOGIN_TRIES) &&((retcode == PAM_AUTH_ERR) ||(retcode == PAM_USER_UNKNOWN) ||(retcode == PAM_CRED_INSUFFICIENT) ||(retcode == PAM_AUTHINFO_UNAVAIL))) {
pam_get_item(pamh, PAM_USER, (const void **) &username);
syslog(LOG_NOTICE,_("FAILED LOGIN %d FROM %s FOR %s, %s"),failcount, hostname, username, pam_strerror(pamh, retcode));
logbtmp(tty_name, username, hostname);
fprintf(stderr,_("Login incorrect\n\n"));
pam_set_item(pamh,PAM_USER,NULL);
retcode = pam_authenticate(pamh, 0);
}
if (retcode != PAM_SUCCESS) {//达到一定的出错次数,则退出
pam_get_item(pamh, PAM_USER, (const void **) &username);
if (retcode == PAM_MAXTRIES)
syslog(LOG_NOTICE,_("TOO MANY LOGIN TRIES (%d) FROM %s FOR ""%s, %s"), failcount, hostname, username,pam_strerror(pamh, retcode));
else
syslog(LOG_NOTICE,_("FAILED LOGIN SESSION FROM %s FOR %s, %s"),hostname, username, pam_strerror(pamh, retcode));
logbtmp(tty_name, username, hostname);
fprintf(stderr,_("\nLogin incorrect\n"));
pam_end(pamh, retcode);
exit(0);
}
retcode = pam_acct_mgmt(pamh, 0);//进行account类型认证,通过了密码认证之后再调用帐号管理API,检查用户帐号是否已经过期
if(retcode == PAM_NEW_AUTHTOK_REQD) {
retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
}
PAM_FAIL_CHECK;
}
//再次取得用户名
retcode = pam_get_item(pamh, PAM_USER, (const void **) &username);
PAM_FAIL_CHECK;
if (!username || !*username) {//为空的话出错退出
fprintf(stderr, _("\nSession setup problem, abort.\n"));
syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."),__FUNCTION__, __LINE__);
pam_end(pamh, PAM_SYSTEM_ERR);
exit(1);
}
if (!(pwd = getpwnam(username))) {//获取用户登录相关信息,读取口令文件/etc/passwd获得username的登录相关信息
fprintf(stderr, _("\nSession setup problem, abort.\n"));
syslog(LOG_ERR, _("Invalid user name \"%s\" in %s:%d. Abort."),username, __FUNCTION__, __LINE__);
pam_end(pamh, PAM_SYSTEM_ERR);
exit(1);
}
//复制用户登录信息
memcpy(&pwdcopy, pwd, sizeof(*pwd));
pwd = &pwdcopy;
pwd->pw_name = strdup(pwd->pw_name);
pwd->pw_passwd = strdup(pwd->pw_passwd);
pwd->pw_gecos = strdup(pwd->pw_gecos);
pwd->pw_dir = strdup(pwd->pw_dir);
pwd->pw_shell = strdup(pwd->pw_shell);
if (!pwd->pw_name || !pwd->pw_passwd || !pwd->pw_gecos ||!pwd->pw_dir || !pwd->pw_shell) {
fprintf(stderr, _("login: Out of memory\n"));
syslog(LOG_ERR, "Out of memory");
pam_end(pamh, PAM_SYSTEM_ERR);
exit(1);
}
username = pwd->pw_name;
//因为每个用户可以属于多个组,该函数将user所属的所有组还有group都添加到当前运行进程的有效组,即将这些要添加的组作为添加组。
if (initgroups(username, pwd->pw_gid) < 0) {
syslog(LOG_ERR, "initgroups: %m");
fprintf(stderr, _("\nSession setup problem, abort.\n"));
pam_end(pamh, PAM_SYSTEM_ERR);
exit(1);
}
retcode = pam_open_session(pamh, 0);//通过帐户管理检查之后则打开会话
PAM_FAIL_CHECK;
retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);//建立认证服务的用户证书
PAM_FAIL_CHECK;
#else//若是没有定义PAM机制,则进行以下操作
for (cnt = 0;; ask = 1) {
if (ask) {
fflag = 0;
getloginname();//获得用户名,存在username中
}
if (username[0] == '+') {
puts(_("Illegal username"));
badlogin(username);
sleepexit(1);
}
//取得用户登录相关信息
if ((pwd = getpwnam(username))) {//读取口令文件/etc/passwd获得登录相关信息
salt = pwd->pw_passwd;//加密口令
} else
salt = "xx";
if (pwd) {
initgroups(username, pwd->pw_gid);
checktty(username, tty_name, pwd); /* in checktty.c */
}
/* if user not super-user, check for disabled logins */
if (pwd == NULL || pwd->pw_uid)
checknologin();
/*
* Disallow automatic login to root; if not invoked by
* root, disallow if the uid's differ.
*/
if (fflag && pwd) {
int uid = getuid();
passwd_req = pwd->pw_uid == 0 ||(uid && uid != pwd->pw_uid);
}
if (pwd && pwd->pw_uid == 0 && !rootterm(tty_name)) {
fprintf(stderr,_("%s login refused on this terminal.\n"),pwd->pw_name);
if (hostname)
syslog(LOG_NOTICE,_("LOGIN %s REFUSED FROM %s ON TTY %s"),pwd->pw_name, hostname, tty_name);
else
syslog(LOG_NOTICE,_("LOGIN %s REFUSED ON TTY %s"),pwd->pw_name, tty_name);
continue;
}
//若passwd_req设置于为0(表示不需输入密码,直接登录),或没有密码则不需登录比较密码,直接登录
if (!passwd_req || (pwd && !*pwd->pw_passwd))
break;
setpriority(PRIO_PROCESS, 0, -4);//设置进程优先级
pp = getpass(_("Password: "));//读入密码
//crypt返回使用 DES、Blowfish 或 MD5 加密的字符串
//key:要加密的明文。salt:密钥。
p = crypt(pp, salt);
setpriority(PRIO_PROCESS, 0, 0);//设置进程优先级
memset(pp, 0, strlen(pp));//清空pp
if (pwd && !strcmp(p, pwd->pw_passwd))//若是密码正确就退出for循环,不用再输入用户名和密码再判断
break;
//密码不正确,往下处理
printf(_("Login incorrect\n"));
badlogin(username); /* log ALL bad logins */
failures++;
//错误超过10次会退出
if (++cnt > 3) {
if (cnt >= 10) {
sleepexit(1);
}
sleep((unsigned int)((cnt - 3) * 5));
}
}
#endif /* !HAVE_SECURITY_PAM_MISC_H */
//往下去。表示用户名和密码匹配成功
alarm((unsigned int)0);//turn off timeout
endpwent();//用来关闭由所打开的密码文件/etc/passwd
{
char tmpstr[MAXPATHLEN];
uid_t ruid = getuid();//返回一个调用程序的真实用户ID
gid_t egid = getegid();//用来取得执行目前进程有效组识别码
//比较初始工作目录长度是否大于设定的长度
if (strlen(pwd->pw_dir) + sizeof(_PATH_HUSHLOGIN) + 2 > MAXPATHLEN)
quietlog = 0;
else {
sprintf(tmpstr, "%s/%s", pwd->pw_dir, _PATH_HUSHLOGIN);
setregid(-1, pwd->pw_gid);//rgid设为目前进程的真实组识别码(不变),将pwd->pw_gid设为目前进程的真实组识别码
setreuid(0, pwd->pw_uid);//
quietlog = (access(tmpstr, R_OK) == 0);//初始目录可访问,则quietlog置为1
setuid(0);//设置实际用户ID和有效用户ID
setreuid(ruid, 0);
setregid(-1, egid);
}
}
//登录账户记录
//utmp记录当前登录进系统的各个用户;wtmp文件跟踪各个登录和注销事件
{
struct utmp ut;
struct utmp *utp;
utmpname(_PATH_UTMP);
setutent();//打开utmp文件
while ((utp = getutent()))//读出一个记录
if (utp->ut_pid == pid&& utp->ut_type >= INIT_PROCESS&& utp->ut_type <= DEAD_PROCESS)
break;
if (utp == NULL) {
setutent();
ut.ut_type = LOGIN_PROCESS;
strncpy(ut.ut_line, tty_name, sizeof(ut.ut_line));
utp = getutline(&ut);
}
if (utp) {
memcpy(&ut, utp, sizeof(ut));
} else {
memset(&ut, 0, sizeof(ut));
}
if (ut.ut_id[0] == 0)
strncpy(ut.ut_id, tty_number, sizeof(ut.ut_id));
strncpy(ut.ut_user, username, sizeof(ut.ut_user));
xstrncpy(ut.ut_line, tty_name, sizeof(ut.ut_line));
time_t t;
time(&t);
ut.ut_time = t;//设置登录时间
ut.ut_type = USER_PROCESS;
ut.ut_pid = pid;
if (hostname) {
xstrncpy(ut.ut_host, hostname, sizeof(ut.ut_host));
if (hostaddress[0])
memcpy(&ut.ut_addr, hostaddress, sizeof(ut.ut_addr));
}
pututline(&ut);
endutent();//关闭文件
//更新wtmp文件
#if HAVE_UPDWTMP
updwtmp(_PATH_WTMP, &ut);
#else
{
int lf, wtmp;
if ((lf = open(_PATH_WTMPLOCK, O_CREAT|O_WRONLY, 0660)) >= 0) {
flock(lf, LOCK_EX);
if ((wtmp = open(_PATH_WTMP, O_APPEND|O_WRONLY)) >= 0) {
write(wtmp, (char *)&ut, sizeof(ut));
close(wtmp);
}
flock(lf, LOCK_UN);
close(lf);
}
}
#endif
}
dolastlog(quietlog);
chown(ttyn, pwd->pw_uid,(gr = getgrnam(TTYGRPNAME)) ? gr->gr_gid : pwd->pw_gid);
chmod(ttyn, TTY_MODE);
setgid(pwd->pw_gid);
if (*pwd->pw_shell == '\0')
pwd->pw_shell = _PATH_BSHELL;
/* preserve TERM even without -p flag */
{
char *ep;
if(!((ep = getenv("TERM")) && (termenv = strdup(ep))))
termenv = "dumb";
}
/* destroy environment unless user has requested preservation */
if (!pflag)
{
environ = (char**)malloc(sizeof(char*));
memset(environ, 0, sizeof(char*));
}
//设置shell的环境变量
setenv("HOME", pwd->pw_dir, 0); /* legal to override */
if(pwd->pw_uid)
setenv("PATH", _PATH_DEFPATH, 1);
else
setenv("PATH", _PATH_DEFPATH_ROOT, 1);
setenv("SHELL", pwd->pw_shell, 1);
setenv("TERM", termenv, 1);
{
char tmp[MAXPATHLEN];
if (sizeof(_PATH_MAILDIR) + strlen(pwd->pw_name) + 1 < MAXPATHLEN) {
sprintf(tmp, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
setenv("MAIL",tmp,0);
}
}
setenv("LOGNAME", pwd->pw_name, 1);
#ifdef HAVE_SECURITY_PAM_MISC_H
{
int i;
char ** env = pam_getenvlist(pamh);//获取pam环境变量
if (env != NULL) {
for (i=0; env[i]; i++) {
putenv(env[i]);//把字符串加到当前环境中
}
}
}
#endif
setproctitle("login", username);
if (!strncmp(tty_name, "ttyS", 4))
syslog(LOG_INFO, _("DIALUP AT %s BY %s"), tty_name, pwd->pw_name);
if (pwd->pw_uid == 0) {
if (hostname)
syslog(LOG_NOTICE, _("ROOT LOGIN ON %s FROM %s"),tty_name, hostname);
else
syslog(LOG_NOTICE, _("ROOT LOGIN ON %s"), tty_name);
} else {
if (hostname)
syslog(LOG_INFO, _("LOGIN ON %s BY %s FROM %s"), tty_name, pwd->pw_name, hostname);
else
syslog(LOG_INFO, _("LOGIN ON %s BY %s"), tty_name, pwd->pw_name);
}
if (!quietlog) {
motd();
}
signal(SIGALRM, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_IGN);
#ifdef HAVE_SECURITY_PAM_MISC_H
childPid = fork();//创建子进程
if (childPid < 0) {
int errsv = errno;
fprintf(stderr, _("login: failure forking: %s"), strerror(errsv));
PAM_END;
exit(0);
}
if (childPid) {//父进程,等待子进程退出
/* parent - wait for child to finish, then cleanup session */
signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);//忽略以上信号
wait(NULL);//等待子进程结束
PAM_END;//PAM结束
exit(0);
}
//以下是子进程/* child */
/* start new session */
setsid();//进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
/* make sure we have a controlling tty */
opentty(ttyn);//子进程打开设备终端
openlog("login", LOG_ODELAY, LOG_AUTHPRIV); //重新打开log文件
//TIOCSCTTY: steal tty from other process group
if (ioctl(0, TIOCSCTTY, 1))
syslog(LOG_ERR, _("TIOCSCTTY failed: %m"));
#endif
signal(SIGINT, SIG_DFL);//设置信号为默认处理
//设置实际用户ID和有效用户ID
if(setuid(pwd->pw_uid) < 0 && pwd->pw_uid) {
syslog(LOG_ALERT, _("setuid() failed"));
exit(1);
}
//进入到初始工作目录
if (chdir(pwd->pw_dir) < 0) {
printf(_("No directory %s!\n"), pwd->pw_dir);
if (chdir("/"))
exit(0);
pwd->pw_dir = "/";
printf(_("Logging in with home = \"/\".\n"));
}
//给shell执行命令的字符串分配空间
if (strchr(pwd->pw_shell, ' ')) {
buff = malloc(strlen(pwd->pw_shell) + 6);
if (!buff) {
fprintf(stderr, _("login: no memory for shell script.\n"));
exit(0);
}
strcpy(buff, "exec ");
strcat(buff, pwd->pw_shell);
childArgv[childArgc++] = "/bin/sh";
childArgv[childArgc++] = "-sh";
childArgv[childArgc++] = "-c";
childArgv[childArgc++] = buff;
} else {
tbuf[0] = '-';
xstrncpy(tbuf + 1, ((p = rindex(pwd->pw_shell, '/')) ?p + 1 : pwd->pw_shell),sizeof(tbuf)-1);
childArgv[childArgc++] = pwd->pw_shell;
childArgv[childArgc++] = tbuf;
}
childArgv[childArgc++] = NULL;
//登录成功,执行/bin/sh进入shell
execvp(childArgv[0], childArgv + 1);
errsv = errno;
if (!strcmp(childArgv[0], "/bin/sh"))
fprintf(stderr, _("login: couldn't exec shell script: %s.\n"),strerror(errsv));
else
fprintf(stderr, _("login: no shell: %s.\n"), strerror(errsv));
exit(0);
}