Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
LSH
lsh
Commits
5b0aba61
Commit
5b0aba61
authored
Feb 27, 2006
by
Niels Möller
Browse files
Rewritten. Uses the functions in pty-helper.c. Handle utmp and wtmp.
Rev: src/lshd-pty-helper.c:1.1.2.2
parent
fff819a9
Changes
1
Hide whitespace changes
Inline
Side-by-side
src/lshd-pty-helper.c
View file @
5b0aba61
...
...
@@ -5,10 +5,30 @@
* socket, or socket pair(s) provided as stdin and stdout.
*/
/* lsh, an implementation of the ssh protocol
*
* Copyright (C) 2006 Niels Mller
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
...
...
@@ -18,9 +38,38 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <grp.h>
#include <utmpx.h>
#include "environ.h"
#include "pty-helper.h"
#ifndef GROUP_TTY
#define GROUP_TTY "tty"
#endif
#ifndef GROUP_SYSTEM
#define GROUP_SYSTEM "system"
#endif
/* Includes user, group and other bits, as well as the suid, sgid and
sticky bit. */
#ifndef ACCESS_MASK
#define ACCESS_MASK 07777
#endif
/* Desired tty access bits (rw--w----). Can we gain any portability by
writing S_IRUSR | S_IWUSR | S_IWGRP ? */
#ifndef ACCESS_TTY
#define ACCESS_TTY 0620
#endif
static
const
char
*
wtmp_file
;
void
static
void
die
(
const
char
*
format
,
...)
#if __GNUC___
__attribute__
((
__format__
(
__printf__
,
1
,
2
)))
...
...
@@ -28,7 +77,7 @@ die(const char *format, ...)
#endif
;
void
static
void
die
(
const
char
*
format
,
...)
{
va_list
args
;
...
...
@@ -39,146 +88,526 @@ die(const char *format, ...)
exit
(
EXIT_FAILURE
);
}
/* A request is a single type byte, and a SCM_CREDENTIALS control
message. */
struct
pty_request
static
void
werror
(
const
char
*
format
,
...)
#if __GNUC___
__attribute__
((
__format__
(
__printf__
,
1
,
2
)))
#endif
;
static
void
werror
(
const
char
*
format
,
...)
{
char
type
;
pid_t
pid
;
va_list
args
;
va_start
(
args
,
format
);
vfprintf
(
stderr
,
format
,
args
);
va_end
(
args
);
}
/* The state associated with a pty and an utmp entry. */
struct
pty_object
{
char
free
;
/* Non-zero if we have an utmp entry to clean up. */
char
active
;
/* The client's uid. */
uid_t
uid
;
gid_t
gid
;
};
/* The response includes the fd of the master side of the pty pair,
and the name of the slave tty. It is sent as two messages, one with
the fd and the length, and another with the tty name. Since the
protocol is used only locally on a single machine, we send the
length using whatever the native representation of an unsigned
is. */
/* Name of slave tty */
const
char
*
tty
;
struct
pty_response
struct
utmpx
entry
;
};
struct
pty_state
{
/* Master pty */
int
fd
;
unsigned
tty_length
;
const
char
*
tty_name
;
/* Group that should own the slave tty. */
gid_t
tty_gid
;
/* Expected user id, or -1 if unset. */
uid_t
uid
;
unsigned
nobjects
;
struct
pty_object
*
objects
;
};
static
int
recv_request
(
int
fd
,
struct
pty_request
*
request
)
static
void
init_pty_state
(
struct
pty_state
*
state
,
uid_t
uid
)
{
struct
msghdr
msg
;
struct
cmsghdr
*
cmsg
;
struct
ucred
creds
;
struct
iovec
io
;
int
res
;
/* FIXME: Not portable to assume CMSG_SPACE expands to a constant
expression. */
char
buf
[
CMSG_SPACE
(
sizeof
(
creds
))];
io
.
iov_base
=
&
request
->
type
;
io
.
iov_len
=
sizeof
(
request
->
type
);
msg
.
msg_name
=
NULL
;
msg
.
msg_namelen
=
0
;
msg
.
msg_iov
=
&
io
;
msg
.
msg_iovlen
=
1
;
msg
.
msg_control
=
buf
;
msg
.
msg_controllen
=
sizeof
(
buf
);
struct
group
*
grp
;
/* Points to static area */
grp
=
getgrnam
(
GROUP_TTY
);
if
(
!
grp
)
/* On AIX, tty:s have group "system", not "tty" */
grp
=
getgrnam
(
GROUP_SYSTEM
);
do
res
=
recvmsg
(
fd
,
&
msg
,
0
);
while
(
res
<
0
&&
errno
==
EINTR
);
state
->
tty_gid
=
grp
?
grp
->
gr_gid
:
(
gid_t
)
-
1
;
if
(
res
!=
1
)
return
0
;
state
->
uid
=
uid
;
cmsg
=
CMSG_FIRSTHDR
(
&
msg
);
state
->
nobjects
=
0
;
state
->
objects
=
NULL
;
}
if
(
cmsg
->
cmsg_level
==
SOL_SOCKET
&&
cmsg
->
cmsg_type
==
SCM_CREDENTIALS
&&
cmsg
->
cmsg_len
==
CMSG_LEN
(
sizeof
(
creds
)))
static
struct
pty_object
*
pty_object_alloc
(
struct
pty_state
*
state
,
unsigned
*
index
)
{
struct
pty_object
*
pty
=
NULL
;
unsigned
i
;
for
(
i
=
0
;
i
<
state
->
nobjects
;
i
++
)
if
(
state
->
objects
[
i
].
free
)
{
*
index
=
i
;
pty
=
state
->
objects
+
i
;
break
;
}
if
(
!
pty
)
{
/* No alignment guarantees, so use memcpy. */
memcpy
(
&
creds
,
CMSG_DATA
(
cmsg
),
sizeof
(
creds
));
request
->
pid
=
creds
.
pid
;
request
->
uid
=
creds
.
uid
;
request
->
gid
=
creds
.
gid
;
/* Try reallocating */
size_t
n
=
2
*
state
->
nobjects
+
10
;
void
*
p
=
realloc
(
state
->
objects
,
n
*
sizeof
(
*
state
->
objects
));
if
(
!
p
)
return
NULL
;
state
->
objects
=
p
;
return
1
;
*
index
=
state
->
nobjects
;
pty
=
state
->
objects
+
state
->
nobjects
;
for
(
i
=
state
->
nobjects
;
i
<
n
;
i
++
)
state
->
objects
[
i
].
free
=
1
;
state
->
nobjects
=
n
;
}
return
0
;
memset
(
pty
,
0
,
sizeof
(
*
pty
));
return
pty
;
}
static
int
send_response
(
int
fd
,
const
struct
pty_response
*
response
)
static
void
pty_object_free
(
struct
pty_state
*
state
,
unsigned
index
)
{
struct
msghdr
msg
;
struct
cmsghdr
*
cmsg
;
struct
iovec
io
;
int
res
;
char
buf
[
CMSG_SPACE
(
sizeof
(
response
->
fd
))];
io
.
iov_base
=
(
void
*
)
&
response
->
tty_length
;
io
.
iov_len
=
sizeof
(
response
->
tty_length
);
msg
.
msg_name
=
NULL
;
msg
.
msg_namelen
=
0
;
msg
.
msg_iov
=
&
io
;
msg
.
msg_iovlen
=
1
;
msg
.
msg_control
=
buf
;
msg
.
msg_controllen
=
sizeof
(
buf
);
cmsg
=
CMSG_FIRSTHDR
(
&
msg
);
cmsg
->
cmsg_level
=
SOL_SOCKET
;
cmsg
->
cmsg_type
=
SCM_RIGHTS
;
cmsg
->
cmsg_len
=
CMSG_LEN
(
sizeof
(
response
->
fd
));
assert
(
index
<
state
->
nobjects
);
assert
(
!
state
->
objects
[
index
].
free
);
free
((
void
*
)
state
->
objects
[
index
].
tty
);
memcpy
(
CMSG_DATA
(
cmsg
),
&
response
->
fd
,
sizeof
(
response
->
fd
));
state
->
objects
[
index
].
free
=
1
;
}
do
res
=
sendmsg
(
fd
,
&
msg
,
0
);
while
(
res
<
0
&&
errno
==
EINTR
);
/* Sets the permissions on the slave pty suitably for use by USER.
* This function is derived from the grantpt function in
* sysdeps/unix/grantpt.c in glibc-2.1. */
if
(
res
!=
sizeof
(
response
->
tty_length
))
return
0
;
/* Returns errno value on error */
static
int
pty_set_permissions
(
const
char
*
name
,
uid_t
uid
,
gid_t
gid
)
{
struct
stat
st
;
i
o
.
iov_base
=
(
void
*
)
response
->
tty_name
;
io
.
iov_len
=
response
->
tty_length
;
i
f
(
stat
(
name
,
&
st
)
<
0
)
return
errno
;
msg
.
msg_control
=
NULL
;
msg
.
msg_controllen
=
0
;
/* Make sure that the user owns the device. */
if
(
st
.
st_uid
==
uid
)
uid
=
-
1
;
if
(
st
.
st_gid
==
gid
)
gid
=
-
1
;
do
res
=
sendmsg
(
fd
,
&
msg
,
0
);
while
(
res
<
0
&&
errno
==
EINTR
);
return
(
res
==
response
->
tty_length
);
if
(
uid
!=
(
uid_t
)
-
1
||
gid
!=
(
gid_t
)
-
1
)
if
(
chown
(
name
,
uid
,
gid
)
<
0
)
return
errno
;
/* Make sure the permission mode is set to readable and writable
* by the owner, and writable by the group. */
if
(
(
st
.
st_mode
&
ACCESS_MASK
)
!=
ACCESS_TTY
&&
chmod
(
name
,
ACCESS_TTY
)
<
0
)
return
errno
;
/* Everything is fine */
return
0
;
}
static
int
process_request
(
const
struct
pty_request
*
request
,
struct
pty_response
*
response
)
strprefix_p
(
const
char
*
prefix
,
const
char
*
s
)
{
return
0
;
unsigned
i
;
for
(
i
=
0
;
prefix
[
i
];
i
++
)
if
(
prefix
[
i
]
!=
s
[
i
])
return
0
;
return
1
;
}
#define CP(dst, src) (strncpy((dst), (src), sizeof(dst)))
#define CPN(dst, offset, src) \
(strncpy((dst)+(offset), (src), sizeof(dst)-(offset)))
static
void
process_request
(
struct
pty_state
*
state
,
const
struct
pty_message
*
request
,
struct
pty_message
*
response
)
{
struct
pty_object
*
pty
;
response
->
header
.
type
=
0
;
response
->
header
.
ref
=
-
1
;
response
->
header
.
length
=
0
;
response
->
data
=
NULL
;
response
->
has_creds
=
0
;
response
->
fd
=
-
1
;
/* Require credentials for all requests */
if
(
!
request
->
has_creds
)
{
werror
(
"Missing credentials.
\n
"
);
response
->
header
.
type
=
EPERM
;
return
;
}
if
(
state
->
uid
!=
(
uid_t
)
-
1
&&
request
->
creds
.
uid
!=
state
->
uid
)
{
response
->
header
.
type
=
EPERM
;
return
;
}
if
(
request
->
header
.
ref
==
-
1
)
pty
=
NULL
;
else
if
(
request
->
header
.
ref
<
0
||
(
unsigned
)
request
->
header
.
ref
>=
state
->
nobjects
)
{
response
->
header
.
type
=
EINVAL
;
return
;
}
else
{
pty
=
&
state
->
objects
[
request
->
header
.
ref
];
if
(
pty
->
free
)
{
response
->
header
.
type
=
EINVAL
;
return
;
}
}
switch
(
request
->
header
.
type
)
{
case
PTY_REQUEST_CREATE
:
werror
(
"PTY_REQUEST_CREATE
\n
"
);
if
(
request
->
header
.
ref
!=
-
1
)
{
response
->
header
.
type
=
EINVAL
;
}
else
{
unsigned
index
;
pty
=
pty_object_alloc
(
state
,
&
index
);
if
(
!
pty
)
{
response
->
header
.
type
=
ENOMEM
;
}
else
{
pty
->
uid
=
request
->
creds
.
uid
;
if
(
request
->
fd
!=
-
1
)
{
/* Client can supply the master fd, so we can get
the slave tty name from there. */
char
*
tty
=
ptsname
(
request
->
fd
);
if
(
tty
)
tty
=
strdup
(
tty
);
if
(
!
tty
)
{
response
->
header
.
type
=
errno
;
pty_object_free
(
state
,
index
);
return
;
}
pty
->
tty
=
tty
;
/* FIXME: We could stat the tty and check ownership? */
}
response
->
header
.
ref
=
index
;
}
}
break
;
case
PTY_REQUEST_MASTER
:
werror
(
"PTY_REQUEST_MASTER
\n
"
);
response
->
header
.
type
=
EOPNOTSUPP
;
/* Useful only for old bsd-style tty allocation. When using
/devptmx and grantpt, it's better to let the client create
the pty, since it will get the right ownership from the
start. */
#if 0
if (request->header.ref != -1)
{
response->header.type = EINVAL;
}
else
{
unsigned index;
const char *tty;
pty = pty_object_alloc(state, &index);
if (!pty)
{
response->header.type = ENOMEM;
return;
}
response->fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (response->fd < 0)
{
pty_object_free(state, index);
response->header.type = errno;
return;
}
tty = ptsname(response->fd);
if (!tty)
{
response->header.type = errno;
fail_and_close:
pty_object_free(state, index);
close(response->fd);
response->fd = -1;
return;
}
/* Copy, since ptsname returns a statically allocated value */
pty->tty = strdup(tty);
if (!pty->tty)
{
response->header.type = errno;
goto fail_and_close;
}
pty->uid = request->creds.uid;
if (pty->uid == getuid())
{
/* Use standard grantpt call */
if (grantpt(response->fd) < 0)
{
response->header.type = errno;
goto fail_and_close;
}
}
else
{
gid_t gid = state->tty_gid;
if (gid == (gid_t) -1)
gid = request->creds.gid;
response->header.type
= pty_set_permissions(pty->tty, pty->uid, gid);
if (response->header.type)
goto fail_and_close;
}
if (unlockpt(response->fd < 0))
{
response->header.type = errno;
goto fail_and_close;
}
response->header.ref = index;
response->length = strlen(pty->tty);
response->data = pty->tty;
}
#endif
break
;
case
PTY_REQUEST_LOGIN
:
werror
(
"PTY_REQUEST_LOGIN
\n
"
);
if
(
!
pty
)
{
response
->
header
.
type
=
EINVAL
;
}
else
if
(
request
->
creds
.
uid
!=
pty
->
uid
)
{
response
->
header
.
type
=
EPERM
;
}
else
if
(
pty
->
active
)
{
response
->
header
.
type
=
EEXIST
;
}
else
{
response
->
header
.
ref
=
request
->
header
.
ref
;
memset
(
&
pty
->
entry
,
0
,
sizeof
(
pty
->
entry
));
pty
->
entry
.
ut_type
=
USER_PROCESS
;
pty
->
entry
.
ut_pid
=
request
->
creds
.
pid
;
#if 0
CP(pty->entry.ut_user, state->user_name);
#endif
gettimeofday
(
&
pty
->
entry
.
ut_tv
,
NULL
);
if
(
pty
->
tty
)
{
/* Set tty-related fields, and update utmp */
const
char
*
line
;
if
(
strprefix_p
(
"/dev/"
,
pty
->
tty
))
line
=
pty
->
tty
+
5
;
else
line
=
pty
->
tty
;
CP
(
pty
->
entry
.
ut_line
,
line
);
if
(
strprefix_p
(
"pts/"
,
line
))
{
pty
->
entry
.
ut_id
[
0
]
=
'p'
;
CPN
(
pty
->
entry
.
ut_id
,
1
,
line
+
4
);
}
else
if
(
strprefix_p
(
"tty"
,
line
))
CP
(
pty
->
entry
.
ut_id
,
line
+
3
);
else
CP
(
pty
->
entry
.
ut_id
,
line
);
setutxent
();
if
(
!
pututxline
(
&
pty
->
entry
))
werror
(
"pututxline failed.
\n
"
);
}
updwtmpx
(
wtmp_file
,
&
pty
->
entry
);
pty
->
active
=
1
;
break
;
}
case
PTY_REQUEST_LOGOUT
:
werror
(
"PTY_REQUEST_LOGOUT
\n
"
);
if
(
!
pty
)
{
response
->
header
.
type
=
EINVAL
;
}
else
{
if
(
pty
->
active
)
{
pty
->
entry
.
ut_type
=
DEAD_PROCESS
;
gettimeofday
(
&
pty
->
entry
.
ut_tv
,
NULL
);
if
(
pty
->
tty
)
{
setutxent
();
if
(
!
pututxline
(
&
pty
->
entry
))
werror
(
"pututxline failed.
\n
"
);
}
updwtmpx
(
wtmp_file
,
&
pty
->
entry
);
}
pty_object_free
(
state
,
request
->
header
.
ref
);
}
break
;
case
PTY_REQUEST_DESTROY
:
werror
(
"PTY_REQUEST_DESTROY
\n
"
);
if
(
!
pty
)
{
response
->
header
.
type
=
EINVAL
;
}
else
{
/* This request shouldn't rellay be used for "active" ptys,
i.e., if we have an utmp entry to clean up. */
pty_object_free
(
state
,
request
->
header
.
ref
);
}
break
;
default:
response
->
header
.
type
=
EINVAL
;
break
;
}
}