Trying to use a non-standard shell in your Synology DS212+ NAS may be an exercise of frustration.
I'm a bash-lover, and have a bunch of scripts to automate backups, so I wanted to set up default shell to /bin/bash.
It worked well with root, but when a regular user tries to log in, the connection is terminated with "Permission denied, please try again.".
Weird. Maybe /etc/shells? Nops. Maybe sshd_config? No. PAM modules, then? Also not. I even tried compiling bash from source (which took me a full day, just to get ARM GCC to cross compile properly). Nada.
In a moment of total despair, I tried setting my regular user to uid=0; if it's permissions, then this should work, right?
Sorry. Just uid=0 isn't enough. The user has to be called "root".
WHAAAT?
Thankfully, Synology uses a modified Linux distribution, so they publish the code under GPL.
After a couple of hours looking at the OpenSSH source code, things became pretty obvious. Here's the piece of code from their version of OpenSSH 5.8p1, file 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 */
...
}
And here is the IsAllowShell(pw):
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;
}
Basically synology created a heavily customized version of OpenSSH, including a handful of hacks - e.g., additional checking before accepting a login to see if the SSH service is enabled within the web interface, or stripping special chars (;, |, ') from rsync commands, and... wait for it... blocking anyone other than root or admin from using any shell other than /bin/sh or /bin/ash.
Once identified the cause, the fix was easy: just remove the call to IsAllowShell(). Cross-compile, upload to the NAS, reboot and voilĂ , regular users can now use bash normally.
The hardest part was to get cross-compiling working properly. If there's anyone interested, here's my modified version of the Makefile. This was tested with OpenSSH-5.8p1 from DSM4.1 source code, branch 2636, on models with Marvell Kirkwood mv6281/mv6282 CPU (like DS212+), under Ubuntu 12.10 x64.
Bottom line: Synology did exactly what companies should not do: deviate from standards, for no reason. The result is ugly and unmaintainable code, and creates all sorts of headaches for users.
Thankfully there's GPL to keep them open.