【Busybox】Busybox source code analysis-04 | ash and login program

busybox version:1.35.0

1. Ash program entry analysis

The ash program is the default shell started in the busybox after the linux kernel enters the busybox at the later stage, and is used to respond to and execute command input. ashThe operation entry is ash_main()represented by a function, which is defined in the /shell/ash.c file.

Paste ash_mainthe complete code of the function (from /shell/ash.c):

int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
#if NUM_SCRIPTS > 0
int ash_main(int argc, char **argv)
#else
int ash_main(int argc UNUSED_PARAM, char **argv)
#endif
/* note: 'argc' is used only if embedded scripts are enabled */
{
    
    
	volatile smallint state;
	struct jmploc jmploc;
	struct stackmark smark;
	int login_sh;

	/* Initialize global data */
	INIT_G_misc();
	INIT_G_memstack();
	INIT_G_var();
#if ENABLE_ASH_ALIAS
	INIT_G_alias();
#endif
	INIT_G_cmdtable();

#if PROFILE
	monitor(4, etext, profile_buf, sizeof(profile_buf), 50);
#endif

	state = 0;
	if (setjmp(jmploc.loc)) {
    
    
		smallint e;
		smallint s;

		exitreset();

		e = exception_type;
		s = state;
		if (e == EXEND || e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
    
    
			exitshell();
		}

		reset();

		if (e == EXINT) {
    
    
			newline_and_flush(stderr);
		}

		popstackmark(&smark);
		FORCE_INT_ON; /* enable interrupts */
		if (s == 1)
			goto state1;
		if (s == 2)
			goto state2;
		if (s == 3)
			goto state3;
		goto state4;
	}
	exception_handler = &jmploc;
	rootpid = getpid();

	init();
	setstackmark(&smark);

#if NUM_SCRIPTS > 0
	if (argc < 0)
		/* Non-NULL minusc tells procargs that an embedded script is being run */
		minusc = get_script_content(-argc - 1);
#endif
	login_sh = procargs(argv);
#if DEBUG
	TRACE(("Shell args: "));
	trace_puts_args(argv);
#endif

	if (login_sh) {
    
    
		const char *hp;

		state = 1;
		read_profile("/etc/profile");
 state1:
		state = 2;
		hp = lookupvar("HOME");
		if (hp)
			read_profile("$HOME/.profile");
	}
 state2:
	state = 3;
	if (iflag
#ifndef linux
	 && getuid() == geteuid() && getgid() == getegid()
#endif
	) {
    
    
		const char *shinit = lookupvar("ENV");
		if (shinit != NULL && *shinit != '\0')
			read_profile(shinit);
	}
	popstackmark(&smark);
 state3:
	state = 4;
	if (minusc) {
    
    
		/* evalstring pushes parsefile stack.
		 * Ensure we don't falsely claim that 0 (stdin)
		 * is one of stacked source fds.
		 * Testcase: ash -c 'exec 1>&0' must not complain. */

		// if (!sflag) g_parsefile->pf_fd = -1;
		// ^^ not necessary since now we special-case fd 0
		// in save_fd_on_redirect()

		lineno = 0; // bash compat
		// dash: evalstring(minusc, sflag ? 0 : EV_EXIT);
		// The above makes
		//  ash -sc 'echo $-'
		// continue reading input from stdin after running 'echo'.
		// bash does not do this: it prints "hBcs" and exits.
		evalstring(minusc, EV_EXIT);
	}

	if (sflag || minusc == NULL) {
    
    
#if MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY
		if (line_input_state) {
    
    
			const char *hp = lookupvar("HISTFILE");
			if (!hp) {
    
    
				hp = lookupvar("HOME");
				if (hp) {
    
    
					INT_OFF;
					hp = concat_path_file(hp, ".ash_history");
					setvar0("HISTFILE", hp);
					free((char*)hp);
					INT_ON;
					hp = lookupvar("HISTFILE");
				}
			}
			if (hp)
				line_input_state->hist_file = xstrdup(hp);
# if ENABLE_FEATURE_SH_HISTFILESIZE
			hp = lookupvar("HISTFILESIZE");
			line_input_state->max_history = size_from_HISTFILESIZE(hp);
# endif
		}
#endif
 state4: /* XXX ??? - why isn't this before the "if" statement */
		cmdloop(1);
	}
#if PROFILE
	monitor(0);
#endif
#ifdef GPROF
	{
    
    
		extern void _mcleanup(void);
		_mcleanup();
	}
#endif
	TRACE(("End of main reached\n"));
	exitshell();
	/* NOTREACHED */
}

This function is described in sections below.

The first is to call several INIT_G_XXXnamed macro definitions:

	INIT_G_misc();
	INIT_G_memstack();
	INIT_G_var();
#if ENABLE_ASH_ALIAS
	INIT_G_alias();
#endif
	INIT_G_cmdtable();

The above functions are used to initialize global variables.

Then stateset the variable value to 0:

state = 0;

Then call a function in the C language standard library setjmp()to implement the exception handling mechanism:

	if (setjmp(jmploc.loc)) {
    
    
		smallint e;
		smallint s;

		exitreset();

		e = exception_type;
		s = state;
		if (e == EXEND || e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
    
    
			exitshell();
		}

		reset();

		if (e == EXINT) {
    
    
			newline_and_flush(stderr);
		}

		popstackmark(&smark);
		FORCE_INT_ON; /* enable interrupts */
		
		printf("s : %d\r\n",s);
		if (s == 1)
			goto state1;
		if (s == 2)
			goto state2;
		if (s == 3)
			goto state3;
		goto state4;
	}
	exception_handler = &jmploc;

In the following code, it will be called procargs(argv)to process the command line parameters; it will be called to read_profile("/etc/profile")read the configuration file, which is the description file for configuring the shell that we need to add in busybox.

At the end, the function is called cmdloop(1)to execute the command line loop operation. This function is used to read the execution command.

Two, ash_main summary

ash_main()Functions are used for initialization, parsing parameters, executing /etc/profileconfiguration files, and then calling cmdloop()to execute commands. setjmp()function is a C library function that sets the location to jump to when an event occurs. The variable "state" is where the jump is calculated when an exception occurs.


Three, login process

After busybox is running, you can enter loginthe command on the command line to run the login program. Under the default busybox configuration, after starting busybox, the ash program will be executed instead of the login program. In actual application requirements, we can set login as the running program after busybox starts. Methods as below:

(1) Use make menuconfigcompilation to build the graphical configuration interface of busybox, and select the following options:

(2) Enter Login/Password Management Utilitiesthe option and configure all the items under this configuration:

(3) Use make -j12compile to build busybox.

(4) Install busybox

Through the above steps, the busybox at this time supports the login program. Next, set the startup item in the /etc/inittab file:

::respawn:-/bin/login
或者
::respawn:/sbin/getty 115200 console

Note: Choose one of the above configurations

Configure /etc/group, /etc/passwd, /etc/shadow three files (if not in busybox, you need to create it yourself
)

  • In the /etc/group file, add the following configuration:
root:x:0:root
  • The /etc/passwd file needs to have the password information of the root user:
root:x:0:0:root:/root:/bin/sh

The contents of the /etc/passed file here are as follows:

  • Configure the user (here is the root user) password in the /etc/shadow file:
root:DH9Ade75qXIdI:1:0:99999:7:::

DH9Ade75qXIdI indicates the set password

This string of ciphertext can be mkpasswdgenerated using commands. After entering mkpasswd in the command line terminal, you will be prompted to enter a password. At this time, enter the plaintext password we want to set, and press the Enter key after completion to generate a string in crypt format:

The above operations set the login password to iriczhao, and the user name to root.

So far, through the above steps, the login configuration is completed. After running busybox, you can enter the login program, as shown in the following figure:

After typing root and password (this article is iriczhao), you can enter the shell.

Four, login program entrance analysis

According to the tool features of busybox, knowing loginthe corresponding entry of the program is login_main(), this section will analyze the function:

After running the program in busybox , you will be prompted to enter the login name, and then you will be prompted to enter the password. After pressing the Enter key, it will verify whether the login password is correct. This series of operations is completed loginby the structure in the login_main function. while(1){}The code is as follows (from /loginutils/login.c):

while (1) {
    
    
		/* flush away any type-ahead (as getty does) */
		tcflush(0, TCIFLUSH);

		if (!username[0])
			get_username_or_die(username, sizeof(username));

#if ENABLE_PAM
		pamret = pam_start("login", username, &conv, &pamh);
		if (pamret != PAM_SUCCESS) {
    
    
			failed_msg = "start";
			goto pam_auth_failed;
		}
		/* set TTY (so things like securetty work) */
		pamret = pam_set_item(pamh, PAM_TTY, short_tty);
		if (pamret != PAM_SUCCESS) {
    
    
			failed_msg = "set_item(TTY)";
			goto pam_auth_failed;
		}
		/* set RHOST */
		if (opt_host) {
    
    
			pamret = pam_set_item(pamh, PAM_RHOST, opt_host);
			if (pamret != PAM_SUCCESS) {
    
    
				failed_msg = "set_item(RHOST)";
				goto pam_auth_failed;
			}
		}
		if (!(opt & LOGIN_OPT_f)) {
    
    
			pamret = pam_authenticate(pamh, 0);
			if (pamret != PAM_SUCCESS) {
    
    
				failed_msg = "authenticate";
				goto pam_auth_failed;
				/* TODO: or just "goto auth_failed"
				 * since user seems to enter wrong password
				 * (in this case pamret == 7)
				 */
			}
		}
		/* check that the account is healthy */
		pamret = pam_acct_mgmt(pamh, 0);
		if (pamret == PAM_NEW_AUTHTOK_REQD) {
    
    
			pamret = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
		}
		if (pamret != PAM_SUCCESS) {
    
    
			failed_msg = "acct_mgmt";
			goto pam_auth_failed;
		}
		/* read user back */
		pamuser = NULL;
		/* gcc: "dereferencing type-punned pointer breaks aliasing rules..."
		 * thus we cast to (void*) */
		if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) {
    
    
			failed_msg = "get_item(USER)";
			goto pam_auth_failed;
		}
		if (!pamuser || !pamuser[0])
			goto auth_failed;
		safe_strncpy(username, pamuser, sizeof(username));
		/* Don't use "pw = getpwnam(username);",
		 * PAM is said to be capable of destroying static storage
		 * used by getpwnam(). We are using safe(r) function */
		pw = NULL;
		getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw);
		if (!pw)
			goto auth_failed;
		pamret = pam_open_session(pamh, 0);
		if (pamret != PAM_SUCCESS) {
    
    
			failed_msg = "open_session";
			goto pam_auth_failed;
		}
		pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED);
		if (pamret != PAM_SUCCESS) {
    
    
			failed_msg = "setcred";
			goto pam_auth_failed;
		}
		break; /* success, continue login process */

 pam_auth_failed:
		/* syslog, because we don't want potential attacker
		 * to know _why_ login failed */
		syslog(LOG_WARNING, "pam_%s call failed: %s (%d)", failed_msg,
					pam_strerror(pamh, pamret), pamret);
		login_pam_end(pamh);
		safe_strncpy(username, "UNKNOWN", sizeof(username));
#else /* not PAM */
		pw = getpwnam(username);
		if (!pw) {
    
    
			strcpy(username, "UNKNOWN");
			goto fake_it;
		}

		if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*')
			goto auth_failed;

		if (opt & LOGIN_OPT_f)
			break; /* -f USER: success without asking passwd */

		if (pw->pw_uid == 0 && !is_tty_secure(short_tty))
			goto auth_failed;

		/* Don't check the password if password entry is empty (!) */
		if (!pw->pw_passwd[0])
			break;
 fake_it:
		/* Password reading and authorization takes place here.
		 * Note that reads (in no-echo mode) trash tty attributes.
		 * If we get interrupted by SIGALRM, we need to restore attrs.
		 */
		if (ask_and_check_password(pw) > 0)
			break;
#endif /* ENABLE_PAM */
 auth_failed:
		opt &= ~LOGIN_OPT_f;
		pause_after_failed_login();
		/* TODO: doesn't sound like correct English phrase to me */
		puts("Login incorrect");
		syslog(LOG_WARNING, "invalid password for '%s'%s",
					username, fromhost);
		if (++count == 3) {
    
    
			if (ENABLE_FEATURE_CLEAN_UP)
				free(fromhost);
			return EXIT_FAILURE;
		}
		username[0] = '\0';
	} /* while (1) */

The important data structure operated in the login_main function pwis pwa struct passwdpointer to a structure, which is defined as follows:

#include <sys/types.h> 
#include <pwd.h> 
struct passwd {
    
    
   char *pw_name;                /* 用户登录名 */
   char *pw_passwd;              /* 密码(加密后) */
   __uid_t pw_uid;               /* 用户ID */
   __gid_t pw_gid;               /* 组ID */
   char *pw_gecos;               /* 详细用户名 */
   char *pw_dir;                 /* 用户目录 */
   char *pw_shell;               /* Shell程序名 */ 
};

login_main()Use in function :

pw = getpwnam(username);

Get user information based on username.

breakIf the input is correct, it will use the jump out loop in while(1) to continue to execute the subsequent code; if the verification fails, it will jump to auth_failedthe label and return EXIT_FAILURE.

At the end of the login_main function, exec_login_shell(pw->pw_shell);the login shell is invoked. Essentially execv()a system call:

Five, login_main summary

After entering the login command on the command line, the login program will be executed; you can also set the login program as the program to be executed after the busybox starts, so as to realize the login mode with the user name and password. In the buildroot construction tool, the login mechanism is automatically implemented. You only need to enable and configure the login password in the graphical configuration interface.

Guess you like

Origin blog.csdn.net/iriczhao/article/details/127500315