Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/kernel-core/vms_devtab.c
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,132 @@ long vms_ioctl_term_resolve(struct vms_proc *proc, unsigned long arg)
return 0;
}

/*
* vms_ioctl_term_setlogin - stamp a network daemon's PRE-AUTHENTICATED user
* name onto an RTAn: (rd vms-65b). The conveyance channel for the SSH ->
* $CREPRC(LOGINOUT) handoff: the daemon has already authenticated the user in
* its own protocol against the SAME SYSUAF authority, and vouches that here so
* LOGINOUT does not re-challenge on a session it created for that terminal.
*
* PRIVILEGED: only a caller holding CAP_SYS_ADMIN/SETPRV (a not-yet-dropped
* network daemon -- the same authority that establishes a run-as identity) may
* vouch, exactly like vms_ioctl_establish_system. The note is a NAME, never a
* credential, and only a dynamic terminal (an RTAn:, never OPA0:) may carry
* one. LOGINOUT still builds the persona from the binary SYSUAF record and
* grants nothing beyond it.
*/
long vms_ioctl_term_setlogin(struct vms_proc *proc, unsigned long arg)
{
struct vms_termlogin_args args;
struct vms_device *dev;
char devnam[VMS_DEVNAM_SIZE];
uint32_t status;

(void)proc;

memset(&args, 0, sizeof(args));
if (exec_copyin(&args, (const void *)arg, sizeof(args)))
return -EFAULT;
args.devnam[VMS_DEVNAM_SIZE - 1] = '\0';
args.username[VMS_USERNAME_SIZE - 1] = '\0';

if (!exec_current_is_privileged()) {
args.status = SS__NOPRIV;
goto out;
}

status = normalize_devnam(args.devnam, devnam, sizeof(devnam));
if (status != SS__NORMAL) {
args.status = status;
goto out;
}

exec_lock(&vms_device_list_lock);
dev = devtab_lookup_locked(devnam);
if (!dev) {
exec_unlock(&vms_device_list_lock);
args.status = SS__NOSUCHDEV;
goto out;
}
if (dev->devclass != DC__TERM || !dev->dynamic_term) {
/* Only a dynamically-minted RTAn: carries a network-login note; a
* static row (OPA0:) or a non-terminal is a category error, the same
* IVDEVNAM verdict resolve gives. */
exec_unlock(&vms_device_list_lock);
args.status = SS__IVDEVNAM;
goto out;
}
exec_lock(&dev->lock);
memset(dev->netlogin_user, 0, sizeof(dev->netlogin_user));
strscpy(dev->netlogin_user, args.username, sizeof(dev->netlogin_user));
exec_unlock(&dev->lock);
exec_unlock(&vms_device_list_lock);

args.status = SS__NORMAL;

out:
if (exec_copyout((void *)arg, &args, sizeof(args)))
return -EFAULT;
return 0;
}

/*
* vms_ioctl_term_getlogin - read back the network-login note for a terminal
* (rd vms-65b). An ordinary read (like RESOLVE, unprivileged): the LOGINOUT
* child bound to an RTAn: asks for ITS OWN terminal's note. An empty note is
* the honest "no network pre-authentication" (SS$_NORMAL, empty username), on
* which LOGINOUT falls back to the interactive prompt (fail-closed, INV-6) --
* NOT an error, because a terminal with no note is a perfectly ordinary
* console/DECnet terminal that authenticates its user itself.
*/
long vms_ioctl_term_getlogin(struct vms_proc *proc, unsigned long arg)
{
struct vms_termlogin_args args;
struct vms_device *dev;
char devnam[VMS_DEVNAM_SIZE];
uint32_t status;

(void)proc;

memset(&args, 0, sizeof(args));
if (exec_copyin(&args, (const void *)arg, sizeof(args)))
return -EFAULT;
args.devnam[VMS_DEVNAM_SIZE - 1] = '\0';
memset(args.username, 0, sizeof(args.username));

status = normalize_devnam(args.devnam, devnam, sizeof(devnam));
if (status != SS__NORMAL) {
args.status = status;
goto out;
}

exec_lock(&vms_device_list_lock);
dev = devtab_lookup_locked(devnam);
if (!dev) {
exec_unlock(&vms_device_list_lock);
args.status = SS__NOSUCHDEV;
goto out;
}
if (dev->devclass != DC__TERM || !dev->dynamic_term) {
exec_unlock(&vms_device_list_lock);
args.status = SS__IVDEVNAM;
goto out;
}
exec_lock(&dev->lock);
strscpy(args.username, dev->netlogin_user, sizeof(args.username));
exec_unlock(&dev->lock);
exec_unlock(&vms_device_list_lock);

/* Found: SS$_NORMAL whether or not a note is present -- the caller reads an
* empty username as "no network pre-auth" (the honest omission). */
args.status = SS__NORMAL;

out:
if (exec_copyout((void *)arg, &args, sizeof(args)))
return -EFAULT;
return 0;
}

int vms_devtab_remove_terminal(const char *devnam)
{
struct vms_device *dev;
Expand Down
24 changes: 24 additions & 0 deletions src/kernel-netbsd/vms_acp_nb.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@
#define VMS_BACKING_SIZE 16
#endif

/* Username field width -- matches src/kernel/vms_ioctl.h's VMS_USERNAME_SIZE
* (32). Guarded like the widths above so this header composes with any other
* /dev/vms contract header that also defines it. */
#ifndef VMS_USERNAME_SIZE
#define VMS_USERNAME_SIZE 32
#endif

/* FIB$L_ACCTL access-control flag + the IO$_ACCESS name buffer size --
* byte-identical to src/kernel/vms_acp.h. */
#define VMS_ACP_ACCTL_WRITE 0x00000001u
Expand Down Expand Up @@ -291,6 +298,15 @@ struct vms_terminal_args {
uint32_t pad;
};

/* Mirror of struct vms_termlogin_args (src/kernel/vms_ioctl.h) -- the RTAn:
* network-login pre-authentication note (rd vms-65b). Byte-identical layout. */
struct vms_termlogin_args {
char devnam[VMS_DEVNAM_SIZE]; /* the RTAn: terminal (in) */
char username[VMS_USERNAME_SIZE]; /* SETLOGIN: in. GETLOGIN: out. */
uint32_t status; /* return: SS$_ status */
uint32_t pad;
};

/* ================================================================
* Request numbers -- same NR band as src/kernel/vms_acp.h (0x68-0x6F); the
* NetBSD _IOWR encoding of type/nr/size legitimately differs in VALUE from
Expand Down Expand Up @@ -326,6 +342,8 @@ struct vms_terminal_args {
#define VMS_IOCTL_TERM_CREATE _IOWR(VMS_ACP_IOC_MAGIC, 0x59, struct vms_terminal_args)
#define VMS_IOCTL_TERM_DELETE _IOWR(VMS_ACP_IOC_MAGIC, 0x5a, struct vms_terminal_args)
#define VMS_IOCTL_TERM_RESOLVE _IOWR(VMS_ACP_IOC_MAGIC, 0x5b, struct vms_terminal_args)
#define VMS_IOCTL_TERM_SETLOGIN _IOWR(VMS_ACP_IOC_MAGIC, 0x5c, struct vms_termlogin_args)
#define VMS_IOCTL_TERM_GETLOGIN _IOWR(VMS_ACP_IOC_MAGIC, 0x5d, struct vms_termlogin_args)

/*
* Freeze the shared layouts -- see src/kernel/vms_acp.h's identical asserts:
Expand Down Expand Up @@ -363,11 +381,17 @@ _Static_assert(VMS_IOCTL_DISK_RESOLVE == 0xC0305657u,
"VMS_IOCTL_DISK_RESOLVE encodes differently here than on the Linux reference build");
_Static_assert(sizeof(struct vms_terminal_args) == 40,
"struct vms_terminal_args changed size -- RTAn: create/delete/resolve would decode at the wrong offsets");
_Static_assert(sizeof(struct vms_termlogin_args) == 56,
"struct vms_termlogin_args changed size -- RTAn: netlogin note would decode at the wrong offsets");
_Static_assert(VMS_IOCTL_TERM_CREATE == 0xC0285659u,
"VMS_IOCTL_TERM_CREATE encodes differently here than on the Linux reference build");
_Static_assert(VMS_IOCTL_TERM_DELETE == 0xC028565Au,
"VMS_IOCTL_TERM_DELETE encodes differently here than on the Linux reference build");
_Static_assert(VMS_IOCTL_TERM_RESOLVE == 0xC028565Bu,
"VMS_IOCTL_TERM_RESOLVE encodes differently here than on the Linux reference build");
_Static_assert(VMS_IOCTL_TERM_SETLOGIN == 0xC038565Cu,
"VMS_IOCTL_TERM_SETLOGIN encodes differently here than on the Linux reference build");
_Static_assert(VMS_IOCTL_TERM_GETLOGIN == 0xC038565Du,
"VMS_IOCTL_TERM_GETLOGIN encodes differently here than on the Linux reference build");

#endif /* _VMS_ACP_NB_H */
10 changes: 10 additions & 0 deletions src/kernel-netbsd/vms_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,14 @@ struct vms_device {
*/
uint32_t dynamic_term;

/*
* The SSH-pre-authenticated user name a network daemon vouched for this
* RTAn: (rd vms-65b), stamped by VMS_IOCTL_TERM_SETLOGIN and read back by
* the $CREPRC(LOGINOUT) child bound here (VMS_IOCTL_TERM_GETLOGIN). Empty
* unless a privileged daemon stamped it. Written/read under `lock`.
*/
char netlogin_user[VMS_USERNAME_SIZE];

/*
* Every channel currently assigned to this device, by any process: the
* device has to know this to decide when IMPLICIT ownership ends (when the
Expand Down Expand Up @@ -1073,6 +1081,8 @@ long vms_ioctl_disk_resolve(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_create(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_delete(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_resolve(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_setlogin(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_getlogin(struct vms_proc *proc, unsigned long arg);
int vms_acp_dassgn(struct vms_proc *proc, uint32_t chan);
void vms_acp_release_all(struct vms_proc *proc);
/*
Expand Down
6 changes: 6 additions & 0 deletions src/kernel-netbsd/vms_netbsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,8 @@ vms_ioctl(dev_t self __unused, u_long cmd, void *data, int flag __unused,
case VMS_IOCTL_TERM_CREATE:
case VMS_IOCTL_TERM_DELETE:
case VMS_IOCTL_TERM_RESOLVE:
case VMS_IOCTL_TERM_SETLOGIN:
case VMS_IOCTL_TERM_GETLOGIN:
uarg = data;
proc = vms_proc_get(l->l_proc->p_pid);
if (proc == NULL)
Expand Down Expand Up @@ -991,6 +993,10 @@ vms_ioctl(dev_t self __unused, u_long cmd, void *data, int flag __unused,
r = vms_ioctl_term_delete(proc, (unsigned long)uarg); break;
case VMS_IOCTL_TERM_RESOLVE:
r = vms_ioctl_term_resolve(proc, (unsigned long)uarg); break;
case VMS_IOCTL_TERM_SETLOGIN:
r = vms_ioctl_term_setlogin(proc, (unsigned long)uarg); break;
case VMS_IOCTL_TERM_GETLOGIN:
r = vms_ioctl_term_getlogin(proc, (unsigned long)uarg); break;
default:
return ENOTTY; /* unreachable */
}
Expand Down
10 changes: 10 additions & 0 deletions src/kernel/vms_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,14 @@ struct vms_device {
*/
uint32_t dynamic_term;

/*
* The SSH-pre-authenticated user name a network daemon vouched for this
* RTAn: (rd vms-65b), stamped by VMS_IOCTL_TERM_SETLOGIN and read back by
* the $CREPRC(LOGINOUT) child bound here (VMS_IOCTL_TERM_GETLOGIN). Empty
* unless a privileged daemon stamped it. Written/read under `lock`.
*/
char netlogin_user[VMS_USERNAME_SIZE];

/*
* Every channel currently assigned to this device, by any process.
* The device has to know this to decide when implicit ownership
Expand Down Expand Up @@ -1327,6 +1335,8 @@ long vms_ioctl_disk_resolve(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_create(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_delete(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_resolve(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_setlogin(struct vms_proc *proc, unsigned long arg);
long vms_ioctl_term_getlogin(struct vms_proc *proc, unsigned long arg);
/*
* Internal (non-ioctl) twin of disk_resolve for an in-executive caller: the
* Files-11 ODS-2 ACP $MOUNT (vms-127) resolves a canonical disk-unit name to its
Expand Down
43 changes: 43 additions & 0 deletions src/kernel/vms_ioctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,49 @@ _Static_assert(VMS_IOCTL_TERM_RESOLVE == 0xC028565Bu,
*/
#define VMS_USERNAME_SIZE 32

/*
* NETWORK-LOGIN PRE-AUTHENTICATION NOTE on a dynamic terminal (rd vms-65b).
*
* The conveyance channel that lets an inbound network daemon which has ALREADY
* authenticated a user in its own protocol (SSH: cryptographic/Purdy password
* against the same SYSUAF authority) hand that user to a $CREPRC(LOGINOUT,
* RTAn:, PRC$M_LOGINOUT) session WITHOUT LOGINOUT re-challenging (Option A,
* design docs/design-ssh-loginout-handoff.md). Under PRC$M_LOGINOUT the creator
* stamps NO identity (sys_process.c) and $SETIDENT is self-targeted, so the
* daemon cannot reach into the LOGINOUT child; instead it stamps the
* pre-authenticated user name onto the RTAn: DEVICE record it minted, and the
* LOGINOUT child -- bound to that same terminal by creprc_bind_terminal --
* reads it back for ITS OWN terminal.
*
* SETLOGIN is CAP_SYS_ADMIN/SETPRV-gated (exec_current_is_privileged): only a
* trusted, not-yet-dropped network daemon may vouch a pre-authentication -- the
* same authority INETD/sshd hold to establish a run-as identity. The note is a
* user NAME, never a credential: LOGINOUT still builds the persona from the
* binary SYSUAF record and grants nothing beyond it. GETLOGIN is an ordinary
* read (like RESOLVE): absent/empty is the honest "no network pre-auth", on
* which LOGINOUT falls back to the interactive prompt (fail-closed, INV-6).
*
* A DEDICATED arg struct (not a widened vms_terminal_args) so the RTAn:
* create/delete/resolve request numbers and their frozen size are untouched.
* Placed after VMS_USERNAME_SIZE because the username field uses it.
*/
struct vms_termlogin_args {
char devnam[VMS_DEVNAM_SIZE]; /* the RTAn: terminal (in) */
char username[VMS_USERNAME_SIZE]; /* SETLOGIN: in. GETLOGIN: out. */
uint32_t status; /* return: SS$_ status */
uint32_t pad;
};

#define VMS_IOCTL_TERM_SETLOGIN _IOWR(VMS_IOC_MAGIC, 0x5c, struct vms_termlogin_args)
#define VMS_IOCTL_TERM_GETLOGIN _IOWR(VMS_IOC_MAGIC, 0x5d, struct vms_termlogin_args)

_Static_assert(sizeof(struct vms_termlogin_args) == 56,
"struct vms_termlogin_args changed size -- RTAn: netlogin note would decode at the wrong offsets");
_Static_assert(VMS_IOCTL_TERM_SETLOGIN == 0xC038565Cu,
"VMS_IOCTL_TERM_SETLOGIN encodes differently here than on the reference build");
_Static_assert(VMS_IOCTL_TERM_GETLOGIN == 0xC038565Du,
"VMS_IOCTL_TERM_GETLOGIN encodes differently here than on the reference build");

/*
* Invoking CLI command-line bound (vms-f60d). OVMX DESIGN CHOICE
* (CLAUDE.md Rule 8): 256 bytes holds the classic 255-character DCL
Expand Down
4 changes: 4 additions & 0 deletions src/kernel/vms_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -2209,6 +2209,10 @@ static long vms_dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg
return vms_ioctl_term_delete(proc, arg);
case VMS_IOCTL_TERM_RESOLVE:
return vms_ioctl_term_resolve(proc, arg);
case VMS_IOCTL_TERM_SETLOGIN:
return vms_ioctl_term_setlogin(proc, arg);
case VMS_IOCTL_TERM_GETLOGIN:
return vms_ioctl_term_getlogin(proc, arg);
case VMS_IOCTL_SETTERM:
return vms_ioctl_setterm(proc, arg);

Expand Down
39 changes: 39 additions & 0 deletions src/libvmssys/vms_kif.c
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,45 @@ uint32_t vms_kif_terminal_resolve(const char *devnam, char *backing,
return args.status;
}

uint32_t vms_kif_terminal_setlogin(const char *devnam, const char *username)
{
struct vms_termlogin_args args;

if (!devnam || !username)
return 0x00000014; /* SS$_BADPARAM */

vms_memset(&args, 0, sizeof(args));
vms_strncpy(args.devnam, devnam, VMS_DEVNAM_SIZE - 1);
args.devnam[VMS_DEVNAM_SIZE - 1] = '\0';
vms_strncpy(args.username, username, VMS_USERNAME_SIZE - 1);
args.username[VMS_USERNAME_SIZE - 1] = '\0';

KIF_CALL(VMS_IOCTL_TERM_SETLOGIN, &args);
return args.status;
}

uint32_t vms_kif_terminal_getlogin(const char *devnam, char *username,
uint32_t username_size)
{
struct vms_termlogin_args args;

if (!devnam || !username || username_size == 0)
return 0x00000014; /* SS$_BADPARAM */

vms_memset(&args, 0, sizeof(args));
vms_strncpy(args.devnam, devnam, VMS_DEVNAM_SIZE - 1);
args.devnam[VMS_DEVNAM_SIZE - 1] = '\0';

KIF_CALL(VMS_IOCTL_TERM_GETLOGIN, &args);

username[0] = '\0';
if (args.status & 1) {
vms_strncpy(username, args.username, username_size - 1);
username[username_size - 1] = '\0';
}
return args.status;
}

uint32_t vms_kif_getvol(const char *devnam, struct vms_getvol_args *out)
{
struct vms_getvol_args args;
Expand Down
22 changes: 22 additions & 0 deletions src/libvmssys/vms_kif.h
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,28 @@ uint32_t vms_kif_terminal_delete(const char *devnam);
uint32_t vms_kif_terminal_resolve(const char *devnam, char *backing,
uint32_t backing_size);

/*
* Stamp / read the SSH-pre-authenticated network-login note on a dynamic
* terminal (rd vms-65b). setlogin is CAP_SYS_ADMIN/SETPRV-gated (a network
* daemon vouching a pre-authentication before it drops privilege); getlogin is
* an ordinary read. On getlogin, an EMPTY *username with SS$_NORMAL is the
* honest "no network pre-auth" -- the caller (LOGINOUT) then prompts.
*
* OVMX-UNWIRED: vms_kif_terminal_setlogin (vms-65b) -- the STAMP is emitted by
* the wrapped OpenSSH sshd (src/vmsssh/sshd_session.c, ovmx_sshd_pre_drop_pw),
* which is not a CMake product target: it is built as a separate musl-static
* binary by third-party/openssh/build-ssh-harness.sh and reached from OpenSSH's
* own main() through --wrap=permanently_set_uid, so the caller census (which
* follows the CMake product graph) cannot see the call -- the same footing as
* vms_kif_dlm_xnode above, whose caller (scsd) is likewise a separately-built
* daemon. The READ half (vms_kif_terminal_getlogin) IS census-wired: LOGINOUT
* (tools/vms_login.c) is a CMake product target and calls it. Retire this line
* if the wrapped-sshd sources ever join the census's product graph.
*/
uint32_t vms_kif_terminal_setlogin(const char *devnam, const char *username);
uint32_t vms_kif_terminal_getlogin(const char *devnam, char *username,
uint32_t username_size);

/* Set terminal characteristics through an assigned channel (the
* $QIO IO$_SETMODE path). flags is a mask of VMS_TTSET_*; SS$_IVCHAN
* if the caller holds no such channel.
Expand Down
Loading
Loading