Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
varnish-cache
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
varnishcache
varnish-cache
Commits
94443fed
Commit
94443fed
authored
Mar 12, 2018
by
Geoff Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add VMOD unix.
parent
63d19da4
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
532 additions
and
2 deletions
+532
-2
m00047.vtc
bin/varnishtest/tests/m00047.vtc
+130
-0
vmods.h
bin/varnishtest/vmods.h
+1
-0
configure.ac
configure.ac
+3
-0
Makefile.am
doc/sphinx/Makefile.am
+4
-0
index.rst
doc/sphinx/reference/index.rst
+1
-0
Makefile.am
lib/Makefile.am
+2
-1
Makefile.am
lib/libvmod_unix/Makefile.am
+8
-0
automake_boilerplate.am
lib/libvmod_unix/automake_boilerplate.am
+39
-0
cred_compat.h
lib/libvmod_unix/cred_compat.h
+87
-0
vmod.vcc
lib/libvmod_unix/vmod.vcc
+110
-0
vmod_unix.c
lib/libvmod_unix/vmod_unix.c
+142
-0
Makefile.am
man/Makefile.am
+5
-1
No files found.
bin/varnishtest/tests/m00047.vtc
0 → 100644
View file @
94443fed
varnishtest "VMOD unix"
# This test requires some manual verification, by checking the log,
# because support for peer credentials via UDS varies by platform, see
# below.
server s1 {
rxreq
txresp
} -start
varnish v1 -vcl+backend {
import unix;
sub vcl_backend_response {
set beresp.http.b-uid = unix.uid();
set beresp.http.b-gid = unix.gid();
set beresp.http.b-user = unix.user();
set beresp.http.b-group = unix.group();
}
sub vcl_deliver {
set resp.http.c-uid = unix.uid();
set resp.http.c-gid = unix.gid();
set resp.http.c-user = unix.user();
set resp.http.c-group = unix.group();
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.b-uid == -1
expect resp.http.b-gid == -1
expect resp.http.b-user == ""
expect resp.http.b-group == ""
expect resp.http.c-uid == -1
expect resp.http.c-gid == -1
expect resp.http.c-user == ""
expect resp.http.c-group == ""
} -run
logexpect l1 -v v1 -d 1 -b {
expect * 1002 Begin bereq
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
} -run
logexpect l1 -v v1 -d 1 -c {
expect * 1001 Begin req
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
expect * = VCL_Error {^vmod unix error: not listening on a Unix domain socket$}
} -run
varnish v1 -errvcl {vmod unix failure: may not be called in vcl_init or vcl_fini} {
import unix;
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
std.log(unix.uid());
}
}
varnish v1 -errvcl {vmod unix failure: may not be called in vcl_init or vcl_fini} {
import unix;
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
std.log(unix.gid());
}
}
varnish v1 -errvcl {vmod unix failure: may not be called in vcl_init or vcl_fini} {
import unix;
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
std.log(unix.user());
}
}
varnish v1 -errvcl {vmod unix failure: may not be called in vcl_init or vcl_fini} {
import unix;
import std;
backend b { .host = "${bad_ip}"; }
sub vcl_init {
std.log(unix.group());
}
}
varnish v1 -stop
server s1 -wait
server s1 -start
varnish v2 -arg "-a ${tmpdir}/v2.sock" -vcl+backend {
import unix;
sub vcl_backend_response {
set beresp.http.b-uid = unix.uid();
set beresp.http.b-gid = unix.gid();
set beresp.http.b-user = unix.user();
set beresp.http.b-group = unix.group();
}
sub vcl_deliver {
set resp.http.c-uid = unix.uid();
set resp.http.c-gid = unix.gid();
set resp.http.c-user = unix.user();
set resp.http.c-group = unix.group();
}
} -start
# Check the log output for the response header values to see how this
# worked on your platform.
client c2 -connect "${v2_addr}" {
txreq
rxresp
expect resp.status ~ "^(200|503)$"
} -run
bin/varnishtest/vmods.h
View file @
94443fed
...
@@ -33,3 +33,4 @@ VTC_VMOD(directors)
...
@@ -33,3 +33,4 @@ VTC_VMOD(directors)
VTC_VMOD
(
purge
)
VTC_VMOD
(
purge
)
VTC_VMOD
(
vtc
)
VTC_VMOD
(
vtc
)
VTC_VMOD
(
blob
)
VTC_VMOD
(
blob
)
VTC_VMOD
(
unix
)
configure.ac
View file @
94443fed
...
@@ -216,6 +216,8 @@ AC_CHECK_FUNCS([setppriv])
...
@@ -216,6 +216,8 @@ AC_CHECK_FUNCS([setppriv])
AC_CHECK_FUNCS([fallocate])
AC_CHECK_FUNCS([fallocate])
AC_CHECK_FUNCS([closefrom])
AC_CHECK_FUNCS([closefrom])
AC_CHECK_FUNCS([sigaltstack])
AC_CHECK_FUNCS([sigaltstack])
AC_CHECK_FUNCS([getpeereid])
AC_CHECK_FUNCS([getpeerucred])
save_LIBS="${LIBS}"
save_LIBS="${LIBS}"
LIBS="${PTHREAD_LIBS}"
LIBS="${PTHREAD_LIBS}"
...
@@ -758,6 +760,7 @@ AC_CONFIG_FILES([
...
@@ -758,6 +760,7 @@ AC_CONFIG_FILES([
lib/libvmod_purge/Makefile
lib/libvmod_purge/Makefile
lib/libvmod_vtc/Makefile
lib/libvmod_vtc/Makefile
lib/libvmod_blob/Makefile
lib/libvmod_blob/Makefile
lib/libvmod_unix/Makefile
man/Makefile
man/Makefile
varnishapi.pc
varnishapi.pc
varnishapi-uninstalled.pc
varnishapi-uninstalled.pc
...
...
doc/sphinx/Makefile.am
View file @
94443fed
...
@@ -218,6 +218,10 @@ reference/vmod_blob.generated.rst: reference $(top_builddir)/lib/libvmod_blob/vm
...
@@ -218,6 +218,10 @@ reference/vmod_blob.generated.rst: reference $(top_builddir)/lib/libvmod_blob/vm
cp
$(top_builddir)
/lib/libvmod_blob/vmod_blob.rst
$@
||
true
cp
$(top_builddir)
/lib/libvmod_blob/vmod_blob.rst
$@
||
true
BUILT_SOURCES
+=
reference/vmod_blob.generated.rst
BUILT_SOURCES
+=
reference/vmod_blob.generated.rst
reference/vmod_unix.generated.rst
:
reference $(top_builddir)/lib/libvmod_unix/vmod_unix.rst
cp
$(top_builddir)
/lib/libvmod_unix/vmod_unix.rst
$@
||
true
BUILT_SOURCES
+=
reference/vmod_unix.generated.rst
EXTRA_DIST
+=
$(BUILT_SOURCES)
EXTRA_DIST
+=
$(BUILT_SOURCES)
MAINTAINERCLEANFILES
=
$(EXTRA_DIST)
MAINTAINERCLEANFILES
=
$(EXTRA_DIST)
...
...
doc/sphinx/reference/index.rst
View file @
94443fed
...
@@ -25,6 +25,7 @@ The Varnish Reference Manual
...
@@ -25,6 +25,7 @@ The Varnish Reference Manual
vmod_vtc.generated.rst
vmod_vtc.generated.rst
vmod_purge.generated.rst
vmod_purge.generated.rst
vmod_blob.generated.rst
vmod_blob.generated.rst
vmod_unix.generated.rst
directors.rst
directors.rst
varnish-counters.rst
varnish-counters.rst
vsl.rst
vsl.rst
...
...
lib/Makefile.am
View file @
94443fed
...
@@ -10,4 +10,5 @@ SUBDIRS = \
...
@@ -10,4 +10,5 @@ SUBDIRS = \
libvmod_directors
\
libvmod_directors
\
libvmod_purge
\
libvmod_purge
\
libvmod_vtc
\
libvmod_vtc
\
libvmod_blob
libvmod_blob
\
libvmod_unix
lib/libvmod_unix/Makefile.am
0 → 100644
View file @
94443fed
#
libvmod_unix_la_SOURCES
=
\
vmod_unix.c
\
cred_compat.h
# Use vmodtool.py generated automake boilerplate
include
$(srcdir)/automake_boilerplate.am
lib/libvmod_unix/automake_boilerplate.am
0 → 100644
View file @
94443fed
# Boilerplate generated by vmodtool.py - changes will be overwritten
AM_LDFLAGS = $(AM_LT_LDFLAGS)
AM_CPPFLAGS = \
-I$(top_srcdir)/include \
-I$(top_srcdir)/bin/varnishd \
-I$(top_builddir)/include
vmoddir = $(pkglibdir)/vmods
vmodtool = $(top_srcdir)/lib/libvcc/vmodtool.py
vmodtoolargs = --strict --boilerplate
vmod_LTLIBRARIES = libvmod_unix.la
libvmod_unix_la_CFLAGS = \
@SAN_CFLAGS@
libvmod_unix_la_LDFLAGS = \
$(AM_LDFLAGS) \
$(VMOD_LDFLAGS) \
@SAN_LDFLAGS@
nodist_libvmod_unix_la_SOURCES = vcc_if.c vcc_if.h
$(libvmod_unix_la_OBJECTS): vcc_if.h
vcc_if.h vmod_unix.rst vmod_unix.man.rst: vcc_if.c
vcc_if.c: $(vmodtool) $(srcdir)/vmod.vcc
@PYTHON@ $(vmodtool) $(vmodtoolargs) $(srcdir)/vmod.vcc
EXTRA_DIST = vmod.vcc automake_boilerplate.am
CLEANFILES = $(builddir)/vcc_if.c $(builddir)/vcc_if.h \
$(builddir)/vmod_unix.rst \
$(builddir)/vmod_unix.man.rst
lib/libvmod_unix/cred_compat.h
0 → 100644
View file @
94443fed
/*-
* Copyright 2018 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
*
* Authors: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "config.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#if defined(HAVE_GETPEEREID)
#include <unistd.h>
#endif
#if defined(HAVE_GETPEERUCRED)
#include <ucred.h>
#endif
#define CREDS_FAIL -1
#define NOT_SUPPORTED -2
static
int
get_ids
(
int
fd
,
uid_t
*
uid
,
gid_t
*
gid
)
{
#if defined(SO_PEERCRED)
struct
ucred
ucred
;
socklen_t
l
=
sizeof
(
ucred
);
errno
=
0
;
if
(
getsockopt
(
fd
,
SOL_SOCKET
,
SO_PEERCRED
,
(
void
*
)
&
ucred
,
&
l
)
!=
0
)
return
(
CREDS_FAIL
);
*
uid
=
ucred
.
uid
;
*
gid
=
ucred
.
gid
;
return
(
0
);
#elif defined(HAVE_GETPEEREID)
errno
=
0
;
if
(
getpeereid
(
fd
,
uid
,
gid
)
!=
0
)
return
(
CREDS_FAIL
);
return
(
0
);
#elif defined(HAVE_GETPEERUCRED)
ucred_t
*
ucred
;
errno
=
0
;
if
(
getpeerucred
(
fd
,
&
ucred
)
!=
0
)
return
(
CREDS_FAIL
);
*
uid
=
ucred_geteuid
(
ucred
);
*
gid
=
ucred_getegid
(
ucred
);
ucred_free
(
ucred
);
return
(
0
);
#else
(
void
)
fd
;
(
void
)
uid
;
(
void
)
gid
;
return
(
NOT_SUPPORTED
);
#endif
}
lib/libvmod_unix/vmod.vcc
0 → 100644
View file @
94443fed
#-
# This document is licensed under the same conditions as Varnish itself.
# See LICENSE for details.
#
# Authors: Geoffrey Simmons <geoffrey.simmons@uplex.de>
#
$Module unix 3 utilities for Unix domain sockets
$ABI strict
DESCRIPTION
===========
This VMOD provides information about the credentials of the peer
process (user and group of the process owner) that is connected to
Varnish via a Unix domain socket, if the platform supports it.
Examples::
sub vcl_recv {
# Return "403 Forbidden" if the connected peer is
# not running as the user "trusteduser".
if (unix.user() != "trusteduser") {
return( synth(403) );
}
# Require the connected peer to run in the group
# "trustedgroup".
if (unix.group() != "trustedgroup") {
return( synth(403) );
}
# Require the connected peer to run under a specific numeric
# user id.
if (unix.uid() != 4711) {
return( synth(403) );
}
# Require the connected peer to run under a numeric group id.
if (unix.gid() != 815) {
return( synth(403) );
}
}
Obtaining the peer credentials is possible on a platform that supports
one of the following:
* ``getpeereid(3)`` (such as FreeBSD and other BSD-derived systems)
* ``getpeerucred(3)`` (SunOS and descendants)
* Reading peer credentials on such a system may require that the
Varnish child process runs with certain privileges (such as
``PRIV_PROC_INFO``)
* the socket option ``SO_PEERCRED`` for ``getsockopt(2)`` (Linux)
On most platforms, the value returned is the effective user or group
that was valid when the peer process initiated the connection.
$Function STRING user()
Return the user name of the peer process owner.
$Function STRING group()
Return the group name of the peer process owner.
$Function INT uid()
Return the numeric user id of the peer process owner.
$Function INT gid()
Return the numeric group id of the peer process owner.
ERRORS
======
All functions in this VMOD are subject to the following constraints:
* None of them may be called in ``vcl_init`` or ``vcl_fini``. If one
of them is called in ``vcl_init``, then the VCL program will fail to
load, with an error message from the VMOD.
* If called on a platform that is not supported, then VCL failure is
invoked. An error message is written to the log (with the
``VCL_Error`` tag), and for all VCL subroutines except for
``vcl_synth``, control is directed immediately to ``vcl_synth``,
with the response status set to 503 and the reason string set to
"VCL failed".
If the failure occurs during ``vcl_synth``, then ``vcl_synth`` is
aborted, and the the response line "503 VCL failed" is sent.
* If the current listener is not a Unix domain socket, or if the
attempt to read credentials fails, then a ``VCL_Error`` message is
written to the log. The STRING functions (``vmod_user`` and
``vmod_group``) return NULL, while the INT functions (``vmod_user``
and ``vmod_gid``) return -1.
SEE ALSO
========
* :ref:`varnishd(1)`
* :ref:`vcl(7)`
* ``getpeereid(3)``
* ``getpeerucred(3)``
* ``getsockopt(2)``
lib/libvmod_unix/vmod_unix.c
0 → 100644
View file @
94443fed
/*-
* Copyright 2018 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
*
* Authors: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "cred_compat.h"
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include "cache/cache.h"
#include "vcl.h"
#include "common/heritage.h"
#include "vcc_if.h"
#define FAIL(ctx, msg) \
VRT_fail((ctx), "vmod unix failure: " msg)
#define ERR(ctx, msg) \
VSLb((ctx)->vsl, SLT_VCL_Error, "vmod unix error: " msg)
#define VERR(ctx, fmt, ...) \
VSLb((ctx)->vsl, SLT_VCL_Error, "vmod unix error: " fmt, __VA_ARGS__)
#define FAILNOINIT(ctx) \
FAIL((ctx), "may not be called in vcl_init or vcl_fini")
#define ERRNOTUDS(ctx) \
ERR((ctx), "not listening on a Unix domain socket")
#define FAIL_SUPPORT(ctx) \
FAIL((ctx), "not supported on this platform")
#define ERRNOCREDS(ctx) \
VERR((ctx), "could not read peer credentials: %s", strerror(errno))
#define ERRNOMEM(ctx) \
ERR((ctx), "out of space")
static
struct
sess
*
get_sp
(
VRT_CTX
)
{
struct
sess
*
sp
;
if
(
VALID_OBJ
(
ctx
->
req
,
REQ_MAGIC
))
sp
=
ctx
->
req
->
sp
;
else
{
CHECK_OBJ_NOTNULL
(
ctx
->
bo
,
BUSYOBJ_MAGIC
);
sp
=
ctx
->
bo
->
sp
;
}
CHECK_OBJ_NOTNULL
(
sp
,
SESS_MAGIC
);
CHECK_OBJ_NOTNULL
(
sp
->
listen_sock
,
LISTEN_SOCK_MAGIC
);
return
(
sp
);
}
#define NUM_FUNC(func) \
VCL_INT \
vmod_##func(VRT_CTX) \
{ \
struct sess *sp; \
uid_t uid; \
gid_t gid; \
int ret; \
\
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); \
if ((ctx->method & VCL_MET_TASK_H) != 0) { \
FAILNOINIT(ctx); \
return (-1); \
} \
\
sp = get_sp(ctx); \
if (! sp->listen_sock->uds) { \
ERRNOTUDS(ctx); \
return (-1); \
} \
\
ret = get_ids(sp->fd, &uid, &gid); \
if (ret == 0) \
return (func); \
\
if (ret == NOT_SUPPORTED) \
FAIL_SUPPORT(ctx); \
else if (ret == CREDS_FAIL) \
ERRNOCREDS(ctx); \
return (-1); \
}
NUM_FUNC
(
uid
)
NUM_FUNC
(
gid
)
#define NAME_FUNC(func, type, get, id, fld) \
VCL_STRING \
vmod_##func(VRT_CTX) \
{ \
struct type *s; \
id##_t i; \
VCL_STRING name; \
\
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); \
i = (id##_t) vmod_##id(ctx); \
if (i == -1) \
return (NULL); \
\
errno = 0; \
s = get(i); \
if (s == NULL) { \
ERRNOCREDS(ctx); \
return (NULL); \
} \
if ((name = WS_Copy(ctx->ws, s->fld, -1)) == NULL) { \
ERRNOMEM(ctx); \
return (NULL); \
} \
return (name); \
}
NAME_FUNC
(
user
,
passwd
,
getpwuid
,
uid
,
pw_name
)
NAME_FUNC
(
group
,
group
,
getgrgid
,
gid
,
gr_name
)
man/Makefile.am
View file @
94443fed
...
@@ -19,7 +19,8 @@ dist_man_MANS = \
...
@@ -19,7 +19,8 @@ dist_man_MANS = \
vmod_purge.3
\
vmod_purge.3
\
vmod_std.3
\
vmod_std.3
\
vmod_vtc.3
\
vmod_vtc.3
\
vmod_blob.3
vmod_blob.3
\
vmod_unix.3
CLEANFILES
=
$(dist_man_MANS)
CLEANFILES
=
$(dist_man_MANS)
...
@@ -101,4 +102,7 @@ vmod_vtc.3: $(top_builddir)/lib/libvmod_vtc/vmod_vtc.man.rst
...
@@ -101,4 +102,7 @@ vmod_vtc.3: $(top_builddir)/lib/libvmod_vtc/vmod_vtc.man.rst
vmod_blob.3
:
$(top_builddir)/lib/libvmod_blob/vmod_blob.man.rst
vmod_blob.3
:
$(top_builddir)/lib/libvmod_blob/vmod_blob.man.rst
${
RST2MAN
}
$(RST2ANY_FLAGS)
$?
$@
${
RST2MAN
}
$(RST2ANY_FLAGS)
$?
$@
vmod_unix.3
:
$(top_builddir)/lib/libvmod_unix/vmod_unix.man.rst
${
RST2MAN
}
$(RST2ANY_FLAGS)
$?
$@
.NOPATH
:
$(dist_man_MANS)
.NOPATH
:
$(dist_man_MANS)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment