mirror of
				https://github.com/clearml/dropbear
				synced 2025-06-26 18:17:32 +00:00 
			
		
		
		
	added keyboard-interactive client support
--HG-- extra : convert_revision : 3df738e42f4fc8b7f0f3ff9ca767386f54edb1ea
This commit is contained in:
		
							parent
							
								
									cb2cb15916
								
							
						
					
					
						commit
						876b7081d8
					
				| @ -29,7 +29,7 @@ SVROBJS=svr-kex.o svr-algo.o svr-auth.o sshpty.o \ | ||||
| 
 | ||||
| CLIOBJS=cli-algo.o cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o \
 | ||||
| 		cli-session.o cli-service.o cli-runopts.o cli-chansession.o \
 | ||||
| 		cli-authpubkey.o cli-tcpfwd.o cli-channel.o | ||||
| 		cli-authpubkey.o cli-tcpfwd.o cli-channel.o cli-authinteract.o | ||||
| 
 | ||||
| CLISVROBJS=common-session.o packet.o common-algo.o common-kex.o \
 | ||||
| 			common-channel.o common-chansession.o termcodes.o loginrec.o \
 | ||||
|  | ||||
							
								
								
									
										18
									
								
								auth.h
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								auth.h
									
									
									
									
									
								
							| @ -41,28 +41,36 @@ void svr_auth_pam(); | ||||
| /* Client functions */ | ||||
| void recv_msg_userauth_failure(); | ||||
| void recv_msg_userauth_success(); | ||||
| void recv_msg_userauth_specific_60(); | ||||
| void recv_msg_userauth_pk_ok(); | ||||
| void recv_msg_userauth_info_request(); | ||||
| void cli_get_user(); | ||||
| void cli_auth_getmethods(); | ||||
| void cli_auth_try(); | ||||
| void recv_msg_userauth_banner(); | ||||
| void cli_pubkeyfail(); | ||||
| int cli_auth_password(); | ||||
| void cli_auth_password(); | ||||
| int cli_auth_pubkey(); | ||||
| void cli_auth_interactive(); | ||||
| 
 | ||||
| 
 | ||||
| #define MAX_USERNAME_LEN 25 /* arbitrary for the moment */ | ||||
| 
 | ||||
| #define AUTH_TYPE_PUBKEY	1 << 0 | ||||
| #define AUTH_TYPE_PASSWORD	1 << 1 | ||||
| #define AUTH_TYPE_NONE      1 | ||||
| #define AUTH_TYPE_PUBKEY    1 << 1 | ||||
| #define AUTH_TYPE_PASSWORD  1 << 2 | ||||
| #define AUTH_TYPE_INTERACT  1 << 3 | ||||
| 
 | ||||
| /* auth types, "none" means we should return list of acceptable types */ | ||||
| #define AUTH_METHOD_NONE	"none" | ||||
| #define AUTH_METHOD_NONE "none" | ||||
| #define AUTH_METHOD_NONE_LEN 4 | ||||
| #define AUTH_METHOD_PUBKEY "publickey" | ||||
| #define AUTH_METHOD_PUBKEY_LEN 9 | ||||
| #define AUTH_METHOD_PASSWORD "password" | ||||
| #define AUTH_METHOD_PASSWORD_LEN 8 | ||||
| #define AUTH_METHOD_INTERACT "keyboard-interactive" | ||||
| #define AUTH_METHOD_INTERACT_LEN 20 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /* This structure is shared between server and client - it contains
 | ||||
|  * relatively little extraneous bits when used for the client rather than the | ||||
|  | ||||
							
								
								
									
										78
									
								
								cli-auth.c
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								cli-auth.c
									
									
									
									
									
								
							| @ -32,7 +32,6 @@ | ||||
| #include "packet.h" | ||||
| #include "runopts.h" | ||||
| 
 | ||||
| 
 | ||||
| void cli_authinitialise() { | ||||
| 
 | ||||
| 	memset(&ses.authstate, 0, sizeof(ses.authstate)); | ||||
| @ -99,6 +98,40 @@ out: | ||||
| 	TRACE(("leave recv_msg_userauth_banner")) | ||||
| } | ||||
| 
 | ||||
| /* This handles the message-specific types which
 | ||||
|  * all have a value of 60. These are | ||||
|  * SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, | ||||
|  * SSH_MSG_USERAUTH_PK_OK, & | ||||
|  * SSH_MSG_USERAUTH_INFO_REQUEST. */ | ||||
| void recv_msg_userauth_specific_60() { | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_PUBKEY_AUTH | ||||
| 	if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) { | ||||
| 		recv_msg_userauth_pk_ok(); | ||||
| 		return; | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_INTERACT_AUTH | ||||
| 	if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT) { | ||||
| 		recv_msg_userauth_info_request(); | ||||
| 		return; | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_PASSWORD_AUTH | ||||
| 	if (cli_ses.lastauthtype == AUTH_TYPE_PASSWORD) { | ||||
| 		/* Eventually there could be proper password-changing
 | ||||
| 		 * support. However currently few servers seem to | ||||
| 		 * implement it, and password auth is last-resort | ||||
| 		 * regardless - keyboard-interactive is more likely | ||||
| 		 * to be used anyway. */ | ||||
| 		dropbear_close("Your password has expired."); | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| 	dropbear_exit("Unexpected userauth packet"); | ||||
| } | ||||
| 
 | ||||
| void recv_msg_userauth_failure() { | ||||
| 
 | ||||
| @ -113,8 +146,7 @@ void recv_msg_userauth_failure() { | ||||
| 
 | ||||
| 	if (cli_ses.state != USERAUTH_REQ_SENT) { | ||||
| 		/* Perhaps we should be more fatal? */ | ||||
| 		TRACE(("But we didn't send a userauth request!!!!!!")) | ||||
| 		return; | ||||
| 		dropbear_exit("Unexpected userauth failure"); | ||||
| 	} | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_PUBKEY_AUTH | ||||
| @ -125,6 +157,19 @@ void recv_msg_userauth_failure() { | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_INTERACT_AUTH | ||||
| 	/* If we get a failure message for keyboard interactive without
 | ||||
| 	 * receiving any request info packet, then we don't bother trying | ||||
| 	 * keyboard interactive again */ | ||||
| 	if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT | ||||
| 			&& !cli_ses.interact_request_received) { | ||||
| 		TRACE(("setting auth_interact_failed = 1")) | ||||
| 		cli_ses.auth_interact_failed = 1; | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| 	cli_ses.lastauthtype = AUTH_TYPE_NONE; | ||||
| 
 | ||||
| 	methods = buf_getstring(ses.payload, &methlen); | ||||
| 
 | ||||
| 	partial = buf_getbool(ses.payload); | ||||
| @ -157,6 +202,12 @@ void recv_msg_userauth_failure() { | ||||
| 				ses.authstate.authtypes |= AUTH_TYPE_PUBKEY; | ||||
| 			} | ||||
| #endif | ||||
| #ifdef ENABLE_CLI_INTERACT_AUTH | ||||
| 			if (strncmp(AUTH_METHOD_INTERACT, tok, | ||||
| 				AUTH_METHOD_INTERACT_LEN) == 0) { | ||||
| 				ses.authstate.authtypes |= AUTH_TYPE_INTERACT; | ||||
| 			} | ||||
| #endif | ||||
| #ifdef ENABLE_CLI_PASSWORD_AUTH | ||||
| 			if (strncmp(AUTH_METHOD_PASSWORD, tok, | ||||
| 				AUTH_METHOD_PASSWORD_LEN) == 0) { | ||||
| @ -180,6 +231,7 @@ void recv_msg_userauth_success() { | ||||
| 	TRACE(("received msg_userauth_success")) | ||||
| 	ses.authstate.authdone = 1; | ||||
| 	cli_ses.state = USERAUTH_SUCCESS_RCVD; | ||||
| 	cli_ses.lastauthtype = AUTH_TYPE_NONE; | ||||
| } | ||||
| 
 | ||||
| void cli_auth_try() { | ||||
| @ -189,7 +241,8 @@ void cli_auth_try() { | ||||
| 
 | ||||
| 	CHECKCLEARTOWRITE(); | ||||
| 	 | ||||
| 	/* XXX We hardcode that we try a pubkey first */ | ||||
| 	/* Order to try is pubkey, interactive, password.
 | ||||
| 	 * As soon as "finished" is set for one, we don't do any more. */ | ||||
| #ifdef ENABLE_CLI_PUBKEY_AUTH | ||||
| 	if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) { | ||||
| 		finished = cli_auth_pubkey(); | ||||
| @ -197,13 +250,28 @@ void cli_auth_try() { | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_INTERACT_AUTH | ||||
| 	if (!finished && ses.authstate.authtypes & AUTH_TYPE_INTERACT) { | ||||
| 		if (cli_ses.auth_interact_failed) { | ||||
| 			finished = 0; | ||||
| 		} else { | ||||
| 			cli_auth_interactive(); | ||||
| 			cli_ses.lastauthtype = AUTH_TYPE_INTERACT; | ||||
| 			finished = 1; | ||||
| 		} | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_PASSWORD_AUTH | ||||
| 	if (!finished && ses.authstate.authtypes & AUTH_TYPE_PASSWORD) { | ||||
| 		finished = cli_auth_password(); | ||||
| 		cli_auth_password(); | ||||
| 		finished = 1; | ||||
| 		cli_ses.lastauthtype = AUTH_TYPE_PASSWORD; | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| 	TRACE(("cli_auth_try lastauthtype %d", cli_ses.lastauthtype)) | ||||
| 
 | ||||
| 	if (!finished) { | ||||
| 		dropbear_exit("No auth methods could be used."); | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										168
									
								
								cli-authinteract.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								cli-authinteract.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,168 @@ | ||||
| /*
 | ||||
|  * Dropbear SSH | ||||
|  *  | ||||
|  * Copyright (c) 2005 Matt Johnston | ||||
|  * All rights reserved. | ||||
|  *  | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  *  | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  *  | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. */ | ||||
| 
 | ||||
| #include "includes.h" | ||||
| #include "buffer.h" | ||||
| #include "dbutil.h" | ||||
| #include "session.h" | ||||
| #include "ssh.h" | ||||
| #include "runopts.h" | ||||
| 
 | ||||
| #ifdef ENABLE_CLI_INTERACT_AUTH | ||||
| 
 | ||||
| static unsigned char* get_response(unsigned char* prompt) | ||||
| { | ||||
| 	FILE* tty = NULL; | ||||
| 	unsigned char* response = NULL; | ||||
| 	/* not a password, but a reasonable limit */ | ||||
| 	char buf[DROPBEAR_MAX_CLI_PASS]; | ||||
| 	char* ret = NULL; | ||||
| 
 | ||||
| 	fprintf(stderr, "%s", prompt); | ||||
| 
 | ||||
| 	tty = fopen(_PATH_TTY, "r"); | ||||
| 	if (tty) { | ||||
| 		ret = fgets(buf, sizeof(buf), tty); | ||||
| 		fclose(tty); | ||||
| 	} else { | ||||
| 		ret = fgets(buf, sizeof(buf), stdin); | ||||
| 	} | ||||
| 
 | ||||
| 	if (ret == NULL) { | ||||
| 		response = (unsigned char*)m_strdup(""); | ||||
| 	} else { | ||||
| 		unsigned int buflen = strlen(buf); | ||||
| 		/* fgets includes newlines */ | ||||
| 		if (buflen > 0 && buf[buflen-1] == '\n') | ||||
| 			buf[buflen-1] = '\0'; | ||||
| 		response = (unsigned char*)m_strdup(buf); | ||||
| 	} | ||||
| 
 | ||||
| 	m_burn(buf, sizeof(buf)); | ||||
| 
 | ||||
| 	return response; | ||||
| } | ||||
| 
 | ||||
| void recv_msg_userauth_info_request() { | ||||
| 
 | ||||
| 	unsigned char *name = NULL; | ||||
| 	unsigned char *instruction = NULL; | ||||
| 	unsigned int num_prompts = 0; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	unsigned char *prompt = NULL; | ||||
| 	unsigned int echo = 0; | ||||
| 	unsigned char *response = NULL; | ||||
| 
 | ||||
| 	TRACE(("enter recv_msg_recv_userauth_info_request")) | ||||
| 
 | ||||
| 	cli_ses.interact_request_received = 1; | ||||
| 
 | ||||
| 	name = buf_getstring(ses.payload, NULL); | ||||
| 	instruction = buf_getstring(ses.payload, NULL); | ||||
| 
 | ||||
| 	/* language tag */ | ||||
| 	buf_eatstring(ses.payload); | ||||
| 
 | ||||
| 	num_prompts = buf_getint(ses.payload); | ||||
| 	 | ||||
| 	if (num_prompts >= DROPBEAR_MAX_CLI_INTERACT_PROMPTS) { | ||||
| 		dropbear_exit("Too many prompts received for keyboard-interactive"); | ||||
| 	} | ||||
| 
 | ||||
| 	/* we'll build the response as we go */ | ||||
| 	CHECKCLEARTOWRITE(); | ||||
| 	buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_INFO_RESPONSE); | ||||
| 	buf_putint(ses.writepayload, num_prompts); | ||||
| 
 | ||||
| 	if (strlen(name) > 0) { | ||||
| 		cleantext(name); | ||||
| 		fprintf(stderr, "%s", name); | ||||
| 		m_free(name); | ||||
| 	} | ||||
| 	if (strlen(instruction) > 0) { | ||||
| 		cleantext(instruction); | ||||
| 		fprintf(stderr, "%s", instruction); | ||||
| 		m_free(instruction); | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i < num_prompts; i++) { | ||||
| 		unsigned int response_len = 0; | ||||
| 		prompt = buf_getstring(ses.payload, NULL); | ||||
| 		cleantext(prompt); | ||||
| 
 | ||||
| 		echo = buf_getbool(ses.payload); | ||||
| 
 | ||||
| 		if (echo) { | ||||
| 			unsigned char* p = getpass(prompt); | ||||
| 			response = m_strdup(p); | ||||
| 			m_burn(p, strlen(p)); | ||||
| 		} else { | ||||
| 			response = get_response(prompt); | ||||
| 		} | ||||
| 
 | ||||
| 		response_len = strlen(response); | ||||
| 		buf_putstring(ses.writepayload, response, response_len); | ||||
| 		m_burn(response, response_len); | ||||
| 		m_free(response); | ||||
| 	} | ||||
| 
 | ||||
| 	encrypt_packet(); | ||||
| 
 | ||||
| 
 | ||||
| 	TRACE(("leave recv_msg_recv_userauth_info_request")) | ||||
| } | ||||
| 
 | ||||
| void cli_auth_interactive() { | ||||
| 
 | ||||
| 	TRACE(("enter cli_auth_interactive")) | ||||
| 	CHECKCLEARTOWRITE(); | ||||
| 
 | ||||
| 	buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST); | ||||
| 
 | ||||
| 	/* username */ | ||||
| 	buf_putstring(ses.writepayload, cli_opts.username, | ||||
| 			strlen(cli_opts.username)); | ||||
| 
 | ||||
| 	/* service name */ | ||||
| 	buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION,  | ||||
| 			SSH_SERVICE_CONNECTION_LEN); | ||||
| 
 | ||||
| 	/* method */ | ||||
| 	buf_putstring(ses.writepayload, AUTH_METHOD_INTERACT, | ||||
| 			AUTH_METHOD_INTERACT_LEN); | ||||
| 
 | ||||
| 	/* empty language tag */ | ||||
| 	buf_putstring(ses.writepayload, "", 0); | ||||
| 
 | ||||
| 	/* empty submethods */ | ||||
| 	buf_putstring(ses.writepayload, "", 0); | ||||
| 
 | ||||
| 	encrypt_packet(); | ||||
| 	cli_ses.interact_request_received = 0; | ||||
| 
 | ||||
| 	TRACE(("leave cli_auth_interactive")) | ||||
| 
 | ||||
| } | ||||
| #endif	/* ENABLE_CLI_INTERACT_AUTH */ | ||||
| @ -113,7 +113,7 @@ static char *gui_getpass(const char *prompt) { | ||||
| } | ||||
| #endif /* ENABLE_CLI_ASKPASS_HELPER */ | ||||
| 
 | ||||
| int cli_auth_password() { | ||||
| void cli_auth_password() { | ||||
| 
 | ||||
| 	char* password = NULL; | ||||
| 
 | ||||
| @ -149,7 +149,5 @@ int cli_auth_password() { | ||||
| 	m_burn(password, strlen(password)); | ||||
| 
 | ||||
| 	TRACE(("leave cli_auth_password")) | ||||
| 	return 1; /* Password auth can always be tried */ | ||||
| 
 | ||||
| } | ||||
| #endif	/* ENABLE_CLI_PASSWORD_AUTH */ | ||||
|  | ||||
| @ -63,9 +63,7 @@ static const packettype cli_packettypes[] = { | ||||
| 	{SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation}, | ||||
| 	{SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, | ||||
| 	{SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */ | ||||
| #ifdef ENABLE_CLI_PUBKEY_AUTH | ||||
| 	{SSH_MSG_USERAUTH_PK_OK, recv_msg_userauth_pk_ok}, /* client */ | ||||
| #endif | ||||
| 	{SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */ | ||||
| 	{0, 0} /* End */ | ||||
| }; | ||||
| 
 | ||||
| @ -285,7 +283,8 @@ static void cli_remoteclosed() { | ||||
| } | ||||
| 
 | ||||
| /* Operates in-place turning dirty (untrusted potentially containing control
 | ||||
|  * characters) text into clean text. */ | ||||
|  * characters) text into clean text.  | ||||
|  * Note: this is safe only with ascii - other charsets could have problems. */ | ||||
| void cleantext(unsigned char* dirtytext) { | ||||
| 
 | ||||
| 	unsigned int i, j; | ||||
|  | ||||
| @ -133,6 +133,7 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ | ||||
| 
 | ||||
| #define ENABLE_CLI_PASSWORD_AUTH | ||||
| #define ENABLE_CLI_PUBKEY_AUTH | ||||
| #define ENABLE_CLI_INTERACT_AUTH | ||||
| 
 | ||||
| /* Define this (as well as ENABLE_CLI_PASSWORD_AUTH) to allow the use of
 | ||||
|  * a helper program for the ssh client. The helper program should be | ||||
| @ -315,6 +316,10 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ | ||||
| 
 | ||||
| #define DROPBEAR_MAX_CLI_PASS 1024 | ||||
| 
 | ||||
| #define DROPBEAR_MAX_CLI_INTERACT_PROMPTS 80 /* The number of prompts we'll  | ||||
| 												accept for keyb-interactive | ||||
| 												auth */ | ||||
| 
 | ||||
| #if defined(DROPBEAR_AES256_CBC) || defined(DROPBEAR_AES128_CBC) | ||||
| #define DROPBEAR_AES_CBC | ||||
| #endif | ||||
|  | ||||
| @ -226,6 +226,13 @@ struct clientsession { | ||||
| 
 | ||||
| 	int lastauthtype; /* either AUTH_TYPE_PUBKEY or AUTH_TYPE_PASSWORD,
 | ||||
| 						 for the last type of auth we tried */ | ||||
| #ifdef ENABLE_CLI_INTERACT_AUTH | ||||
| 	int auth_interact_failed; /* flag whether interactive auth can still
 | ||||
| 								 be used */ | ||||
| 	int interact_request_received; /* flag whether we've received an 
 | ||||
| 									  info request from the server for | ||||
| 									  interactive auth.*/ | ||||
| #endif | ||||
| 	struct SignKeyList *lastprivkey; | ||||
| 
 | ||||
| 	int retval; /* What the command exit status was - we emulate it */ | ||||
|  | ||||
							
								
								
									
										11
									
								
								ssh.h
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								ssh.h
									
									
									
									
									
								
							| @ -42,8 +42,19 @@ | ||||
| #define SSH_MSG_USERAUTH_FAILURE            51 | ||||
| #define SSH_MSG_USERAUTH_SUCCESS            52 | ||||
| #define SSH_MSG_USERAUTH_BANNER             53 | ||||
| 
 | ||||
| /* packets 60-79 are method-specific, aren't one-one mapping */ | ||||
| #define SSH_MSG_USERAUTH_SPECIFIC_60   60 | ||||
| 
 | ||||
| #define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ   60 | ||||
| 
 | ||||
| #define SSH_MSG_USERAUTH_PK_OK				60 | ||||
| 
 | ||||
| /* keyboard interactive auth */ | ||||
| #define SSH_MSG_USERAUTH_INFO_REQUEST           60 | ||||
| #define SSH_MSG_USERAUTH_INFO_RESPONSE          61 | ||||
| 
 | ||||
| 
 | ||||
| /* If adding numbers here, check MAX_UNAUTH_PACKET_TYPE in process-packet.c
 | ||||
|  * is still valid */ | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user