- 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);
- }
系统登录函数login.c
最新推荐文章于 2022-09-27 12:09:53 发布