Commit 4119bf9f authored by Linus Torvalds's avatar Linus Torvalds

Merge tag '5.7-rc-smb3-fixes-part2' of git://git.samba.org/sfrench/cifs-2.6

Pull cifs fixes from Steve French:
 "Ten cifs/smb fixes:

   - five RDMA (smbdirect) related fixes

   - add experimental support for swap over SMB3 mounts

   - also a fix which improves performance of signed connections"

* tag '5.7-rc-smb3-fixes-part2' of git://git.samba.org/sfrench/cifs-2.6:
  smb3: enable swap on SMB3 mounts
  smb3: change noisy error message to FYI
  smb3: smbdirect support can be configured by default
  cifs: smbd: Do not schedule work to send immediate packet on every receive
  cifs: smbd: Properly process errors on ib_post_send
  cifs: Allocate crypto structures on the fly for calculating signatures of incoming packets
  cifs: smbd: Update receive credits before sending and deal with credits roll back on failure before sending
  cifs: smbd: Check send queue size before posting a send
  cifs: smbd: Merge code to track pending packets
  cifs: ignore cached share root handle closing errors
parents 50bda5fa 4e8aea30
...@@ -202,7 +202,7 @@ config CIFS_SMB_DIRECT ...@@ -202,7 +202,7 @@ config CIFS_SMB_DIRECT
help help
Enables SMB Direct support for SMB 3.0, 3.02 and 3.1.1. Enables SMB Direct support for SMB 3.0, 3.02 and 3.1.1.
SMB Direct allows transferring SMB packets over RDMA. If unsure, SMB Direct allows transferring SMB packets over RDMA. If unsure,
say N. say Y.
config CIFS_FSCACHE config CIFS_FSCACHE
bool "Provide CIFS client caching support" bool "Provide CIFS client caching support"
......
...@@ -323,10 +323,8 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) ...@@ -323,10 +323,8 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
atomic_read(&server->smbd_conn->send_credits), atomic_read(&server->smbd_conn->send_credits),
atomic_read(&server->smbd_conn->receive_credits), atomic_read(&server->smbd_conn->receive_credits),
server->smbd_conn->receive_credit_target); server->smbd_conn->receive_credit_target);
seq_printf(m, "\nPending send_pending: %x " seq_printf(m, "\nPending send_pending: %x ",
"send_payload_pending: %x", atomic_read(&server->smbd_conn->send_pending));
atomic_read(&server->smbd_conn->send_pending),
atomic_read(&server->smbd_conn->send_payload_pending));
seq_printf(m, "\nReceive buffers count_receive_queue: %x " seq_printf(m, "\nReceive buffers count_receive_queue: %x "
"count_empty_packet_queue: %x", "count_empty_packet_queue: %x",
server->smbd_conn->count_receive_queue, server->smbd_conn->count_receive_queue,
......
...@@ -1208,6 +1208,10 @@ static ssize_t cifs_copy_file_range(struct file *src_file, loff_t off, ...@@ -1208,6 +1208,10 @@ static ssize_t cifs_copy_file_range(struct file *src_file, loff_t off,
{ {
unsigned int xid = get_xid(); unsigned int xid = get_xid();
ssize_t rc; ssize_t rc;
struct cifsFileInfo *cfile = dst_file->private_data;
if (cfile->swapfile)
return -EOPNOTSUPP;
rc = cifs_file_copychunk_range(xid, src_file, off, dst_file, destoff, rc = cifs_file_copychunk_range(xid, src_file, off, dst_file, destoff,
len, flags); len, flags);
......
...@@ -426,7 +426,8 @@ struct smb_version_operations { ...@@ -426,7 +426,8 @@ struct smb_version_operations {
/* generate new lease key */ /* generate new lease key */
void (*new_lease_key)(struct cifs_fid *); void (*new_lease_key)(struct cifs_fid *);
int (*generate_signingkey)(struct cifs_ses *); int (*generate_signingkey)(struct cifs_ses *);
int (*calc_signature)(struct smb_rqst *, struct TCP_Server_Info *); int (*calc_signature)(struct smb_rqst *, struct TCP_Server_Info *,
bool allocate_crypto);
int (*set_integrity)(const unsigned int, struct cifs_tcon *tcon, int (*set_integrity)(const unsigned int, struct cifs_tcon *tcon,
struct cifsFileInfo *src_file); struct cifsFileInfo *src_file);
int (*enum_snapshots)(const unsigned int xid, struct cifs_tcon *tcon, int (*enum_snapshots)(const unsigned int xid, struct cifs_tcon *tcon,
...@@ -1312,6 +1313,7 @@ struct cifsFileInfo { ...@@ -1312,6 +1313,7 @@ struct cifsFileInfo {
struct tcon_link *tlink; struct tcon_link *tlink;
unsigned int f_flags; unsigned int f_flags;
bool invalidHandle:1; /* file closed via session abend */ bool invalidHandle:1; /* file closed via session abend */
bool swapfile:1;
bool oplock_break_cancelled:1; bool oplock_break_cancelled:1;
unsigned int oplock_epoch; /* epoch from the lease break */ unsigned int oplock_epoch; /* epoch from the lease break */
__u32 oplock_level; /* oplock/lease level from the lease break */ __u32 oplock_level; /* oplock/lease level from the lease break */
......
...@@ -4808,6 +4808,60 @@ cifs_direct_io(struct kiocb *iocb, struct iov_iter *iter) ...@@ -4808,6 +4808,60 @@ cifs_direct_io(struct kiocb *iocb, struct iov_iter *iter)
return -EINVAL; return -EINVAL;
} }
static int cifs_swap_activate(struct swap_info_struct *sis,
struct file *swap_file, sector_t *span)
{
struct cifsFileInfo *cfile = swap_file->private_data;
struct inode *inode = swap_file->f_mapping->host;
unsigned long blocks;
long long isize;
cifs_dbg(FYI, "swap activate\n");
spin_lock(&inode->i_lock);
blocks = inode->i_blocks;
isize = inode->i_size;
spin_unlock(&inode->i_lock);
if (blocks*512 < isize) {
pr_warn("swap activate: swapfile has holes\n");
return -EINVAL;
}
*span = sis->pages;
printk_once(KERN_WARNING "Swap support over SMB3 is experimental\n");
/*
* TODO: consider adding ACL (or documenting how) to prevent other
* users (on this or other systems) from reading it
*/
/* TODO: add sk_set_memalloc(inet) or similar */
if (cfile)
cfile->swapfile = true;
/*
* TODO: Since file already open, we can't open with DENY_ALL here
* but we could add call to grab a byte range lock to prevent others
* from reading or writing the file
*/
return 0;
}
static void cifs_swap_deactivate(struct file *file)
{
struct cifsFileInfo *cfile = file->private_data;
cifs_dbg(FYI, "swap deactivate\n");
/* TODO: undo sk_set_memalloc(inet) will eventually be needed */
if (cfile)
cfile->swapfile = false;
/* do we need to unpin (or unlock) the file */
}
const struct address_space_operations cifs_addr_ops = { const struct address_space_operations cifs_addr_ops = {
.readpage = cifs_readpage, .readpage = cifs_readpage,
...@@ -4821,6 +4875,13 @@ const struct address_space_operations cifs_addr_ops = { ...@@ -4821,6 +4875,13 @@ const struct address_space_operations cifs_addr_ops = {
.direct_IO = cifs_direct_io, .direct_IO = cifs_direct_io,
.invalidatepage = cifs_invalidate_page, .invalidatepage = cifs_invalidate_page,
.launder_page = cifs_launder_page, .launder_page = cifs_launder_page,
/*
* TODO: investigate and if useful we could add an cifs_migratePage
* helper (under an CONFIG_MIGRATION) in the future, and also
* investigate and add an is_dirty_writeback helper if needed
*/
.swap_activate = cifs_swap_activate,
.swap_deactivate = cifs_swap_deactivate,
}; };
/* /*
......
...@@ -2026,6 +2026,10 @@ cifs_revalidate_mapping(struct inode *inode) ...@@ -2026,6 +2026,10 @@ cifs_revalidate_mapping(struct inode *inode)
int rc; int rc;
unsigned long *flags = &CIFS_I(inode)->flags; unsigned long *flags = &CIFS_I(inode)->flags;
/* swapfiles are not supposed to be shared */
if (IS_SWAPFILE(inode))
return 0;
rc = wait_on_bit_lock_action(flags, CIFS_INO_LOCK, cifs_wait_bit_killable, rc = wait_on_bit_lock_action(flags, CIFS_INO_LOCK, cifs_wait_bit_killable,
TASK_KILLABLE); TASK_KILLABLE);
if (rc) if (rc)
......
...@@ -246,7 +246,7 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, ...@@ -246,7 +246,7 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info,
*/ */
fattr->cf_mode = le32_to_cpu(info->Mode) & ~S_IFMT; fattr->cf_mode = le32_to_cpu(info->Mode) & ~S_IFMT;
cifs_dbg(VFS, "XXX dev %d, reparse %d, mode %o", cifs_dbg(FYI, "posix fattr: dev %d, reparse %d, mode %o",
le32_to_cpu(info->DeviceId), le32_to_cpu(info->DeviceId),
le32_to_cpu(info->ReparseTag), le32_to_cpu(info->ReparseTag),
le32_to_cpu(info->Mode)); le32_to_cpu(info->Mode));
......
...@@ -766,6 +766,20 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid, ...@@ -766,6 +766,20 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid,
cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count); cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count);
spin_lock(&cifs_tcp_ses_lock); spin_lock(&cifs_tcp_ses_lock);
if (tcon->tc_count <= 0) {
struct TCP_Server_Info *server = NULL;
WARN_ONCE(tcon->tc_count < 0, "tcon refcount is negative");
spin_unlock(&cifs_tcp_ses_lock);
if (tcon->ses)
server = tcon->ses->server;
cifs_server_dbg(FYI, "tid=%u: tcon is closing, skipping async close retry of fid %llu %llu\n",
tcon->tid, persistent_fid, volatile_fid);
return 0;
}
tcon->tc_count++; tcon->tc_count++;
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
......
...@@ -55,9 +55,11 @@ extern struct cifs_ses *smb2_find_smb_ses(struct TCP_Server_Info *server, ...@@ -55,9 +55,11 @@ extern struct cifs_ses *smb2_find_smb_ses(struct TCP_Server_Info *server,
extern struct cifs_tcon *smb2_find_smb_tcon(struct TCP_Server_Info *server, extern struct cifs_tcon *smb2_find_smb_tcon(struct TCP_Server_Info *server,
__u64 ses_id, __u32 tid); __u64 ses_id, __u32 tid);
extern int smb2_calc_signature(struct smb_rqst *rqst, extern int smb2_calc_signature(struct smb_rqst *rqst,
struct TCP_Server_Info *server); struct TCP_Server_Info *server,
bool allocate_crypto);
extern int smb3_calc_signature(struct smb_rqst *rqst, extern int smb3_calc_signature(struct smb_rqst *rqst,
struct TCP_Server_Info *server); struct TCP_Server_Info *server,
bool allocate_crypto);
extern void smb2_echo_request(struct work_struct *work); extern void smb2_echo_request(struct work_struct *work);
extern __le32 smb2_get_lease_state(struct cifsInodeInfo *cinode); extern __le32 smb2_get_lease_state(struct cifsInodeInfo *cinode);
extern bool smb2_is_valid_oplock_break(char *buffer, extern bool smb2_is_valid_oplock_break(char *buffer,
......
...@@ -40,14 +40,6 @@ ...@@ -40,14 +40,6 @@
#include "smb2status.h" #include "smb2status.h"
#include "smb2glob.h" #include "smb2glob.h"
static int
smb2_crypto_shash_allocate(struct TCP_Server_Info *server)
{
return cifs_alloc_hash("hmac(sha256)",
&server->secmech.hmacsha256,
&server->secmech.sdeschmacsha256);
}
static int static int
smb3_crypto_shash_allocate(struct TCP_Server_Info *server) smb3_crypto_shash_allocate(struct TCP_Server_Info *server)
{ {
...@@ -219,7 +211,8 @@ smb2_find_smb_tcon(struct TCP_Server_Info *server, __u64 ses_id, __u32 tid) ...@@ -219,7 +211,8 @@ smb2_find_smb_tcon(struct TCP_Server_Info *server, __u64 ses_id, __u32 tid)
} }
int int
smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
bool allocate_crypto)
{ {
int rc; int rc;
unsigned char smb2_signature[SMB2_HMACSHA256_SIZE]; unsigned char smb2_signature[SMB2_HMACSHA256_SIZE];
...@@ -228,6 +221,8 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -228,6 +221,8 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[0].iov_base; struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[0].iov_base;
struct cifs_ses *ses; struct cifs_ses *ses;
struct shash_desc *shash; struct shash_desc *shash;
struct crypto_shash *hash;
struct sdesc *sdesc = NULL;
struct smb_rqst drqst; struct smb_rqst drqst;
ses = smb2_find_smb_ses(server, shdr->SessionId); ses = smb2_find_smb_ses(server, shdr->SessionId);
...@@ -239,24 +234,32 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -239,24 +234,32 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
memset(smb2_signature, 0x0, SMB2_HMACSHA256_SIZE); memset(smb2_signature, 0x0, SMB2_HMACSHA256_SIZE);
memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE); memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE);
rc = smb2_crypto_shash_allocate(server); if (allocate_crypto) {
if (rc) { rc = cifs_alloc_hash("hmac(sha256)", &hash, &sdesc);
cifs_server_dbg(VFS, "%s: sha256 alloc failed\n", __func__); if (rc) {
return rc; cifs_server_dbg(VFS,
"%s: sha256 alloc failed\n", __func__);
return rc;
}
shash = &sdesc->shash;
} else {
hash = server->secmech.hmacsha256;
shash = &server->secmech.sdeschmacsha256->shash;
} }
rc = crypto_shash_setkey(server->secmech.hmacsha256, rc = crypto_shash_setkey(hash, ses->auth_key.response,
ses->auth_key.response, SMB2_NTLMV2_SESSKEY_SIZE); SMB2_NTLMV2_SESSKEY_SIZE);
if (rc) { if (rc) {
cifs_server_dbg(VFS, "%s: Could not update with response\n", __func__); cifs_server_dbg(VFS,
return rc; "%s: Could not update with response\n",
__func__);
goto out;
} }
shash = &server->secmech.sdeschmacsha256->shash;
rc = crypto_shash_init(shash); rc = crypto_shash_init(shash);
if (rc) { if (rc) {
cifs_server_dbg(VFS, "%s: Could not init sha256", __func__); cifs_server_dbg(VFS, "%s: Could not init sha256", __func__);
return rc; goto out;
} }
/* /*
...@@ -271,9 +274,10 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -271,9 +274,10 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
rc = crypto_shash_update(shash, iov[0].iov_base, rc = crypto_shash_update(shash, iov[0].iov_base,
iov[0].iov_len); iov[0].iov_len);
if (rc) { if (rc) {
cifs_server_dbg(VFS, "%s: Could not update with payload\n", cifs_server_dbg(VFS,
__func__); "%s: Could not update with payload\n",
return rc; __func__);
goto out;
} }
drqst.rq_iov++; drqst.rq_iov++;
drqst.rq_nvec--; drqst.rq_nvec--;
...@@ -283,6 +287,9 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -283,6 +287,9 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
if (!rc) if (!rc)
memcpy(shdr->Signature, sigptr, SMB2_SIGNATURE_SIZE); memcpy(shdr->Signature, sigptr, SMB2_SIGNATURE_SIZE);
out:
if (allocate_crypto)
cifs_free_hash(&hash, &sdesc);
return rc; return rc;
} }
...@@ -504,14 +511,17 @@ generate_smb311signingkey(struct cifs_ses *ses) ...@@ -504,14 +511,17 @@ generate_smb311signingkey(struct cifs_ses *ses)
} }
int int
smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
bool allocate_crypto)
{ {
int rc; int rc;
unsigned char smb3_signature[SMB2_CMACAES_SIZE]; unsigned char smb3_signature[SMB2_CMACAES_SIZE];
unsigned char *sigptr = smb3_signature; unsigned char *sigptr = smb3_signature;
struct kvec *iov = rqst->rq_iov; struct kvec *iov = rqst->rq_iov;
struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[0].iov_base; struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[0].iov_base;
struct shash_desc *shash = &server->secmech.sdesccmacaes->shash; struct shash_desc *shash;
struct crypto_shash *hash;
struct sdesc *sdesc = NULL;
struct smb_rqst drqst; struct smb_rqst drqst;
u8 key[SMB3_SIGN_KEY_SIZE]; u8 key[SMB3_SIGN_KEY_SIZE];
...@@ -519,14 +529,24 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -519,14 +529,24 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
if (rc) if (rc)
return 0; return 0;
if (allocate_crypto) {
rc = cifs_alloc_hash("cmac(aes)", &hash, &sdesc);
if (rc)
return rc;
shash = &sdesc->shash;
} else {
hash = server->secmech.cmacaes;
shash = &server->secmech.sdesccmacaes->shash;
}
memset(smb3_signature, 0x0, SMB2_CMACAES_SIZE); memset(smb3_signature, 0x0, SMB2_CMACAES_SIZE);
memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE); memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE);
rc = crypto_shash_setkey(server->secmech.cmacaes, rc = crypto_shash_setkey(hash, key, SMB2_CMACAES_SIZE);
key, SMB2_CMACAES_SIZE);
if (rc) { if (rc) {
cifs_server_dbg(VFS, "%s: Could not set key for cmac aes\n", __func__); cifs_server_dbg(VFS, "%s: Could not set key for cmac aes\n", __func__);
return rc; goto out;
} }
/* /*
...@@ -537,7 +557,7 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -537,7 +557,7 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
rc = crypto_shash_init(shash); rc = crypto_shash_init(shash);
if (rc) { if (rc) {
cifs_server_dbg(VFS, "%s: Could not init cmac aes\n", __func__); cifs_server_dbg(VFS, "%s: Could not init cmac aes\n", __func__);
return rc; goto out;
} }
/* /*
...@@ -554,7 +574,7 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -554,7 +574,7 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
if (rc) { if (rc) {
cifs_server_dbg(VFS, "%s: Could not update with payload\n", cifs_server_dbg(VFS, "%s: Could not update with payload\n",
__func__); __func__);
return rc; goto out;
} }
drqst.rq_iov++; drqst.rq_iov++;
drqst.rq_nvec--; drqst.rq_nvec--;
...@@ -564,6 +584,9 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -564,6 +584,9 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
if (!rc) if (!rc)
memcpy(shdr->Signature, sigptr, SMB2_SIGNATURE_SIZE); memcpy(shdr->Signature, sigptr, SMB2_SIGNATURE_SIZE);
out:
if (allocate_crypto)
cifs_free_hash(&hash, &sdesc);
return rc; return rc;
} }
...@@ -593,7 +616,7 @@ smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -593,7 +616,7 @@ smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server)
return 0; return 0;
} }
rc = server->ops->calc_signature(rqst, server); rc = server->ops->calc_signature(rqst, server, false);
return rc; return rc;
} }
...@@ -631,9 +654,7 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -631,9 +654,7 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
memset(shdr->Signature, 0, SMB2_SIGNATURE_SIZE); memset(shdr->Signature, 0, SMB2_SIGNATURE_SIZE);
mutex_lock(&server->srv_mutex); rc = server->ops->calc_signature(rqst, server, true);
rc = server->ops->calc_signature(rqst, server);
mutex_unlock(&server->srv_mutex);
if (rc) if (rc)
return rc; return rc;
......
This diff is collapsed.
...@@ -114,8 +114,7 @@ struct smbd_connection { ...@@ -114,8 +114,7 @@ struct smbd_connection {
/* Activity accoutning */ /* Activity accoutning */
atomic_t send_pending; atomic_t send_pending;
wait_queue_head_t wait_send_pending; wait_queue_head_t wait_send_pending;
atomic_t send_payload_pending; wait_queue_head_t wait_post_send;
wait_queue_head_t wait_send_payload_pending;
/* Receive queue */ /* Receive queue */
struct list_head receive_queue; struct list_head receive_queue;
...@@ -154,7 +153,6 @@ struct smbd_connection { ...@@ -154,7 +153,6 @@ struct smbd_connection {
struct workqueue_struct *workqueue; struct workqueue_struct *workqueue;
struct delayed_work idle_timer_work; struct delayed_work idle_timer_work;
struct delayed_work send_immediate_work;
/* Memory pool for preallocating buffers */ /* Memory pool for preallocating buffers */
/* request pool for RDMA send */ /* request pool for RDMA send */
...@@ -234,9 +232,6 @@ struct smbd_request { ...@@ -234,9 +232,6 @@ struct smbd_request {
struct smbd_connection *info; struct smbd_connection *info;
struct ib_cqe cqe; struct ib_cqe cqe;
/* true if this request carries upper layer payload */
bool has_payload;
/* the SGE entries for this packet */ /* the SGE entries for this packet */
struct ib_sge sge[SMBDIRECT_MAX_SGE]; struct ib_sge sge[SMBDIRECT_MAX_SGE];
int num_sge; int num_sge;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment