stuff
This commit is contained in:
parent
d7d427e902
commit
8922e82aae
12 changed files with 106 additions and 30 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/01 15:50:56 by jhalford #+# #+# */
|
/* Created: 2017/11/01 15:50:56 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:39:39 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 19:04:13 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -65,6 +65,7 @@ int ftp_msg(t_ftp *ftp, char **msg);
|
||||||
int ftp_code(t_ftp *ftp);
|
int ftp_code(t_ftp *ftp);
|
||||||
|
|
||||||
int ftp_send(int sock, char *msg, ...);
|
int ftp_send(int sock, char *msg, ...);
|
||||||
|
int ftp_sendraw(int sock, char *file, off_t size);
|
||||||
int ftp_recv(int sock, char **msg);
|
int ftp_recv(int sock, char **msg);
|
||||||
int ftp_recvraw(int sock, char **msg);
|
int ftp_recvraw(int sock, char **msg);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/01 15:56:59 by jhalford #+# #+# */
|
/* Created: 2017/11/01 15:56:59 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:40:29 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 19:02:49 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -21,8 +21,6 @@
|
||||||
|
|
||||||
# include <arpa/inet.h>
|
# include <arpa/inet.h>
|
||||||
|
|
||||||
# define REPOPATH "data/"
|
|
||||||
|
|
||||||
# define FTP_SERVER_USAGE "%s <port>"
|
# define FTP_SERVER_USAGE "%s <port>"
|
||||||
# define FTP_RET(ftp, ...) ftp_send((ftp)->cmd_sock, ##__VA_ARGS__)
|
# define FTP_RET(ftp, ...) ftp_send((ftp)->cmd_sock, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <marvin@42.fr> +#+ +:+ +#+ */
|
/* By: jhalford <marvin@42.fr> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2016/11/03 14:56:28 by jhalford #+# #+# */
|
/* Created: 2016/11/03 14:56:28 by jhalford #+# #+# */
|
||||||
/* Updated: 2016/11/03 15:35:42 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 18:12:56 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/10/07 17:59:28 by jhalford #+# #+# */
|
/* Created: 2017/10/07 17:59:28 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:15:13 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 17:57:47 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -35,8 +35,10 @@ int create_tcpclient(char *host, char *port)
|
||||||
{
|
{
|
||||||
struct addrinfo *ai;
|
struct addrinfo *ai;
|
||||||
int sock;
|
int sock;
|
||||||
|
struct addrinfo *first;
|
||||||
|
|
||||||
ai = resolve_host(host, port);
|
first = resolve_host(host, port);
|
||||||
|
ai = first;
|
||||||
while (ai)
|
while (ai)
|
||||||
{
|
{
|
||||||
if ((sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol))
|
if ((sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol))
|
||||||
|
|
@ -48,7 +50,7 @@ int create_tcpclient(char *host, char *port)
|
||||||
sock = -1;
|
sock = -1;
|
||||||
ai = ai->ai_next;
|
ai = ai->ai_next;
|
||||||
}
|
}
|
||||||
freeaddrinfo(ai);
|
freeaddrinfo(first);
|
||||||
return (sock);
|
return (sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/10/07 18:02:51 by jhalford #+# #+# */
|
/* Created: 2017/10/07 18:02:51 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 11:39:18 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 18:26:33 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -26,6 +26,12 @@ int create_server(int port, int backlog, char *protoname)
|
||||||
sin.sin6_addr = in6addr_any;
|
sin.sin6_addr = in6addr_any;
|
||||||
if (bind(sock, (const struct sockaddr *)&sin, sizeof(sin)) < 0)
|
if (bind(sock, (const struct sockaddr *)&sin, sizeof(sin)) < 0)
|
||||||
return (-1);
|
return (-1);
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 },
|
||||||
|
sizeof(int)) < 0)
|
||||||
|
return (-1);
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &(int){ 1 },
|
||||||
|
sizeof(int)) < 0)
|
||||||
|
return (-1);
|
||||||
listen(sock, backlog);
|
listen(sock, backlog);
|
||||||
return (sock);
|
return (sock);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,23 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/10 16:20:14 by jhalford #+# #+# */
|
/* Created: 2017/11/10 16:20:14 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 15:01:06 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 19:19:47 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
#include "ftp_client.h"
|
#include "ftp_client.h"
|
||||||
|
|
||||||
|
static char *fname(char *path)
|
||||||
|
{
|
||||||
|
char *bs;
|
||||||
|
|
||||||
|
bs = ft_strrchr(path, '/');
|
||||||
|
if (bs)
|
||||||
|
return (bs + 1);
|
||||||
|
else
|
||||||
|
return (path);
|
||||||
|
}
|
||||||
|
|
||||||
int cli_put(t_ftp *ftp, char **av)
|
int cli_put(t_ftp *ftp, char **av)
|
||||||
{
|
{
|
||||||
struct stat buf;
|
struct stat buf;
|
||||||
|
|
@ -25,17 +36,20 @@ int cli_put(t_ftp *ftp, char **av)
|
||||||
console_msg(0, "no such file or directory");
|
console_msg(0, "no such file or directory");
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
if (!buf.st_size || (file = mmap(NULL, buf.st_size, PROT_READ | PROT_WRITE,
|
if (!buf.st_size || (file = mmap(NULL, buf.st_size, PROT_READ,
|
||||||
MAP_PRIVATE, fd, 0)) == MAP_FAILED)
|
MAP_PRIVATE, fd, 0)) == MAP_FAILED)
|
||||||
return (close(fd));
|
return (close(fd));
|
||||||
close(fd);
|
close(fd);
|
||||||
if (dconn_init(ftp) < 0)
|
if (dconn_init(ftp) < 0)
|
||||||
return (munmap(file, buf.st_size));
|
return (munmap(file, buf.st_size));
|
||||||
FTP_CMD(ftp, "STOR %s", av[1]);
|
FTP_CMD(ftp, "STOR %s", fname(av[1]));
|
||||||
if (dconn_open(ftp) < 0)
|
if (dconn_open(ftp) < 0)
|
||||||
return (munmap(file, buf.st_size));
|
return (munmap(file, buf.st_size));
|
||||||
|
console_msg(0, "Upload in progess, please wait... %i", buf.st_size);
|
||||||
|
/* ftp_sendraw(ftp->d_sock, file, buf.st_size); */
|
||||||
send(ftp->d_sock, file, buf.st_size, 0);
|
send(ftp->d_sock, file, buf.st_size, 0);
|
||||||
close(ftp->d_sock);
|
close(ftp->d_sock);
|
||||||
|
ftp->d_sock = 0;
|
||||||
dconn_close(ftp);
|
dconn_close(ftp);
|
||||||
munmap(file, buf.st_size);
|
munmap(file, buf.st_size);
|
||||||
return (0);
|
return (0);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/08 19:52:07 by jhalford #+# #+# */
|
/* Created: 2017/11/08 19:52:07 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:56:15 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 19:11:18 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
** stream mode with file structure --> raw data no EOF
|
** stream mode with file structure --> raw data no EOF
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define M (1024 * 1024)
|
||||||
|
|
||||||
int ftp_recvraw(int sock, char **msg)
|
int ftp_recvraw(int sock, char **msg)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
@ -61,6 +63,32 @@ int ftp_recv(int sock, char **msg)
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ftp_sendraw(int sock, char *file, off_t size)
|
||||||
|
{
|
||||||
|
off_t sent;
|
||||||
|
off_t chunk;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
sent = 0;
|
||||||
|
chunk = M / 512;
|
||||||
|
DG("size=%zu", size);
|
||||||
|
while (sent < size)
|
||||||
|
{
|
||||||
|
if (size - sent < chunk)
|
||||||
|
chunk = size - sent;
|
||||||
|
DG("sent=%zu", sent);
|
||||||
|
ret = send(sock, file, chunk, 0);
|
||||||
|
DG("ret=%i", ret);
|
||||||
|
console_msg(2, "---> sendraw error");
|
||||||
|
file += chunk;
|
||||||
|
sent += chunk;
|
||||||
|
}
|
||||||
|
DG("sent=%zu", sent);
|
||||||
|
console_msg(1, "---> rawsend done size %zu", size);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int ftp_send(int sock, char *msg, ...)
|
int ftp_send(int sock, char *msg, ...)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/08 14:55:15 by jhalford #+# #+# */
|
/* Created: 2017/11/08 14:55:15 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:58:42 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 18:48:44 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -69,13 +69,18 @@ int dconn_close(t_ftp *ftp)
|
||||||
{
|
{
|
||||||
int code;
|
int code;
|
||||||
|
|
||||||
|
DG("check");
|
||||||
if ((code = ftp_code(ftp)) < 0)
|
if ((code = ftp_code(ftp)) < 0)
|
||||||
return (-1);
|
return (-1);
|
||||||
|
DG("check");
|
||||||
if (code == 226)
|
if (code == 226)
|
||||||
|
{
|
||||||
|
if (ftp->d_sock != 0)
|
||||||
{
|
{
|
||||||
close(ftp->d_sock);
|
close(ftp->d_sock);
|
||||||
ftp->data_state = DATA_NONE;
|
|
||||||
ftp->d_sock = 0;
|
ftp->d_sock = 0;
|
||||||
|
}
|
||||||
|
ftp->data_state = DATA_NONE;
|
||||||
console_msg(1, "dataconn closed");
|
console_msg(1, "dataconn closed");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/02 16:01:54 by jhalford #+# #+# */
|
/* Created: 2017/11/02 16:01:54 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:37:45 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 18:35:55 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -21,7 +21,10 @@ int cmd_stor(t_ftp *ftp, char **av)
|
||||||
if (dconn_open(ftp) < 0)
|
if (dconn_open(ftp) < 0)
|
||||||
return (-1);
|
return (-1);
|
||||||
if ((fd = open(av[1], O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0)
|
if ((fd = open(av[1], O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0)
|
||||||
|
{
|
||||||
|
dconn_close(ftp);
|
||||||
return (FTP_RET(ftp, "550 couldn't open/create file"));
|
return (FTP_RET(ftp, "550 couldn't open/create file"));
|
||||||
|
}
|
||||||
size = ftp_recvraw(ftp->d_sock, &msg);
|
size = ftp_recvraw(ftp->d_sock, &msg);
|
||||||
write(fd, msg, size);
|
write(fd, msg, size);
|
||||||
ft_strdel(&msg);
|
ft_strdel(&msg);
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,26 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/02 14:20:46 by jhalford #+# #+# */
|
/* Created: 2017/11/02 14:20:46 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:37:45 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 18:28:35 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
#include "ftp_server.h"
|
#include "ftp_server.h"
|
||||||
|
|
||||||
|
static int strisalnum(char *str)
|
||||||
|
{
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
s = str;
|
||||||
|
while (*s)
|
||||||
|
{
|
||||||
|
if (!ft_isalnum(*s))
|
||||||
|
return (0);
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
int cmd_user(t_ftp *ftp, char **av)
|
int cmd_user(t_ftp *ftp, char **av)
|
||||||
{
|
{
|
||||||
if (ftp->log_state == LOG_YES)
|
if (ftp->log_state == LOG_YES)
|
||||||
|
|
@ -19,15 +33,18 @@ int cmd_user(t_ftp *ftp, char **av)
|
||||||
FTP_RET(ftp, "230 user '%s' logged in, proceed", ftp->username);
|
FTP_RET(ftp, "230 user '%s' logged in, proceed", ftp->username);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
if (!strisalnum(av[1]))
|
||||||
|
return (FTP_RET(ftp, "530 only ASCII in username"));
|
||||||
ft_strcpy(ftp->username, av[1]);
|
ft_strcpy(ftp->username, av[1]);
|
||||||
ft_strcpy(ftp->path, REPOPATH);
|
getcwd(ftp->path, 1024);
|
||||||
|
ft_strcat(ftp->path, "/");
|
||||||
ft_strcat(ftp->path, av[1]);
|
ft_strcat(ftp->path, av[1]);
|
||||||
if (mkdir(ftp->path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0
|
console_msg(2, "userpath=%s", ftp->path);
|
||||||
&& chdir(ftp->path) < 0)
|
if ((mkdir(ftp->path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0
|
||||||
return (FTP_RET(ftp, "530 mkdir/chdir"));
|
&& errno != EEXIST)
|
||||||
if (getcwd(ftp->path, 100) < 0)
|
|| chdir(ftp->path) < 0)
|
||||||
return (FTP_RET(ftp, "530 getcwd"));
|
return (FTP_RET(ftp, "530 mkdir/chdir error"));
|
||||||
FTP_RET(ftp, "230 user '%s' logged in, proceed", ftp->username);
|
FTP_RET(ftp, "230 user logged in, proceed", ftp->username);
|
||||||
console_msg(1, "logon: %s@ftp://%s", ftp->username, ftp->path);
|
console_msg(1, "logon: %s@ftp://%s", ftp->username, ftp->path);
|
||||||
ftp->log_state = LOG_YES;
|
ftp->log_state = LOG_YES;
|
||||||
return (0);
|
return (0);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/11/08 19:52:07 by jhalford #+# #+# */
|
/* Created: 2017/11/08 19:52:07 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 15:13:42 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 19:02:56 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
@ -16,16 +16,18 @@
|
||||||
** stream mode with file structure --> raw data no EOF
|
** stream mode with file structure --> raw data no EOF
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define M (1024 * 1024)
|
||||||
|
|
||||||
int ftp_recvraw(int sock, char **msg)
|
int ftp_recvraw(int sock, char **msg)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
char buf[1024];
|
char buf[10 * M];
|
||||||
void *tmp;
|
void *tmp;
|
||||||
int size;
|
int size;
|
||||||
|
|
||||||
tmp = NULL;
|
|
||||||
size = 0;
|
size = 0;
|
||||||
while ((ret = recv(sock, buf, 1024, 0)) > 0)
|
tmp = NULL;
|
||||||
|
while ((ret = recv(sock, buf, 10 * M, 0)) > 0)
|
||||||
{
|
{
|
||||||
buf[ret] = 0;
|
buf[ret] = 0;
|
||||||
*msg = ft_strnew(size + ret);
|
*msg = ft_strnew(size + ret);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
/* By: jhalford <jack@crans.org> +#+ +:+ +#+ */
|
||||||
/* +#+#+#+#+#+ +#+ */
|
/* +#+#+#+#+#+ +#+ */
|
||||||
/* Created: 2017/04/02 15:02:48 by jhalford #+# #+# */
|
/* Created: 2017/04/02 15:02:48 by jhalford #+# #+# */
|
||||||
/* Updated: 2017/11/12 14:44:33 by jhalford ### ########.fr */
|
/* Updated: 2017/11/12 17:59:25 by jhalford ### ########.fr */
|
||||||
/* */
|
/* */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue