- (djm) Sync sftp and scp stuff from OpenBSD:
- djm@cvs.openbsd.org 2001/02/07 03:55:13 [sftp-client.c] Don't free handles before we are done with them. Based on work from Corinna Vinschen <vinschen@redhat.com>. ok markus@ - djm@cvs.openbsd.org 2001/02/06 22:32:53 [sftp.1] Punctuation fix from Pekka Savola <pekkas@netcore.fi> - deraadt@cvs.openbsd.org 2001/02/07 04:07:29 [sftp.1] pretty up significantly - itojun@cvs.openbsd.org 2001/02/07 06:49:42 [sftp.1] .Bl-.El mismatch. markus ok - djm@cvs.openbsd.org 2001/02/07 06:12:30 [sftp-int.c] Check that target is a directory before doing ls; ok markus@ - itojun@cvs.openbsd.org 2001/02/07 11:01:18 [scp.c sftp-client.c sftp-server.c] unsigned long long -> %llu, not %qu. markus ok - stevesk@cvs.openbsd.org 2001/02/07 11:10:39 [sftp.1 sftp-int.c] more man page cleanup and sync of help text with man page; ok markus@ - markus@cvs.openbsd.org 2001/02/07 14:58:34 [sftp-client.c] older servers reply with SSH2_FXP_NAME + count==0 instead of EOF - djm@cvs.openbsd.org 2001/02/07 15:27:19 [sftp.c] Don't forward agent and X11 in sftp. Suggestion from Roumen Petrov <roumen.petrov@skalasoft.com> - stevesk@cvs.openbsd.org 2001/02/07 15:36:04 [sftp-int.c] portable; ok markus@ - stevesk@cvs.openbsd.org 2001/02/07 15:55:47 [sftp-int.c] lowercase cmds[].c also; ok markus@ - markus@cvs.openbsd.org 2001/02/07 17:04:52 [pathnames.h sftp.c] allow sftp over ssh protocol 1; ok djm@ - deraadt@cvs.openbsd.org 2001/02/08 07:38:55 [scp.c] memory leak fix, and snprintf throughout - deraadt@cvs.openbsd.org 2001/02/08 08:02:02 [sftp-int.c] plug a memory leak - stevesk@cvs.openbsd.org 2001/02/08 10:11:23 [session.c sftp-client.c] %i -> %d - stevesk@cvs.openbsd.org 2001/02/08 10:57:59 [sftp-int.c] typo - stevesk@cvs.openbsd.org 2001/02/08 15:28:07 [sftp-int.c pathnames.h] _PATH_LS; ok markus@ - djm@cvs.openbsd.org 2001/02/09 04:46:25 [sftp-int.c] Check for NULL attribs for chown, chmod & chgrp operations, only send relevant attribs back to server; ok markus@ - (djm) Update makefile.in for _PATH_SFTP_SERVER
This commit is contained in:
parent
4192c46791
commit
d7686fd1fb
66
ChangeLog
66
ChangeLog
|
@ -1,3 +1,67 @@
|
|||
20010210
|
||||
- (djm) Sync sftp and scp stuff from OpenBSD:
|
||||
- djm@cvs.openbsd.org 2001/02/07 03:55:13
|
||||
[sftp-client.c]
|
||||
Don't free handles before we are done with them. Based on work from
|
||||
Corinna Vinschen <vinschen@redhat.com>. ok markus@
|
||||
- djm@cvs.openbsd.org 2001/02/06 22:32:53
|
||||
[sftp.1]
|
||||
Punctuation fix from Pekka Savola <pekkas@netcore.fi>
|
||||
- deraadt@cvs.openbsd.org 2001/02/07 04:07:29
|
||||
[sftp.1]
|
||||
pretty up significantly
|
||||
- itojun@cvs.openbsd.org 2001/02/07 06:49:42
|
||||
[sftp.1]
|
||||
.Bl-.El mismatch. markus ok
|
||||
- djm@cvs.openbsd.org 2001/02/07 06:12:30
|
||||
[sftp-int.c]
|
||||
Check that target is a directory before doing ls; ok markus@
|
||||
- itojun@cvs.openbsd.org 2001/02/07 11:01:18
|
||||
[scp.c sftp-client.c sftp-server.c]
|
||||
unsigned long long -> %llu, not %qu. markus ok
|
||||
- stevesk@cvs.openbsd.org 2001/02/07 11:10:39
|
||||
[sftp.1 sftp-int.c]
|
||||
more man page cleanup and sync of help text with man page; ok markus@
|
||||
- markus@cvs.openbsd.org 2001/02/07 14:58:34
|
||||
[sftp-client.c]
|
||||
older servers reply with SSH2_FXP_NAME + count==0 instead of EOF
|
||||
- djm@cvs.openbsd.org 2001/02/07 15:27:19
|
||||
[sftp.c]
|
||||
Don't forward agent and X11 in sftp. Suggestion from Roumen Petrov
|
||||
<roumen.petrov@skalasoft.com>
|
||||
- stevesk@cvs.openbsd.org 2001/02/07 15:36:04
|
||||
[sftp-int.c]
|
||||
portable; ok markus@
|
||||
- stevesk@cvs.openbsd.org 2001/02/07 15:55:47
|
||||
[sftp-int.c]
|
||||
lowercase cmds[].c also; ok markus@
|
||||
- markus@cvs.openbsd.org 2001/02/07 17:04:52
|
||||
[pathnames.h sftp.c]
|
||||
allow sftp over ssh protocol 1; ok djm@
|
||||
- deraadt@cvs.openbsd.org 2001/02/08 07:38:55
|
||||
[scp.c]
|
||||
memory leak fix, and snprintf throughout
|
||||
- deraadt@cvs.openbsd.org 2001/02/08 08:02:02
|
||||
[sftp-int.c]
|
||||
plug a memory leak
|
||||
- stevesk@cvs.openbsd.org 2001/02/08 10:11:23
|
||||
[session.c sftp-client.c]
|
||||
%i -> %d
|
||||
- stevesk@cvs.openbsd.org 2001/02/08 10:57:59
|
||||
[sftp-int.c]
|
||||
typo
|
||||
- stevesk@cvs.openbsd.org 2001/02/08 15:28:07
|
||||
[sftp-int.c pathnames.h]
|
||||
_PATH_LS; ok markus@
|
||||
- djm@cvs.openbsd.org 2001/02/09 04:46:25
|
||||
[sftp-int.c]
|
||||
Check for NULL attribs for chown, chmod & chgrp operations, only send
|
||||
relevant attribs back to server; ok markus@
|
||||
- (djm) Update makefile.in for _PATH_SFTP_SERVER
|
||||
|
||||
|
||||
|
||||
|
||||
20010209
|
||||
- (bal) patch to vis.c to deal with HAVE_VIS right by Robert Mooney
|
||||
<rjmooney@mediaone.net>
|
||||
|
@ -3703,4 +3767,4 @@
|
|||
- Wrote replacements for strlcpy and mkdtemp
|
||||
- Released 1.0pre1
|
||||
|
||||
$Id: ChangeLog,v 1.709 2001/02/09 11:55:16 djm Exp $
|
||||
$Id: ChangeLog,v 1.710 2001/02/09 13:40:03 djm Exp $
|
||||
|
|
13
Makefile.in
13
Makefile.in
|
@ -1,4 +1,4 @@
|
|||
# $Id: Makefile.in,v 1.149 2001/02/07 23:07:09 djm Exp $
|
||||
# $Id: Makefile.in,v 1.150 2001/02/09 13:40:03 djm Exp $
|
||||
|
||||
prefix=@prefix@
|
||||
exec_prefix=@exec_prefix@
|
||||
|
@ -16,10 +16,15 @@ DESTDIR=
|
|||
VPATH=@srcdir@
|
||||
SSH_PROGRAM=@bindir@/ssh
|
||||
ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
|
||||
SFTP_SERVER=$(libexecdir)/sftp-server
|
||||
|
||||
PATHS= -DETCDIR=\"$(sysconfdir)\" \
|
||||
-D_PATH_SSH_PROGRAM=\"$(SSH_PROGRAM)\" \
|
||||
-D_PATH_SSH_ASKPASS_DEFAULT=\"$(ASKPASS_PROGRAM)\" \
|
||||
-D_PATH_SFTP_SERVER=\"$(SFTP_SERVER)\"
|
||||
|
||||
CC=@CC@
|
||||
LD=@LD@
|
||||
PATHS=-DETCDIR=\"$(sysconfdir)\" -D_PATH_SSH_PROGRAM=\"$(SSH_PROGRAM)\" -D_PATH_SSH_ASKPASS_DEFAULT=\"$(ASKPASS_PROGRAM)\"
|
||||
CFLAGS=@CFLAGS@
|
||||
CPPFLAGS=@CPPFLAGS@ -I. -I$(srcdir)/openbsd-compat -I$(srcdir) $(PATHS) @DEFS@
|
||||
LIBS=@LIBS@
|
||||
|
@ -162,7 +167,7 @@ install-files:
|
|||
$(INSTALL) -m 0775 -s ssh-keyscan $(DESTDIR)$(bindir)/ssh-keyscan
|
||||
$(INSTALL) -m 0755 -s sshd $(DESTDIR)$(sbindir)/sshd
|
||||
@NO_SFTP@$(INSTALL) -m 0755 -s sftp $(DESTDIR)$(bindir)/sftp
|
||||
@NO_SFTP@$(INSTALL) -m 0755 -s sftp-server $(DESTDIR)$(libexecdir)/sftp-server
|
||||
@NO_SFTP@$(INSTALL) -m 0755 -s sftp-server $(DESTDIR)$(SFTP_SERVER)
|
||||
$(INSTALL) -m 644 ssh.[01].out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
|
||||
$(INSTALL) -m 644 scp.[01].out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
|
||||
$(INSTALL) -m 644 ssh-add.[01].out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
|
||||
|
@ -250,7 +255,7 @@ uninstall:
|
|||
-rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
|
||||
-rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT)
|
||||
-rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
|
||||
-rm -r $(DESTDIR)$(libexecdir)/sftp-server$(EXEEXT)
|
||||
-rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
|
||||
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
|
||||
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
|
||||
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: pathnames.h,v 1.2 2001/01/29 01:58:17 niklas Exp $ */
|
||||
/* $OpenBSD: pathnames.h,v 1.4 2001/02/08 22:28:07 stevesk Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
|
@ -116,6 +116,12 @@
|
|||
#define _PATH_CP "cp"
|
||||
#endif
|
||||
|
||||
/* for sftp */
|
||||
#ifndef _PATH_SFTP_SERVER
|
||||
#define _PATH_SFTP_SERVER "/usr/libexec/sftp-server"
|
||||
#endif
|
||||
#define _PATH_LS "ls"
|
||||
|
||||
/* path to login program */
|
||||
#ifndef LOGIN_PROGRAM
|
||||
# ifdef LOGIN_PROGRAM_FALLBACK
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
/* XXX: copy between two remote sites */
|
||||
|
||||
#include "includes.h"
|
||||
RCSID("$OpenBSD: sftp-client.c,v 1.4 2001/02/06 23:30:28 djm Exp $");
|
||||
RCSID("$OpenBSD: sftp-client.c,v 1.8 2001/02/08 17:11:23 stevesk Exp $");
|
||||
|
||||
#include "ssh.h"
|
||||
#include "buffer.h"
|
||||
|
@ -338,7 +338,9 @@ do_ls(int fd_in, int fd_out, char *path)
|
|||
SSH2_FXP_NAME, type);
|
||||
|
||||
count = buffer_get_int(&msg);
|
||||
debug3("Received %i SSH2_FXP_NAME responses", count);
|
||||
if (count == 0)
|
||||
break;
|
||||
debug3("Received %d SSH2_FXP_NAME responses", count);
|
||||
for(i = 0; i < count; i++) {
|
||||
char *filename, *longname;
|
||||
Attrib *a;
|
||||
|
@ -556,6 +558,7 @@ do_download(int fd_in, int fd_out, char *remote_path, char *local_path,
|
|||
char *handle;
|
||||
Buffer msg;
|
||||
Attrib junk, *a;
|
||||
int status;
|
||||
|
||||
a = do_stat(fd_in, fd_out, remote_path);
|
||||
if (a == NULL)
|
||||
|
@ -635,7 +638,7 @@ do_download(int fd_in, int fd_out, char *remote_path, char *local_path,
|
|||
if (id != expected_id)
|
||||
fatal("ID mismatch (%d != %d)", id, expected_id);
|
||||
if (type == SSH2_FXP_STATUS) {
|
||||
int status = buffer_get_int(&msg);
|
||||
status = buffer_get_int(&msg);
|
||||
|
||||
if (status == SSH2_FX_EOF)
|
||||
break;
|
||||
|
@ -644,10 +647,7 @@ do_download(int fd_in, int fd_out, char *remote_path, char *local_path,
|
|||
"file \"%s\" : %s", remote_path,
|
||||
fx2txt(status));
|
||||
do_close(fd_in, fd_out, handle, handle_len);
|
||||
xfree(handle);
|
||||
close(local_fd);
|
||||
buffer_free(&msg);
|
||||
return(status);
|
||||
goto done;
|
||||
}
|
||||
} else if (type != SSH2_FXP_DATA) {
|
||||
fatal("Expected SSH2_FXP_DATA(%d) packet, got %d",
|
||||
|
@ -659,27 +659,27 @@ do_download(int fd_in, int fd_out, char *remote_path, char *local_path,
|
|||
fatal("Received more data than asked for %d > %d",
|
||||
len, COPY_SIZE);
|
||||
|
||||
debug3("In read loop, got %d offset %lld", len,
|
||||
debug3("In read loop, got %d offset %llu", len,
|
||||
(unsigned long long)offset);
|
||||
if (atomicio(write, local_fd, data, len) != len) {
|
||||
error("Couldn't write to \"%s\": %s", local_path,
|
||||
strerror(errno));
|
||||
do_close(fd_in, fd_out, handle, handle_len);
|
||||
xfree(handle);
|
||||
close(local_fd);
|
||||
status = -1;
|
||||
xfree(data);
|
||||
buffer_free(&msg);
|
||||
return(-1);
|
||||
goto done;
|
||||
}
|
||||
|
||||
offset += len;
|
||||
xfree(data);
|
||||
}
|
||||
xfree(handle);
|
||||
buffer_free(&msg);
|
||||
close(local_fd);
|
||||
status = do_close(fd_in, fd_out, handle, handle_len);
|
||||
|
||||
return(do_close(fd_in, fd_out, handle, handle_len));
|
||||
done:
|
||||
close(local_fd);
|
||||
buffer_free(&msg);
|
||||
xfree(handle);
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -693,6 +693,7 @@ do_upload(int fd_in, int fd_out, char *local_path, char *remote_path,
|
|||
Buffer msg;
|
||||
struct stat sb;
|
||||
Attrib a;
|
||||
int status;
|
||||
|
||||
if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
|
||||
error("Couldn't open local file \"%s\" for reading: %s",
|
||||
|
@ -743,7 +744,6 @@ do_upload(int fd_in, int fd_out, char *local_path, char *remote_path,
|
|||
for(;;) {
|
||||
int len;
|
||||
char data[COPY_SIZE];
|
||||
u_int status;
|
||||
|
||||
/*
|
||||
* Can't use atomicio here because it returns 0 on EOF, thus losing
|
||||
|
@ -774,24 +774,29 @@ do_upload(int fd_in, int fd_out, char *local_path, char *remote_path,
|
|||
error("Couldn't write to remote file \"%s\": %s",
|
||||
remote_path, fx2txt(status));
|
||||
do_close(fd_in, fd_out, handle, handle_len);
|
||||
xfree(handle);
|
||||
close(local_fd);
|
||||
return(-1);
|
||||
goto done;
|
||||
}
|
||||
debug3("In write loop, got %d offset %llu", len,
|
||||
(unsigned long long)offset);
|
||||
|
||||
offset += len;
|
||||
}
|
||||
xfree(handle);
|
||||
buffer_free(&msg);
|
||||
|
||||
if (close(local_fd) == -1) {
|
||||
error("Couldn't close local file \"%s\": %s", local_path,
|
||||
strerror(errno));
|
||||
do_close(fd_in, fd_out, handle, handle_len);
|
||||
return(-1);
|
||||
status = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
return(do_close(fd_in, fd_out, handle, handle_len));
|
||||
status = do_close(fd_in, fd_out, handle, handle_len);
|
||||
|
||||
done:
|
||||
xfree(handle);
|
||||
buffer_free(&msg);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
*/
|
||||
|
||||
#include "includes.h"
|
||||
RCSID("$OpenBSD: sftp-common.c,v 1.1 2001/02/04 11:11:54 djm Exp $");
|
||||
RCSID("$OpenBSD: sftp-common.c,v 1.2 2001/02/06 23:50:10 markus Exp $");
|
||||
|
||||
#include "buffer.h"
|
||||
#include "bufaux.h"
|
||||
|
@ -121,13 +121,13 @@ fx2txt(int status)
|
|||
{
|
||||
switch (status) {
|
||||
case SSH2_FX_OK:
|
||||
return("No Error");
|
||||
return("No error");
|
||||
case SSH2_FX_EOF:
|
||||
return("End of File");
|
||||
return("End of file");
|
||||
case SSH2_FX_NO_SUCH_FILE:
|
||||
return("No Such File");
|
||||
return("No such file or directory");
|
||||
case SSH2_FX_PERMISSION_DENIED:
|
||||
return("Permission Denied");
|
||||
return("Permission denied");
|
||||
case SSH2_FX_FAILURE:
|
||||
return("Failure");
|
||||
case SSH2_FX_BAD_MESSAGE:
|
||||
|
|
199
sftp-int.c
199
sftp-int.c
|
@ -24,10 +24,11 @@
|
|||
|
||||
/* XXX: finish implementation of all commands */
|
||||
/* XXX: do fnmatch() instead of using raw pathname */
|
||||
/* XXX: globbed ls */
|
||||
/* XXX: recursive operations */
|
||||
|
||||
#include "includes.h"
|
||||
RCSID("$OpenBSD: sftp-int.c,v 1.7 2001/02/05 00:02:32 deraadt Exp $");
|
||||
RCSID("$OpenBSD: sftp-int.c,v 1.19 2001/02/09 11:46:24 djm Exp $");
|
||||
|
||||
#include "buffer.h"
|
||||
#include "xmalloc.h"
|
||||
|
@ -70,28 +71,29 @@ struct CMD {
|
|||
};
|
||||
|
||||
const struct CMD cmds[] = {
|
||||
{ "CD", I_CHDIR },
|
||||
{ "CHDIR", I_CHDIR },
|
||||
{ "CHGRP", I_CHGRP },
|
||||
{ "CHMOD", I_CHMOD },
|
||||
{ "CHOWN", I_CHOWN },
|
||||
{ "EXIT", I_QUIT },
|
||||
{ "GET", I_GET },
|
||||
{ "HELP", I_HELP },
|
||||
{ "LCD", I_LCHDIR },
|
||||
{ "LCHDIR", I_LCHDIR },
|
||||
{ "LLS", I_LLS },
|
||||
{ "LMKDIR", I_LMKDIR },
|
||||
{ "LPWD", I_LPWD },
|
||||
{ "LS", I_LS },
|
||||
{ "LUMASK", I_LUMASK },
|
||||
{ "MKDIR", I_MKDIR },
|
||||
{ "PUT", I_PUT },
|
||||
{ "PWD", I_PWD },
|
||||
{ "QUIT", I_QUIT },
|
||||
{ "RENAME", I_RENAME },
|
||||
{ "RM", I_RM },
|
||||
{ "RMDIR", I_RMDIR },
|
||||
{ "cd", I_CHDIR },
|
||||
{ "chdir", I_CHDIR },
|
||||
{ "chgrp", I_CHGRP },
|
||||
{ "chmod", I_CHMOD },
|
||||
{ "chown", I_CHOWN },
|
||||
{ "dir", I_LS },
|
||||
{ "exit", I_QUIT },
|
||||
{ "get", I_GET },
|
||||
{ "help", I_HELP },
|
||||
{ "lcd", I_LCHDIR },
|
||||
{ "lchdir", I_LCHDIR },
|
||||
{ "lls", I_LLS },
|
||||
{ "lmkdir", I_LMKDIR },
|
||||
{ "lpwd", I_LPWD },
|
||||
{ "ls", I_LS },
|
||||
{ "lumask", I_LUMASK },
|
||||
{ "mkdir", I_MKDIR },
|
||||
{ "put", I_PUT },
|
||||
{ "pwd", I_PWD },
|
||||
{ "quit", I_QUIT },
|
||||
{ "rename", I_RENAME },
|
||||
{ "rm", I_RM },
|
||||
{ "rmdir", I_RMDIR },
|
||||
{ "!", I_SHELL },
|
||||
{ "?", I_HELP },
|
||||
{ NULL, -1}
|
||||
|
@ -101,28 +103,29 @@ void
|
|||
help(void)
|
||||
{
|
||||
printf("Available commands:\n");
|
||||
printf("CD path Change remote directory to 'path'\n");
|
||||
printf("LCD path Change local directory to 'path'\n");
|
||||
printf("CHGRP grp path Change group of file 'path' to 'grp'\n");
|
||||
printf("CHMOD mode path Change permissions of file 'path' to 'mode'\n");
|
||||
printf("CHOWN own path Change owner of file 'path' to 'own'\n");
|
||||
printf("HELP Display this help text\n");
|
||||
printf("GET remote-path [local-path] Download file\n");
|
||||
printf("LLS [ls options] [path] Display local directory listing\n");
|
||||
printf("LMKDIR path Create local directory\n");
|
||||
printf("LPWD Print local working directory\n");
|
||||
printf("LS [path] Display remote directory listing\n");
|
||||
printf("LUMASK umask Set local umask to 'umask'\n");
|
||||
printf("MKDIR path Create remote directory\n");
|
||||
printf("PUT local-path [remote-path] Upload file\n");
|
||||
printf("PWD Display remote working directory\n");
|
||||
printf("EXIT Quit sftp\n");
|
||||
printf("QUIT Quit sftp\n");
|
||||
printf("RENAME oldpath newpath Rename remote file\n");
|
||||
printf("RMDIR path Remove remote directory\n");
|
||||
printf("RM path Delete remote file\n");
|
||||
printf("cd path Change remote directory to 'path'\n");
|
||||
printf("lcd path Change local directory to 'path'\n");
|
||||
printf("chgrp grp path Change group of file 'path' to 'grp'\n");
|
||||
printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
|
||||
printf("chown own path Change owner of file 'path' to 'own'\n");
|
||||
printf("help Display this help text\n");
|
||||
printf("get remote-path [local-path] Download file\n");
|
||||
printf("lls [ls-options [path]] Display local directory listing\n");
|
||||
printf("lmkdir path Create local directory\n");
|
||||
printf("lpwd Print local working directory\n");
|
||||
printf("ls [path] Display remote directory listing\n");
|
||||
printf("lumask umask Set local umask to 'umask'\n");
|
||||
printf("mkdir path Create remote directory\n");
|
||||
printf("put local-path [remote-path] Upload file\n");
|
||||
printf("pwd Display remote working directory\n");
|
||||
printf("exit Quit sftp\n");
|
||||
printf("quit Quit sftp\n");
|
||||
printf("rename oldpath newpath Rename remote file\n");
|
||||
printf("rmdir path Remove remote directory\n");
|
||||
printf("rm path Delete remote file\n");
|
||||
printf("!command Execute 'command' in local shell\n");
|
||||
printf("! Escape to local shell\n");
|
||||
printf("? Synonym for help\n");
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -166,13 +169,15 @@ void
|
|||
local_do_ls(const char *args)
|
||||
{
|
||||
if (!args || !*args)
|
||||
local_do_shell("ls");
|
||||
local_do_shell(_PATH_LS);
|
||||
else {
|
||||
char *buf = xmalloc(8 + strlen(args) + 1);
|
||||
int len = strlen(_PATH_LS " ") + strlen(args) + 1;
|
||||
char *buf = xmalloc(len);
|
||||
|
||||
/* XXX: quoting - rip quoting code from ftp? */
|
||||
sprintf(buf, "/bin/ls %s", args);
|
||||
snprintf(buf, len, _PATH_LS " %s", args);
|
||||
local_do_shell(buf);
|
||||
xfree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +203,7 @@ parse_getput_flags(const char **cpp, int *pflag)
|
|||
|
||||
/* Check for flags */
|
||||
if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
|
||||
switch (*cp) {
|
||||
switch (cp[1]) {
|
||||
case 'P':
|
||||
*pflag = 1;
|
||||
break;
|
||||
|
@ -216,50 +221,49 @@ parse_getput_flags(const char **cpp, int *pflag)
|
|||
int
|
||||
get_pathname(const char **cpp, char **path)
|
||||
{
|
||||
const char *quot, *cp = *cpp;
|
||||
const char *cp = *cpp, *end;
|
||||
char quot;
|
||||
int i;
|
||||
|
||||
cp += strspn(cp, WHITESPACE);
|
||||
if (!*cp) {
|
||||
*cpp = cp;
|
||||
*path = NULL;
|
||||
return(0);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Check for quoted filenames */
|
||||
if (*cp == '\"' || *cp == '\'') {
|
||||
quot = cp++;
|
||||
for(i = 0; cp[i] && cp[i] != *quot; i++)
|
||||
;
|
||||
if (!cp[i]) {
|
||||
quot = *cp++;
|
||||
|
||||
end = strchr(cp, quot);
|
||||
if (end == NULL) {
|
||||
error("Unterminated quote");
|
||||
*path = NULL;
|
||||
return(-1);
|
||||
goto fail;
|
||||
}
|
||||
if (i == 0) {
|
||||
if (cp == end) {
|
||||
error("Empty quotes");
|
||||
*path = NULL;
|
||||
return(-1);
|
||||
goto fail;
|
||||
}
|
||||
*path = xmalloc(i + 1);
|
||||
memcpy(*path, cp, i);
|
||||
(*path)[i] = '\0';
|
||||
cp += i + 1;
|
||||
*cpp = cp + strspn(cp, WHITESPACE);
|
||||
return(0);
|
||||
*cpp = end + 1 + strspn(end + 1, WHITESPACE);
|
||||
} else {
|
||||
/* Read to end of filename */
|
||||
end = strpbrk(cp, WHITESPACE);
|
||||
if (end == NULL)
|
||||
end = strchr(cp, '\0');
|
||||
*cpp = end + strspn(end, WHITESPACE);
|
||||
}
|
||||
|
||||
/* Read to end of filename */
|
||||
for(i = 0; cp[i] && cp[i] != ' '; i++)
|
||||
;
|
||||
i = end - cp;
|
||||
|
||||
*path = xmalloc(i + 1);
|
||||
memcpy(*path, cp, i);
|
||||
(*path)[i] = '\0';
|
||||
cp += i;
|
||||
*cpp = cp + strspn(cp, WHITESPACE);
|
||||
|
||||
return(0);
|
||||
|
||||
fail:
|
||||
*path = NULL;
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -270,7 +274,6 @@ infer_path(const char *p, char **ifp)
|
|||
debug("XXX: P = \"%s\"", p);
|
||||
|
||||
cp = strrchr(p, '/');
|
||||
|
||||
if (cp == NULL) {
|
||||
*ifp = xstrdup(p);
|
||||
return(0);
|
||||
|
@ -421,14 +424,13 @@ parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
|
|||
}
|
||||
|
||||
*cpp = cp;
|
||||
|
||||
return(cmdnum);
|
||||
}
|
||||
|
||||
int
|
||||
parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
|
||||
{
|
||||
char *path1, *path2;
|
||||
char *path1, *path2, *tmp;
|
||||
int pflag, cmdnum;
|
||||
unsigned long n_arg;
|
||||
Attrib a, *aa;
|
||||
|
@ -471,12 +473,44 @@ parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
|
|||
break;
|
||||
case I_CHDIR:
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
if ((tmp = do_realpath(in, out, path1)) == NULL)
|
||||
break;
|
||||
if ((aa = do_stat(in, out, tmp)) == NULL) {
|
||||
xfree(tmp);
|
||||
break;
|
||||
}
|
||||
if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
|
||||
error("Can't change directory: Can't check target");
|
||||
xfree(tmp);
|
||||
break;
|
||||
}
|
||||
if (!S_ISDIR(aa->perm)) {
|
||||
error("Can't change directory: \"%s\" is not "
|
||||
"a directory", tmp);
|
||||
xfree(tmp);
|
||||
break;
|
||||
}
|
||||
xfree(*pwd);
|
||||
*pwd = do_realpath(in, out, path1);
|
||||
*pwd = tmp;
|
||||
break;
|
||||
case I_LS:
|
||||
if (!path1) {
|
||||
do_ls(in, out, *pwd);
|
||||
break;
|
||||
}
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
do_ls(in, out, path1?path1:*pwd);
|
||||
if ((tmp = do_realpath(in, out, path1)) == NULL)
|
||||
break;
|
||||
xfree(path1);
|
||||
path1 = tmp;
|
||||
if ((aa = do_stat(in, out, path1)) == NULL)
|
||||
break;
|
||||
if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
|
||||
!S_ISDIR(aa->perm)) {
|
||||
error("Can't ls: \"%s\" is not a directory", path1);
|
||||
break;
|
||||
}
|
||||
do_ls(in, out, path1);
|
||||
break;
|
||||
case I_LCHDIR:
|
||||
if (chdir(path1) == -1)
|
||||
|
@ -485,7 +519,7 @@ parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
|
|||
break;
|
||||
case I_LMKDIR:
|
||||
if (mkdir(path1, 0777) == -1)
|
||||
error("Couldn't create local directory to "
|
||||
error("Couldn't create local directory "
|
||||
"\"%s\": %s", path1, strerror(errno));
|
||||
break;
|
||||
case I_LLS:
|
||||
|
@ -506,23 +540,27 @@ parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
|
|||
break;
|
||||
case I_CHOWN:
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
aa = do_stat(in, out, path1);
|
||||
if (!(aa = do_stat(in, out, path1)))
|
||||
break;
|
||||
if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
|
||||
error("Can't get current ownership of "
|
||||
"remote file \"%s\"", path1);
|
||||
break;
|
||||
}
|
||||
aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
|
||||
aa->uid = n_arg;
|
||||
do_setstat(in, out, path1, aa);
|
||||
break;
|
||||
case I_CHGRP:
|
||||
path1 = make_absolute(path1, *pwd);
|
||||
aa = do_stat(in, out, path1);
|
||||
if (!(aa = do_stat(in, out, path1)))
|
||||
break;
|
||||
if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
|
||||
error("Can't get current ownership of "
|
||||
"remote file \"%s\"", path1);
|
||||
break;
|
||||
}
|
||||
aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
|
||||
aa->gid = n_arg;
|
||||
do_setstat(in, out, path1, aa);
|
||||
break;
|
||||
|
@ -550,7 +588,6 @@ parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
|
|||
xfree(path1);
|
||||
if (path2)
|
||||
xfree(path2);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
@ -564,8 +601,8 @@ interactive_loop(int fd_in, int fd_out)
|
|||
if (pwd == NULL)
|
||||
fatal("Need cwd");
|
||||
|
||||
setvbuf(stdout, (char *)NULL, _IOLBF, 0);
|
||||
setvbuf(stdin, (char *)NULL, _IOLBF, 0);
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
setvbuf(stdin, NULL, _IOLBF, 0);
|
||||
|
||||
for(;;) {
|
||||
char *cp;
|
||||
|
|
121
sftp.1
121
sftp.1
|
@ -1,4 +1,4 @@
|
|||
.\" $OpenBSD: sftp.1,v 1.1 2001/02/04 11:11:54 djm Exp $
|
||||
.\" $OpenBSD: sftp.1,v 1.5 2001/02/07 18:10:39 stevesk Exp $
|
||||
.\"
|
||||
.\" Copyright (c) 2001 Damien Miller. All rights reserved.
|
||||
.\"
|
||||
|
@ -30,7 +30,7 @@
|
|||
.Nd Secure file tranfer program
|
||||
.Sh SYNOPSIS
|
||||
.Nm sftp
|
||||
.Op Fl v Li | Fl C
|
||||
.Op Fl vC
|
||||
.Op Fl o Ar ssh_option
|
||||
.Op Ar hostname | user@hostname
|
||||
.Sh DESCRIPTION
|
||||
|
@ -44,113 +44,122 @@ It may also use many features of ssh, such as public key authentication and
|
|||
compression.
|
||||
.Nm
|
||||
connects and logs into the specified
|
||||
.Ar hostname
|
||||
.Ar hostname ,
|
||||
then enters an interactive command mode.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl v
|
||||
Raise logging level. This option is also passed to ssh.
|
||||
.It Fl C
|
||||
Enables compression (via ssh's
|
||||
.Fl C
|
||||
flag)
|
||||
.It Fl v
|
||||
Raise logging level. This option is also passed to ssh.
|
||||
.It Fl o Ar ssh_option
|
||||
Specify an option to be directly passed to
|
||||
.Xr ssh 1 .
|
||||
.El
|
||||
.Sh INTERACTIVE COMMANDS
|
||||
Once in interactive mode
|
||||
.Nm ,
|
||||
Once in interactive mode,
|
||||
.Nm
|
||||
understands a set of commands similar to those of
|
||||
.Xr ftp 1 .
|
||||
Commands are case insensitive.
|
||||
.Bl -tag -width Ds
|
||||
.It Ic CD Ar path
|
||||
.It Ic cd Ar path
|
||||
Change remote directory to
|
||||
.Ar path
|
||||
.It Ic LCD Ar path
|
||||
.Ar path .
|
||||
.It Ic lcd Ar path
|
||||
Change local directory to
|
||||
.Ar path
|
||||
.It Ic CHGRP Ar grp Ar path
|
||||
.Ar path .
|
||||
.It Ic chgrp Ar grp Ar path
|
||||
Change group of file
|
||||
.Ar path to
|
||||
.Ar path
|
||||
to
|
||||
.Ar grp .
|
||||
.Ar grp
|
||||
must be numeric.
|
||||
.It Ic CHMOD Ar mode Ar path
|
||||
must be a numeric GID.
|
||||
.It Ic chmod Ar mode Ar path
|
||||
Change permissions of file
|
||||
.Ar path to
|
||||
.Ar mode
|
||||
.It Ic CHOWN Ar own Ar path
|
||||
.Ar path
|
||||
to
|
||||
.Ar mode .
|
||||
.It Ic chown Ar own Ar path
|
||||
Change owner of file
|
||||
.Ar path to
|
||||
.Ar path
|
||||
to
|
||||
.Ar own .
|
||||
.Ar own
|
||||
must be a numeric UID.
|
||||
.It Ic HELP
|
||||
Display help text
|
||||
.It Ic GET Ar remote-file Op Ar local-file
|
||||
.It Ic help
|
||||
Display help text.
|
||||
.It Ic get Ar remote-path Op Ar local-path
|
||||
Retrieve the
|
||||
.Ar remote-file
|
||||
.Ar remote-path
|
||||
and store it on the local machine.
|
||||
If the local
|
||||
file name is not specified, it is given the same name it has on the
|
||||
path name is not specified, it is given the same name it has on the
|
||||
remote machine.
|
||||
.It Ic LLS Op Ar ls-options Op Ar path
|
||||
.It Ic lls Op Ar ls-options Op Ar path
|
||||
Display local directory listing of either
|
||||
.Ar path
|
||||
or current directory if
|
||||
.Ar path
|
||||
was not specified.
|
||||
.It Ic LMKDIR Ar path
|
||||
is not specified.
|
||||
.It Ic lmkdir Ar path
|
||||
Create local directory specified by
|
||||
.Ar path
|
||||
.It Ic LPWD
|
||||
Print local working directory
|
||||
.It Ic LS Op Ar path
|
||||
.Ar path .
|
||||
.It Ic lpwd
|
||||
Print local working directory.
|
||||
.It Ic ls Op Ar path
|
||||
Display remote directory listing of either
|
||||
.Ar path
|
||||
or current directory, is
|
||||
.Ar path not specified.
|
||||
.It Ic LUMASK Ar umask
|
||||
Set local umask to
|
||||
.Ar umask
|
||||
.It Ic MKDIR Ar path
|
||||
Create remote directory specified by
|
||||
or current directory if
|
||||
.Ar path
|
||||
.It Ic PUT local-file Op Ar remote-file
|
||||
is not specified.
|
||||
.It Ic lumask Ar umask
|
||||
Set local umask to
|
||||
.Ar umask .
|
||||
.It Ic mkdir Ar path
|
||||
Create remote directory specified by
|
||||
.Ar path .
|
||||
.It Ic put Ar local-path Op Ar remote-path
|
||||
Upload
|
||||
.Ar local-file
|
||||
and store it on the remote machine. If the local file name is not specified,
|
||||
.Ar local-path
|
||||
and store it on the remote machine. If the remote path name is not specified,
|
||||
it is given the same name it has on the local machine.
|
||||
.It Ic PWD
|
||||
Display remote working directory
|
||||
.It Ic EXIT
|
||||
Quit sftp
|
||||
.It Ic QUIT
|
||||
Quit sftp
|
||||
.It Ic RENAME Ar oldpath Ar newpath
|
||||
.It Ic pwd
|
||||
Display remote working directory.
|
||||
.It Ic exit
|
||||
Quit sftp.
|
||||
.It Ic quit
|
||||
Quit sftp.
|
||||
.It Ic rename Ar oldpath Ar newpath
|
||||
Rename remote file from
|
||||
.Ar oldpath
|
||||
to
|
||||
.Ar newpath
|
||||
.It Ic RMDIR Ar path
|
||||
.Ar newpath .
|
||||
.It Ic rmdir Ar path
|
||||
Remove remote directory specified by
|
||||
.Ar path
|
||||
.It Ic RM Ar path
|
||||
.Ar path .
|
||||
.It Ic rm Ar path
|
||||
Delete remote file specified by
|
||||
.Ar path
|
||||
.Ar path .
|
||||
.It Ic ! Ar command
|
||||
Execute
|
||||
.Ar command
|
||||
in local shell
|
||||
in local shell.
|
||||
.It Ic !
|
||||
Escape to local shell
|
||||
Escape to local shell.
|
||||
.It Ic ?
|
||||
Synonym for help.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
Damien Miller <djm@mindrot.org>
|
||||
.Sh SEE ALSO
|
||||
.Xr ssh 1 ,
|
||||
.Xr ssh-add 1 ,
|
||||
.Xr ssh-keygen 1 ,
|
||||
.Xr sshd 8
|
||||
.Xr sshd 8 ,
|
||||
.Xr scp 1
|
||||
|
||||
|
|
97
sftp.c
97
sftp.c
|
@ -24,7 +24,7 @@
|
|||
|
||||
#include "includes.h"
|
||||
|
||||
RCSID("$OpenBSD: sftp.c,v 1.2 2001/02/04 15:32:25 stevesk Exp $");
|
||||
RCSID("$OpenBSD: sftp.c,v 1.7 2001/02/08 00:04:52 markus Exp $");
|
||||
|
||||
/* XXX: commandline mode */
|
||||
/* XXX: copy between two remote hosts (commandline) */
|
||||
|
@ -40,6 +40,10 @@ RCSID("$OpenBSD: sftp.c,v 1.2 2001/02/04 15:32:25 stevesk Exp $");
|
|||
#include "sftp-client.h"
|
||||
#include "sftp-int.h"
|
||||
|
||||
int use_ssh1 = 0;
|
||||
char *ssh_program = _PATH_SSH_PROGRAM;
|
||||
char *sftp_server = NULL;
|
||||
|
||||
void
|
||||
connect_to_server(char **args, int *in, int *out, pid_t *sshpid)
|
||||
{
|
||||
|
@ -72,8 +76,8 @@ connect_to_server(char **args, int *in, int *out, pid_t *sshpid)
|
|||
close(*out);
|
||||
close(c_in);
|
||||
close(c_out);
|
||||
execv(_PATH_SSH_PROGRAM, args);
|
||||
fprintf(stderr, "exec: %s", strerror(errno));
|
||||
execv(ssh_program, args);
|
||||
fprintf(stderr, "exec: %s: %s\n", ssh_program, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
@ -87,16 +91,24 @@ make_ssh_args(char *add_arg)
|
|||
static char **args = NULL;
|
||||
static int nargs = 0;
|
||||
char debug_buf[4096];
|
||||
int i;
|
||||
int i, use_subsystem = 1;
|
||||
|
||||
/* no subsystem if protocol 1 or the server-spec contains a '/' */
|
||||
if (use_ssh1 ||
|
||||
(sftp_server != NULL && strchr(sftp_server, '/') != NULL))
|
||||
use_subsystem = 0;
|
||||
|
||||
/* Init args array */
|
||||
if (args == NULL) {
|
||||
nargs = 4;
|
||||
nargs = use_subsystem ? 6 : 5;
|
||||
i = 0;
|
||||
args = xmalloc(sizeof(*args) * nargs);
|
||||
args[i++] = "ssh";
|
||||
args[i++] = "-oProtocol=2";
|
||||
args[i++] = "-s";
|
||||
args[i++] = use_ssh1 ? "-oProtocol=1" : "-oProtocol=2";
|
||||
if (use_subsystem)
|
||||
args[i++] = "-s";
|
||||
args[i++] = "-oForwardAgent=no";
|
||||
args[i++] = "-oForwardX11=no";
|
||||
args[i++] = NULL;
|
||||
}
|
||||
|
||||
|
@ -110,7 +122,10 @@ make_ssh_args(char *add_arg)
|
|||
}
|
||||
|
||||
/* Otherwise finish up and return the arg array */
|
||||
make_ssh_args("sftp");
|
||||
if (sftp_server != NULL)
|
||||
make_ssh_args(sftp_server);
|
||||
else
|
||||
make_ssh_args("sftp");
|
||||
|
||||
/* XXX: overflow - doesn't grow debug_buf */
|
||||
debug_buf[0] = '\0';
|
||||
|
@ -128,49 +143,70 @@ make_ssh_args(char *add_arg)
|
|||
void
|
||||
usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: sftp [-vC] [-osshopt=value] [user@]host\n");
|
||||
fprintf(stderr, "usage: sftp [-1vC] [-osshopt=value] [user@]host\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int in, out, i, debug_level, compress_flag;
|
||||
int in, out, ch, debug_level, compress_flag;
|
||||
pid_t sshpid;
|
||||
char *cp;
|
||||
char *host, *userhost;
|
||||
LogLevel ll;
|
||||
extern int optind;
|
||||
extern char *optarg;
|
||||
|
||||
debug_level = compress_flag = 0;
|
||||
for(i = 1; i < argc && argv[i][0] == '-'; i++) {
|
||||
if (!strcmp(argv[i], "-v"))
|
||||
debug_level = MIN(3, debug_level + 1);
|
||||
else if (!strcmp(argv[i], "-C"))
|
||||
|
||||
while ((ch = getopt(argc, argv, "1hvCo:s:S:")) != -1) {
|
||||
switch (ch) {
|
||||
case 'C':
|
||||
compress_flag = 1;
|
||||
else if (!strncmp(argv[i], "-o", 2)) {
|
||||
make_ssh_args(argv[i]);
|
||||
} else {
|
||||
fprintf(stderr, "Unknown option \"%s\"\n", argv[i]);
|
||||
break;
|
||||
case 'v':
|
||||
debug_level = MIN(3, debug_level + 1);
|
||||
break;
|
||||
case 'o':
|
||||
make_ssh_args("-o");
|
||||
make_ssh_args(optarg);
|
||||
break;
|
||||
case '1':
|
||||
use_ssh1 = 1;
|
||||
if (sftp_server == NULL)
|
||||
sftp_server = _PATH_SFTP_SERVER;
|
||||
break;
|
||||
case 's':
|
||||
sftp_server = optarg;
|
||||
break;
|
||||
case 'S':
|
||||
ssh_program = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (i == argc || argc > (i + 1))
|
||||
if (optind == argc || argc > (optind + 1))
|
||||
usage();
|
||||
|
||||
if ((cp = strchr(argv[i], '@')) == NULL)
|
||||
cp = argv[i];
|
||||
userhost = argv[optind];
|
||||
|
||||
if ((host = strchr(userhost, '@')) == NULL)
|
||||
host = userhost;
|
||||
else {
|
||||
*cp = '\0';
|
||||
if (!argv[i][0]) {
|
||||
*host = '\0';
|
||||
if (!userhost[0]) {
|
||||
fprintf(stderr, "Missing username\n");
|
||||
usage();
|
||||
}
|
||||
make_ssh_args("-l");
|
||||
make_ssh_args(argv[i]);
|
||||
cp++;
|
||||
make_ssh_args(userhost);
|
||||
host++;
|
||||
}
|
||||
|
||||
if (!*cp) {
|
||||
if (!*host) {
|
||||
fprintf(stderr, "Missing hostname\n");
|
||||
usage();
|
||||
}
|
||||
|
@ -200,9 +236,9 @@ main(int argc, char **argv)
|
|||
|
||||
log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
|
||||
|
||||
make_ssh_args(cp);
|
||||
make_ssh_args(host);
|
||||
|
||||
fprintf(stderr, "Connecting to %s...\n", cp);
|
||||
fprintf(stderr, "Connecting to %s...\n", host);
|
||||
|
||||
connect_to_server(make_ssh_args(NULL), &in, &out, &sshpid);
|
||||
|
||||
|
@ -216,7 +252,8 @@ main(int argc, char **argv)
|
|||
if (kill(sshpid, SIGHUP) == -1)
|
||||
fatal("Couldn't terminate ssh process: %s", strerror(errno));
|
||||
|
||||
/* XXX: wait? */
|
||||
if (waitpid(sshpid, NULL, 0) == -1)
|
||||
fatal("Couldn't wait for ssh process: %s", strerror(errno));
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue