Řešení 1:
Pro budoucí použití:po příliš mnoha hodinách zkoumání a ladění tohoto problému jsem konečně objevil hlavní příčinu.
Verze OpenSSH používaná společností Synology je vysoce přizpůsobená verze, která není chovat se jako původní kód. Má spoustu hacků a ad-hoc přizpůsobení - například další kontrolu před přijetím přihlášení, zda je služba SSH povolena ve webovém rozhraní, nebo odstranění speciálních znaků (;, |, ') z příkazů rsync nebo... . počkejte na to... vyhněte se běžným uživatelům používat jiný shell než /bin/sh nebo /bin/ash . Jo, pevně zakódováno v binárním kódu.
Zde je část kódu z OpenSSH 5.8p1, jak jej distribuuje Synology ve svém zdrojovém kódu (DSM4.1 – větev 2636), soubor session.c
:
void do_child(Session *s, const char *command)
{
...
#ifdef MY_ABC_HERE
char szValue[8];
int RunSSH = 0;
SSH_CMD SSHCmd = REQ_UNKNOWN;
if (1 == GetKeyValue("/etc/synoinfo.conf", "runssh", szValue, sizeof(szValue))) {
if (strcasecmp(szValue, "yes") == 0) {
RunSSH = 1;
}
}
if (IsSFTPReq(command)){
SSHCmd = REQ_SFTP;
} else if (IsRsyncReq(command)){
SSHCmd = REQ_RSYNC;
} else if (IsTimebkpRequest(command)){
SSHCmd = REQ_TIMEBKP;
} else if (RunSSH && IsAllowShell(pw)){
SSHCmd = REQ_SHELL;
} else {
goto Err;
}
if (REQ_RSYNC == SSHCmd) {
pw = SYNOChgValForRsync(pw);
}
if (!SSHCanLogin(SSHCmd, pw)) {
goto Err;
}
goto Pass;
Err:
fprintf(stderr, "Permission denied, please try again.\n");
exit(1);
Pass:
#endif /* MY_ABC_HERE */
...
}
Jak si dokážete představit, IsAllowShell(pw)
byl viník:
static int IsAllowShell(const struct passwd *pw)
{
struct passwd *pUnPrivilege = NULL;
char *szUserName = NULL;
if (!pw || !pw->pw_name) {
return 0;
}
szUserName = pw->pw_name;
if(!strcmp(szUserName, "root") || !strcmp(szUserName, "admin")){
return 1;
}
if (NULL != (pUnPrivilege = getpwnam(szUserName))){
if (!strcmp(pUnPrivilege->pw_shell, "/bin/sh") ||
!strcmp(pUnPrivilege->pw_shell, "/bin/ash")) {
return 1;
}
}
return 0;
}
Není divu, proč jsem zažil tak zvláštní chování. Pro jiné uživatele než root by byly akceptovány pouze shelly /bin/sh a /bin/ash nebo admin . A to bez ohledu na uid (testoval jsem také vytváření joeuser uid=0 a nefungovalo to. Teď je jasné proč).
Po identifikaci příčiny byla oprava snadná:stačí odstranit volání IsAllowShell() . Chvíli mi trvalo, než jsem získal správnou konfiguraci pro křížovou kompilaci openssh a všech jeho závislostí, ale nakonec to fungovalo dobře.
Pokud má někdo zájem udělat totéž (nebo se snaží křížově zkompilovat další moduly jádra nebo binární soubory pro Synology), zde je moje verze Makefile . Byl testován se zdrojem OpenSSH-5.8p1 a funguje dobře s modely s procesorem Marvell Kirkwood mv6281/mv6282 (jako DS212+). Použil jsem hostitele se systémem Ubuntu 12.10 x64.
Sečteno a podtrženo:špatný postup, hrozný kód a skvělý příklad toho, co ne dělat. Chápu, že výrobci OEM někdy potřebují vyvinout speciální přizpůsobení, ale měli by si to dvakrát rozmyslet, než se zavrtají příliš hluboko. Nejen, že to pro ně má za následek neudržitelný kód, ale také to vytváří nejrůznější nepředvídané problémy. Naštěstí existuje GPL, aby byli upřímní – a otevření.
Řešení 2:
Abych tento problém obešel, a protože jsem nainstaloval bash přes ipkg a nemohu si být jistý, že /opt bude vždy k dispozici (správně namontovaný), jednoduše jsem do svého .profilu vložil následující
[ -x /opt/bin/bash ] && exec /opt/bin/bash
zatímco /etc/passwd obsahuje /bin/ash jako shell.