Group :: System/Base
RPM: cockpit
Main Changelog Spec Patches Sources Download Gear Bugs and FR Repocop
Patch: cockpit-280.1-alt.patch
Download
Download
pkg/apps/manifest.json | 3 +-
pkg/base1/test-permissions.js | 2 +-
pkg/lib/cockpit-components-password.jsx | 63 ++++++++++++++++++++++++++++++---
pkg/storaged/manifest.json | 1 +
pkg/users/account-create-dialog.js | 7 ++--
pkg/users/account-details.js | 2 +-
pkg/users/account-logs-panel.jsx | 18 ++++++----
pkg/users/accounts-list.js | 2 +-
pkg/users/expiration-dialogs.js | 2 +-
pkg/users/index.html | 1 +
pkg/users/manifest.json | 6 ++++
pkg/users/password-dialogs.js | 17 +++++----
src/bridge/test-connect.c | 10 +++---
src/bridge/test-stream.c | 10 +++---
src/common/cockpitloopback.c | 23 ++++++++++--
15 files changed, 128 insertions(+), 39 deletions(-)
diff --git a/pkg/apps/manifest.json b/pkg/apps/manifest.json
index 0679c1b3b..e09a2d587 100644
--- a/pkg/apps/manifest.json
+++ b/pkg/apps/manifest.json
@@ -17,7 +17,8 @@
"debian": ["appstream"], "ubuntu": ["appstream"]
},
"appstream_data_packages": {
- "fedora": ["appstream-data"], "rhel": ["appstream-data"]
+ "fedora": ["appstream-data"], "rhel": ["appstream-data"],
+ "altlinux": ["appstream-data"]
}
}
}
diff --git a/pkg/base1/test-permissions.js b/pkg/base1/test-permissions.js
index 956395858..123943fad 100644
--- a/pkg/base1/test-permissions.js
+++ b/pkg/base1/test-permissions.js
@@ -9,7 +9,7 @@ const root_user = {
const priv_user = {
name: "user",
- id: 1000,
+ id: 500,
groups: ["user", "agroup"]
};
diff --git a/pkg/lib/cockpit-components-password.jsx b/pkg/lib/cockpit-components-password.jsx
index 08cfc51b6..3e7f8f152 100644
--- a/pkg/lib/cockpit-components-password.jsx
+++ b/pkg/lib/cockpit-components-password.jsx
@@ -17,6 +17,7 @@
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import cockpit from 'cockpit';
+import { read_os_release } from "os-release.js";
import React, { useState } from 'react';
import { FormGroup, Popover, Progress, ProgressSize, ProgressMeasureLocation, TextInput } from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';
@@ -25,10 +26,34 @@ import './cockpit-components-password.scss';
const _ = cockpit.gettext;
-export function password_quality(password, force) {
+export function password_quality_proxy(new_password, old_password, user, force) {
+ function get_config(name, distro_id, def) {
+ if (cockpit.manifests.users && cockpit.manifests.users.config) {
+ let val = cockpit.manifests.users.config[name];
+ if (typeof val === 'object' && val !== null)
+ val = val[distro_id];
+ return val !== undefined ? val : def;
+ } else {
+ return def;
+ }
+ }
+
+ const password_quality_libs = {
+ libpasswdqc: pw_libpasswdqc,
+ libpwquality: pw_libpwquality,
+ };
+
+ return read_os_release().then(os_release => {
+ const quality_lib = get_config('password_quality_lib', os_release.ID, 'libpwquality');
+ const quality_function = password_quality_libs[quality_lib];
+ return quality_function(new_password, old_password, user, force);
+ });
+}
+
+function pw_libpwquality(new_password, old_password, user, force) {
return new Promise((resolve, reject) => {
cockpit.spawn('/usr/bin/pwscore', { err: "message" })
- .input(password)
+ .input(new_password)
.done(function(content) {
const quality = parseInt(content, 10);
if (quality === 0)
@@ -45,12 +70,42 @@ export function password_quality(password, force) {
});
}
+function pw_libpasswdqc(new_password, old_password, user, force) {
+ return new Promise((resolve, reject) => {
+ if (!user || !new_password)
+ reject(new Error(_("Username or password is empty")));
+
+ /* pwqcheck doesn't accept nonexistent users */
+ cockpit.spawn('/usr/bin/pwqcheck', { err: "message" })
+ .input(
+ [
+ new_password,
+ old_password,
+ `${user}:::::/dev/null:/sbin/nologin`,
+ ].join('\n')
+ )
+ .done(function(content) {
+ if (content === 'OK\n')
+ resolve({ value: '', message: undefined });
+ else
+ reject(new Error(_("Password is too weak")));
+ })
+ .fail(function(ex) {
+ if (!force)
+ reject(new Error(ex.message || _("Password is not acceptable")));
+ else
+ resolve({ value: 0 });
+ });
+ });
+}
+
export const PasswordFormFields = ({
password_label, password_confirm_label,
password_label_info,
initial_password,
error_password, error_password_confirm,
- idPrefix, change
+ idPrefix, change,
+ user_name
}) => {
const [password, setPassword] = useState(initial_password);
const [passwordConfirm, setConfirmPassword] = useState(undefined);
@@ -62,7 +117,7 @@ export const PasswordFormFields = ({
change("password", value);
if (value) {
- password_quality(value)
+ password_quality_proxy(value, '', user_name)
.catch(() => {
return { value: 0 };
})
diff --git a/pkg/storaged/manifest.json b/pkg/storaged/manifest.json
index 1e686ab11..3774b405d 100644
--- a/pkg/storaged/manifest.json
+++ b/pkg/storaged/manifest.json
@@ -53,6 +53,7 @@
"config": {
"nfs_client_package": {
"rhel": "nfs-utils", "fedora": "nfs-utils",
+ "altlinux": "nfs-utils",
"opensuse": "nfs-client", "opensuse-leap": "nfs-client",
"debian": "nfs-common", "ubuntu": "nfs-common",
"arch": "nfs-utils"
diff --git a/pkg/users/account-create-dialog.js b/pkg/users/account-create-dialog.js
index 2f66c2775..0ef784aa2 100644
--- a/pkg/users/account-create-dialog.js
+++ b/pkg/users/account-create-dialog.js
@@ -23,7 +23,7 @@ import React from 'react';
import { Checkbox, Form, FormGroup, TextInput, Popover, Flex, FlexItem, Radio } from '@patternfly/react-core';
import { has_errors } from "./dialog-utils.js";
import { passwd_change } from "./password-dialogs.js";
-import { password_quality, PasswordFormFields } from "cockpit-components-password.jsx";
+import { password_quality_proxy, PasswordFormFields } from "cockpit-components-password.jsx";
import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog.jsx";
import { HelpIcon } from '@patternfly/react-icons';
@@ -60,7 +60,8 @@ function AccountCreateBody({ state, errors, change }) {
error_password={errors && errors.password}
error_password_confirm={errors && errors.password_confirm}
idPrefix="accounts-create-password"
- change={change} />
+ change={change}
+ user_name={user_name} />
<FormGroup label={_("Authentication")} fieldId="accounts-create-locked" hasNoPaddingTop>
<Radio id="account-use-password"
@@ -214,7 +215,7 @@ export function account_create_dialog(accounts) {
errs.user_name = validate_username(user_name, accounts);
- return password_quality(password, force)
+ return password_quality_proxy(password, '', user_name, force)
.catch(ex => {
errs.password = (ex.message || ex.toString()).replaceAll("\n", " ");
})
diff --git a/pkg/users/account-details.js b/pkg/users/account-details.js
index f8d4e1079..5ebbcc9ef 100644
--- a/pkg/users/account-details.js
+++ b/pkg/users/account-details.js
@@ -49,7 +49,7 @@ function log_unexpected_error(error) {
}
function get_locked(name) {
- return cockpit.spawn(["/usr/bin/passwd", "-S", name], { environ: ["LC_ALL=C"], superuser: "require" })
+ return cockpit.spawn(["/usr/bin/getent", "shadow", name], { environ: ["LC_ALL=C"], superuser: "require" })
.catch(() => "")
.then(content => {
const status = content.split(" ")[1];
diff --git a/pkg/users/account-logs-panel.jsx b/pkg/users/account-logs-panel.jsx
index 6e3cbac47..8b0210381 100644
--- a/pkg/users/account-logs-panel.jsx
+++ b/pkg/users/account-logs-panel.jsx
@@ -23,15 +23,13 @@ import React, { useState, useEffect } from 'react';
import { Card, CardTitle, CardBody, Text } from '@patternfly/react-core';
import { ListingTable } from 'cockpit-components-table.jsx';
-import * as timeformat from "timeformat.js";
-
const _ = cockpit.gettext;
export function AccountLogs({ name }) {
const [logins, setLogins] = useState([]);
useEffect(() => {
if (logins.length === 0) {
- cockpit.spawn(["/usr/bin/last", "--time-format", "iso", "-n", 25, name], { environ: ["LC_ALL=C"] })
+ cockpit.spawn(["/usr/bin/last", "-F", "-w", "-n", 25, name], { environ: ["LC_ALL=C"] })
.then(data => {
let logins = [];
data.split('\n').forEach(line => {
@@ -46,6 +44,7 @@ export function AccountLogs({ name }) {
// format:
// admin web console ::ffff:172.27.0. 2021-09-24T09:02:13+00:00 - 2021-09-24T09:04:20+00:00 (00:02)
+ /*
const lines = line.split(/ +/);
const ended = new Date(lines[lines.length - 2]);
const started = new Date(lines[lines.length - 4]);
@@ -59,6 +58,13 @@ export function AccountLogs({ name }) {
ended,
from
});
+ */
+ /*
+ one of last's format is
+ "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n",
+ which in unparsable(?), thereby, print it as is
+ */
+ logins.push(line);
});
// Only show 15 login lines
@@ -79,13 +85,11 @@ export function AccountLogs({ name }) {
<CardBody className="contains-list">
<ListingTable variant="compact" aria-label={ _("Login history list") }
columns={ [
- { title: _("Started") },
- { title: _("Ended") },
- { title: _("From") },
+ { title: _("Login history") },
] }
rows={ logins.map((line, index) => ({
props: { key: index },
- columns: [timeformat.dateTime(line.started), timeformat.dateTime(line.ended), line.from]
+ columns: [line]
}))} />
</CardBody>
</Card>
diff --git a/pkg/users/accounts-list.js b/pkg/users/accounts-list.js
index 222cb644a..67bc72fa1 100644
--- a/pkg/users/accounts-list.js
+++ b/pkg/users/accounts-list.js
@@ -57,7 +57,7 @@ function AccountItem({ account, current }) {
export function AccountsList({ accounts, current_user }) {
const filtered_accounts = accounts.filter(function(account) {
- return !((account.uid < 1000 && account.uid !== 0) ||
+ return !((account.uid < 500 && account.uid !== 0) ||
account.shell.match(/^(\/usr)?\/sbin\/nologin/) ||
account.shell === '/bin/false');
});
diff --git a/pkg/users/expiration-dialogs.js b/pkg/users/expiration-dialogs.js
index 4ae408596..b2a287f94 100644
--- a/pkg/users/expiration-dialogs.js
+++ b/pkg/users/expiration-dialogs.js
@@ -218,7 +218,7 @@ export function password_expiration_dialog(account, expire_days) {
clicked: () => {
if (validate()) {
const days = state.mode == "expires" ? parseInt(state.days) : 99999;
- return cockpit.spawn(["/usr/bin/passwd", "-x", String(days), account.name],
+ return cockpit.spawn(["/usr/bin/chage", "-M", String(days), account.name],
{ superuser: true, err: "message" });
} else {
update();
diff --git a/pkg/users/index.html b/pkg/users/index.html
index 28766bc76..84f65f089 100644
--- a/pkg/users/index.html
+++ b/pkg/users/index.html
@@ -25,6 +25,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="users.css" type="text/css" rel="stylesheet">
<script src="../base1/cockpit.js"></script>
+ <script src="../manifests.js"></script>
<script src="../base1/po.js"></script>
<script src="po.js"></script>
<script src="users.js"></script>
diff --git a/pkg/users/manifest.json b/pkg/users/manifest.json
index 4ee89f263..52c42ed0a 100644
--- a/pkg/users/manifest.json
+++ b/pkg/users/manifest.json
@@ -15,5 +15,11 @@
}
]
}
+ },
+
+ "config": {
+ "password_quality_lib": {
+ "altlinux": "libpasswdqc"
+ }
}
}
diff --git a/pkg/users/password-dialogs.js b/pkg/users/password-dialogs.js
index ade6b2079..1bd3a7016 100644
--- a/pkg/users/password-dialogs.js
+++ b/pkg/users/password-dialogs.js
@@ -24,7 +24,7 @@ import { Form, FormGroup, TextInput } from '@patternfly/react-core';
import { has_errors } from "./dialog-utils.js";
import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog.jsx";
-import { password_quality, PasswordFormFields } from "cockpit-components-password.jsx";
+import { password_quality_proxy, PasswordFormFields } from "cockpit-components-password.jsx";
const _ = cockpit.gettext;
@@ -38,7 +38,8 @@ function passwd_self(old_pass, new_pass) {
/.*New password: $/,
/.*Retype new password: $/,
/.*Enter new \w*\s?password: $/,
- /.*Retype new \w*\s?password: $/
+ /.*Retype new \w*\s?password: $/,
+ /.*Re-type new password: $/
];
const bad_exps = [
/.*BAD PASSWORD:.*/
@@ -128,7 +129,7 @@ export function passwd_change(user, new_pass) {
}
function SetPasswordDialogBody({ state, errors, change }) {
- const { need_old, password_old, current_user } = state;
+ const { need_old, password_old, current_user, user_name } = state;
return (
<Form isHorizontal onSubmit={apply_modal_dialog}>
@@ -148,7 +149,8 @@ function SetPasswordDialogBody({ state, errors, change }) {
error_password={errors && errors.password}
error_password_confirm={errors && errors.password_confirm}
idPrefix="account-set-password"
- change={change} />
+ change={change}
+ user_name={user_name} />
</Form>
);
}
@@ -165,6 +167,7 @@ export function set_password_dialog(account, current_user) {
password: "",
password_confirm: "",
confirm_weak: false,
+ user_name: account.name,
};
let errors = { };
@@ -183,7 +186,7 @@ export function set_password_dialog(account, current_user) {
update();
}
- function validate(force, password, password_confirm) {
+ function validate(force, password, password_confirm, password_old) {
const errs = { };
if (password != password_confirm)
@@ -192,7 +195,7 @@ export function set_password_dialog(account, current_user) {
if (password.length > 256)
errs.password = _("Password is longer than 256 characters");
- return password_quality(password, force)
+ return password_quality_proxy(password, password_old, account.name, force)
.catch(ex => {
errs.password = (ex.message || ex.toString()).replaceAll("\n", " ");
})
@@ -277,7 +280,7 @@ export function reset_password_dialog(account) {
caption: _("Reset password"),
style: "primary",
clicked: () => {
- return cockpit.spawn(["/usr/bin/passwd", "-e", account.name],
+ return cockpit.spawn(["/usr/bin/chage", "-d", "0", account.name],
{ superuser: true, err: "message" });
}
}
diff --git a/src/bridge/test-connect.c b/src/bridge/test-connect.c
index 25d147948..3e8ca6d20 100644
--- a/src/bridge/test-connect.c
+++ b/src/bridge/test-connect.c
@@ -181,11 +181,6 @@ setup_connect (TestConnect *tc,
tc->listen_sock = g_socket_new (family, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT, &error);
- g_assert_no_error (error);
-
- g_socket_bind (tc->listen_sock, address, TRUE, &error);
- g_object_unref (address);
-
if (error != NULL && family == G_SOCKET_FAMILY_IPV6)
{
/* Some test runners don't have IPv6 loopback, strangely enough */
@@ -196,6 +191,11 @@ setup_connect (TestConnect *tc,
g_assert_no_error (error);
+ g_socket_bind (tc->listen_sock, address, TRUE, &error);
+ g_object_unref (address);
+
+ g_assert_no_error (error);
+
tc->address = g_socket_get_local_address (tc->listen_sock, &error);
g_assert_no_error (error);
diff --git a/src/bridge/test-stream.c b/src/bridge/test-stream.c
index 227d72911..4af12372f 100644
--- a/src/bridge/test-stream.c
+++ b/src/bridge/test-stream.c
@@ -731,11 +731,6 @@ setup_connect (TestConnect *tc,
tc->listen_sock = g_socket_new (family, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT, &error);
- g_assert_no_error (error);
-
- g_socket_bind (tc->listen_sock, address, TRUE, &error);
- g_object_unref (address);
-
if (error != NULL && family == G_SOCKET_FAMILY_IPV6)
{
/* Some test runners don't have IPv6 loopback, strangely enough */
@@ -746,6 +741,11 @@ setup_connect (TestConnect *tc,
g_assert_no_error (error);
+ g_socket_bind (tc->listen_sock, address, TRUE, &error);
+ g_object_unref (address);
+
+ g_assert_no_error (error);
+
tc->address = g_socket_get_local_address (tc->listen_sock, &error);
g_assert_no_error (error);
diff --git a/src/common/cockpitloopback.c b/src/common/cockpitloopback.c
index 2a2cb6f75..b70fab6c9 100644
--- a/src/common/cockpitloopback.c
+++ b/src/common/cockpitloopback.c
@@ -127,6 +127,20 @@ cockpit_loopback_connectable_iface (GSocketConnectableIface *iface)
iface->proxy_enumerate = cockpit_loopback_enumerate;
}
+static gboolean
+ipv6_enabled (void)
+{
+ GSocket *socket;
+ socket = g_socket_new (G_SOCKET_FAMILY_IPV6, G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT, NULL);
+ if (socket)
+ {
+ g_object_unref (socket);
+ return TRUE;
+ }
+ return FALSE;
+}
+
GSocketConnectable *
cockpit_loopback_new (guint16 port)
{
@@ -135,9 +149,12 @@ cockpit_loopback_new (guint16 port)
self = g_object_new (COCKPIT_TYPE_LOOPBACK, NULL);
- addr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6);
- g_queue_push_tail (&self->addresses, g_inet_socket_address_new (addr, port));
- g_object_unref (addr);
+ if (ipv6_enabled())
+ {
+ addr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6);
+ g_queue_push_tail (&self->addresses, g_inet_socket_address_new (addr, port));
+ g_object_unref (addr);
+ }
addr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
g_queue_push_tail (&self->addresses, g_inet_socket_address_new (addr, port));