|
41 | 41 | #include "process-util.h"
|
42 | 42 | #include "string-util.h"
|
43 | 43 | #include "strv.h"
|
| 44 | +#include "user-util.h" |
44 | 45 |
|
45 | 46 | #define log_debug_bus_message(m) \
|
46 | 47 | do { \
|
@@ -1514,44 +1515,228 @@ _public_ int sd_bus_open_system_remote(sd_bus **ret, const char *host) {
|
1514 | 1515 | return 0;
|
1515 | 1516 | }
|
1516 | 1517 |
|
1517 |
| -int bus_set_address_system_machine(sd_bus *b, const char *machine) { |
1518 |
| - _cleanup_free_ char *e = NULL; |
| 1518 | +int bus_set_address_machine(sd_bus *b, bool user, const char *machine) { |
| 1519 | + const char *rhs; |
1519 | 1520 | char *a;
|
1520 | 1521 |
|
1521 | 1522 | assert(b);
|
1522 | 1523 | assert(machine);
|
1523 | 1524 |
|
1524 |
| - e = bus_address_escape(machine); |
1525 |
| - if (!e) |
1526 |
| - return -ENOMEM; |
| 1525 | + rhs = strchr(machine, '@'); |
| 1526 | + if (rhs || user) { |
| 1527 | + _cleanup_free_ char *u = NULL, *eu = NULL, *erhs = NULL; |
| 1528 | + |
| 1529 | + /* If there's an "@" in the container specification, we'll connect as a user specified at its |
| 1530 | + * left hand side, which is useful in combination with user=true. This isn't as trivial as it |
| 1531 | + * might sound: it's not sufficient to enter the container and connect to some socket there, |
| 1532 | + * since the --user socket path depends on $XDG_RUNTIME_DIR which is set via PAM. Thus, to be |
| 1533 | + * able to connect, we need to have a PAM session. Our way out? We use systemd-run to get |
| 1534 | + * into the container and acquire a PAM session there, and then invoke systemd-stdio-bridge |
| 1535 | + * in it, which propagates the bus transport to us.*/ |
| 1536 | + |
| 1537 | + if (rhs) { |
| 1538 | + if (rhs > machine) |
| 1539 | + u = strndup(machine, rhs - machine); |
| 1540 | + else |
| 1541 | + u = getusername_malloc(); /* Empty user name, let's use the local one */ |
| 1542 | + if (!u) |
| 1543 | + return -ENOMEM; |
1527 | 1544 |
|
1528 |
| - a = strjoin("x-machine-unix:machine=", e); |
1529 |
| - if (!a) |
1530 |
| - return -ENOMEM; |
| 1545 | + eu = bus_address_escape(u); |
| 1546 | + if (!eu) |
| 1547 | + return -ENOMEM; |
| 1548 | + |
| 1549 | + rhs++; |
| 1550 | + } else { |
| 1551 | + /* No "@" specified but we shall connect to the user instance? Then assume root (and |
| 1552 | + * not a user named identically to the calling one). This means: |
| 1553 | + * |
| 1554 | + * --machine=foobar --user → connect to user bus of root user in container "foobar" |
| 1555 | + * --machine=@foobar --user → connect to user bus of user named like the calling user in container "foobar" |
| 1556 | + * |
| 1557 | + * Why? so that behaviour for "--machine=foobar --system" is roughly similar to |
| 1558 | + * "--machine=foobar --user": both times we unconditionally connect as root user |
| 1559 | + * regardless what the calling user is. */ |
| 1560 | + |
| 1561 | + rhs = machine; |
| 1562 | + } |
| 1563 | + |
| 1564 | + if (!isempty(rhs)) { |
| 1565 | + erhs = bus_address_escape(rhs); |
| 1566 | + if (!erhs) |
| 1567 | + return -ENOMEM; |
| 1568 | + } |
| 1569 | + |
| 1570 | + /* systemd-run -M… -PGq --wait -pUser=… -pPAMName=login systemd-stdio-bridge */ |
| 1571 | + |
| 1572 | + a = strjoin("unixexec:path=systemd-run," |
| 1573 | + "argv1=-M", erhs ?: ".host", "," |
| 1574 | + "argv2=-PGq," |
| 1575 | + "argv3=--wait," |
| 1576 | + "argv4=-pUser%3d", eu ?: "root", ",", |
| 1577 | + "argv5=-pPAMName%3dlogin," |
| 1578 | + "argv6=systemd-stdio-bridge"); |
| 1579 | + if (!a) |
| 1580 | + return -ENOMEM; |
| 1581 | + |
| 1582 | + if (user) { |
| 1583 | + char *k; |
| 1584 | + |
| 1585 | + /* Ideally we'd use the "--user" switch to systemd-stdio-bridge here, but it's only |
| 1586 | + * available in recent systemd versions. Using the "-p" switch with the explicit path |
| 1587 | + * is a working alternative, and is compatible with older versions, hence that's what |
| 1588 | + * we use here. */ |
| 1589 | + |
| 1590 | + k = strjoin(a, ",argv7=-punix:path%3d%24%7bXDG_RUNTIME_DIR%7d/bus"); |
| 1591 | + if (!k) |
| 1592 | + return -ENOMEM; |
| 1593 | + |
| 1594 | + free_and_replace(a, k); |
| 1595 | + } |
| 1596 | + } else { |
| 1597 | + _cleanup_free_ char *e = NULL; |
| 1598 | + |
| 1599 | + /* Just a container name, we can go the simple way, and just join the container, and connect |
| 1600 | + * to the well-known path of the system bus there. */ |
| 1601 | + |
| 1602 | + e = bus_address_escape(machine); |
| 1603 | + if (!e) |
| 1604 | + return -ENOMEM; |
| 1605 | + |
| 1606 | + a = strjoin("x-machine-unix:machine=", e); |
| 1607 | + if (!a) |
| 1608 | + return -ENOMEM; |
| 1609 | + } |
1531 | 1610 |
|
1532 | 1611 | return free_and_replace(b->address, a);
|
1533 | 1612 | }
|
1534 | 1613 |
|
1535 |
| -_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *machine) { |
| 1614 | +static int user_and_machine_valid(const char *user_and_machine) { |
| 1615 | + const char *h; |
| 1616 | + |
| 1617 | + /* Checks if a container specification in the form "user@container" or just "container" is valid. |
| 1618 | + * |
| 1619 | + * If the "@" syntax is used we'll allow either the "user" or the "container" part to be omitted, but |
| 1620 | + * not both. */ |
| 1621 | + |
| 1622 | + h = strchr(user_and_machine, '@'); |
| 1623 | + if (!h) |
| 1624 | + h = user_and_machine; |
| 1625 | + else { |
| 1626 | + _cleanup_free_ char *user = NULL; |
| 1627 | + |
| 1628 | + user = strndup(user_and_machine, h - user_and_machine); |
| 1629 | + if (!user) |
| 1630 | + return -ENOMEM; |
| 1631 | + |
| 1632 | + if (!isempty(user) && !valid_user_group_name(user, VALID_USER_RELAX)) |
| 1633 | + return false; |
| 1634 | + |
| 1635 | + h++; |
| 1636 | + |
| 1637 | + if (isempty(h)) |
| 1638 | + return !isempty(user); |
| 1639 | + } |
| 1640 | + |
| 1641 | + return hostname_is_valid(h, VALID_HOSTNAME_DOT_HOST); |
| 1642 | +} |
| 1643 | + |
| 1644 | +static int user_and_machine_equivalent(const char *user_and_machine) { |
| 1645 | + _cleanup_free_ char *un = NULL; |
| 1646 | + const char *f; |
| 1647 | + |
| 1648 | + /* Returns true if the specified user+machine name are actually equivalent to our own identity and |
| 1649 | + * our own host. If so we can shortcut things. Why bother? Because that way we don't have to fork |
| 1650 | + * off short-lived worker processes that are then unavailable for authentication and logging in the |
| 1651 | + * peer. Moreover joining a namespace requires privileges. If we are in the right namespace anyway, |
| 1652 | + * we can avoid permission problems thus. */ |
| 1653 | + |
| 1654 | + assert(user_and_machine); |
| 1655 | + |
| 1656 | + /* Omitting the user name means that we shall use the same user name as we run as locally, which |
| 1657 | + * means we'll end up on the same host, let's shortcut */ |
| 1658 | + if (streq(user_and_machine, "@.host")) |
| 1659 | + return true; |
| 1660 | + |
| 1661 | + /* Otherwise, if we are root, then we can also allow the ".host" syntax, as that's the user this |
| 1662 | + * would connect to. */ |
| 1663 | + if (geteuid() == 0 && STR_IN_SET(user_and_machine, ".host", "root@.host")) |
| 1664 | + return true; |
| 1665 | + |
| 1666 | + /* Otherwise, we have to figure our user name, and compare things with that. */ |
| 1667 | + un = getusername_malloc(); |
| 1668 | + if (!un) |
| 1669 | + return -ENOMEM; |
| 1670 | + |
| 1671 | + f = startswith(user_and_machine, un); |
| 1672 | + if (!f) |
| 1673 | + return false; |
| 1674 | + |
| 1675 | + return STR_IN_SET(f, "@", "@.host"); |
| 1676 | +} |
| 1677 | + |
| 1678 | +_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *user_and_machine) { |
1536 | 1679 | _cleanup_(bus_freep) sd_bus *b = NULL;
|
1537 | 1680 | int r;
|
1538 | 1681 |
|
1539 |
| - assert_return(machine, -EINVAL); |
| 1682 | + assert_return(user_and_machine, -EINVAL); |
1540 | 1683 | assert_return(ret, -EINVAL);
|
1541 |
| - assert_return(hostname_is_valid(machine, VALID_HOSTNAME_DOT_HOST), -EINVAL); |
| 1684 | + |
| 1685 | + if (user_and_machine_equivalent(user_and_machine)) |
| 1686 | + return sd_bus_open_system(ret); |
| 1687 | + |
| 1688 | + r = user_and_machine_valid(user_and_machine); |
| 1689 | + if (r < 0) |
| 1690 | + return r; |
| 1691 | + |
| 1692 | + assert_return(r > 0, -EINVAL); |
1542 | 1693 |
|
1543 | 1694 | r = sd_bus_new(&b);
|
1544 | 1695 | if (r < 0)
|
1545 | 1696 | return r;
|
1546 | 1697 |
|
1547 |
| - r = bus_set_address_system_machine(b, machine); |
| 1698 | + r = bus_set_address_machine(b, false, user_and_machine); |
1548 | 1699 | if (r < 0)
|
1549 | 1700 | return r;
|
1550 | 1701 |
|
1551 | 1702 | b->bus_client = true;
|
1552 |
| - b->trusted = false; |
1553 | 1703 | b->is_system = true;
|
1554 |
| - b->is_local = false; |
| 1704 | + |
| 1705 | + r = sd_bus_start(b); |
| 1706 | + if (r < 0) |
| 1707 | + return r; |
| 1708 | + |
| 1709 | + *ret = TAKE_PTR(b); |
| 1710 | + return 0; |
| 1711 | +} |
| 1712 | + |
| 1713 | +_public_ int sd_bus_open_user_machine(sd_bus **ret, const char *user_and_machine) { |
| 1714 | + _cleanup_(bus_freep) sd_bus *b = NULL; |
| 1715 | + int r; |
| 1716 | + |
| 1717 | + assert_return(user_and_machine, -EINVAL); |
| 1718 | + assert_return(ret, -EINVAL); |
| 1719 | + |
| 1720 | + /* Shortcut things if we'd end up on this host and as the same user. */ |
| 1721 | + if (user_and_machine_equivalent(user_and_machine)) |
| 1722 | + return sd_bus_open_user(ret); |
| 1723 | + |
| 1724 | + r = user_and_machine_valid(user_and_machine); |
| 1725 | + if (r < 0) |
| 1726 | + return r; |
| 1727 | + |
| 1728 | + assert_return(r > 0, -EINVAL); |
| 1729 | + |
| 1730 | + r = sd_bus_new(&b); |
| 1731 | + if (r < 0) |
| 1732 | + return r; |
| 1733 | + |
| 1734 | + r = bus_set_address_machine(b, true, user_and_machine); |
| 1735 | + if (r < 0) |
| 1736 | + return r; |
| 1737 | + |
| 1738 | + b->bus_client = true; |
| 1739 | + b->trusted = true; |
1555 | 1740 |
|
1556 | 1741 | r = sd_bus_start(b);
|
1557 | 1742 | if (r < 0)
|
|
0 commit comments