PAM登录代码

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);
}

你可能感兴趣的:(linux)