1 #include "SshSetupHandler.hpp"
2
3 #include <sys/wait.h>
4
5 namespace et {
genRandom(int len)6 string genRandom(int len) {
7 static const char alphanum[] =
8 "0123456789"
9 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
10 "abcdefghijklmnopqrstuvwxyz";
11 string s(len, '\0');
12
13 int randomFd = ::open("/dev/urandom", O_RDONLY);
14 FATAL_FAIL(randomFd);
15 for (int i = 0; i < len; ++i) {
16 uint32_t randNum;
17 ssize_t rc = ::read(randomFd, &randNum, sizeof(uint32_t));
18 FATAL_FAIL(rc);
19 s[i] = alphanum[randNum % (sizeof(alphanum) - 1)];
20 }
21 close(randomFd);
22
23 return s;
24 }
25
genCommand(const string & passkey,const string & id,const string & clientTerm,const string & user,bool kill,const string & command_prefix,const string & options)26 string genCommand(const string &passkey, const string &id,
27 const string &clientTerm, const string &user, bool kill,
28 const string &command_prefix, const string &options) {
29 string SSH_SCRIPT_PREFIX{
30 "SERVER_TMP_DIR=${TMPDIR:-${TMP:-${TEMP:-/tmp}}};"
31 "TMPFILE=$(mktemp $SERVER_TMP_DIR/et-server.XXXXXXXXXXXX);"
32 "PASSKEY=" +
33 passkey +
34 ";"
35 "ID=" +
36 id +
37 ";"
38 "printf \"%s/%s\\n\" \"$ID\" \"$PASSKEY\" > \"${TMPFILE}\";"
39 "export TERM=" +
40 clientTerm + ";"};
41
42 // Kill old ET sessions of the user
43 if (kill && user != "root") {
44 SSH_SCRIPT_PREFIX =
45 "if [ -x \"$(command -v etterminal)\" ]; then pkill etterminal -u " +
46 user + "; else pkill etserver -u " + user + "; fi;" + SSH_SCRIPT_PREFIX;
47 }
48
49 string COMMAND{"if [ -x \"$(command -v etterminal)\" ]; then " +
50 command_prefix + " etterminal " + options + "; else " +
51 command_prefix + " etserver " + options + ";fi;true"};
52
53 return SSH_SCRIPT_PREFIX + COMMAND;
54 }
55
SetupSsh(string user,string host,string host_alias,int port,string jumphost,int jport,bool kill,int vlevel,string cmd_prefix,bool noratelimit)56 string SshSetupHandler::SetupSsh(string user, string host, string host_alias,
57 int port, string jumphost, int jport,
58 bool kill, int vlevel, string cmd_prefix,
59 bool noratelimit) {
60 string clientTerm("xterm-256color");
61 auto envString = getenv("TERM");
62 if (envString != NULL) {
63 // Default to xterm-256color
64 clientTerm = envString;
65 }
66 string passkey = genRandom(32);
67 string id = genRandom(16);
68 string cmdoptions{"--idpasskeyfile=\"${TMPFILE}\" --v=" +
69 std::to_string(vlevel)};
70
71 if (noratelimit) {
72 cmdoptions += " --noratelimit";
73 }
74
75 string SSH_SCRIPT_DST =
76 genCommand(passkey, id, clientTerm, user, kill, cmd_prefix, cmdoptions);
77
78 int link_client[2];
79 char buf_client[4096];
80 if (pipe(link_client) == -1) {
81 LOG(FATAL) << "pipe";
82 exit(1);
83 }
84
85 pid_t pid = fork();
86 string SSH_USER_PREFIX = "";
87 if (!user.empty()) {
88 SSH_USER_PREFIX += user + "@";
89 }
90 if (!pid) {
91 // start etserver daemon on dst.
92 dup2(link_client[1], 1);
93 close(link_client[0]);
94 close(link_client[1]);
95 // run the command in interactive mode
96 SSH_SCRIPT_DST = "$SHELL -lc \'" + SSH_SCRIPT_DST + "\'";
97 if (!jumphost.empty()) {
98 execlp("ssh", "ssh", "-J", (SSH_USER_PREFIX + jumphost).c_str(),
99 (SSH_USER_PREFIX + host_alias).c_str(), (SSH_SCRIPT_DST).c_str(),
100 NULL);
101 } else {
102 execlp("ssh", "ssh", (SSH_USER_PREFIX + host_alias).c_str(),
103 SSH_SCRIPT_DST.c_str(), NULL);
104 }
105
106 LOG(INFO) << "execl error";
107 exit(1);
108 } else if (pid < 0) {
109 LOG(INFO) << "Failed to fork";
110 exit(1);
111 } else {
112 close(link_client[1]);
113 wait(NULL);
114 int nbytes = read(link_client[0], buf_client, sizeof(buf_client));
115 try {
116 if (nbytes <= 0) {
117 // Ssh failed
118 cout << "Error starting ET process through ssh, please make sure your "
119 "ssh works first"
120 << endl;
121 exit(1);
122 }
123 if (split(string(buf_client), ':').size() != 2 ||
124 split(string(buf_client), ':')[0] != "IDPASSKEY") {
125 // Returned value not start with "IDPASSKEY:"
126 cout << "Error in authentication with etserver: " << buf_client
127 << ", please make sure you don't print anything in server's "
128 ".bashrc/.zshrc"
129 << endl;
130 exit(1);
131 }
132 auto idpasskey = split(string(buf_client), ':')[1];
133 idpasskey = idpasskey.substr(0, 16 + 1 + 32);
134 auto idpasskey_splited = split(idpasskey, '/');
135 string returned_id = idpasskey_splited[0];
136 string returned_passkey = idpasskey_splited[1];
137 if (returned_id == id && returned_passkey == passkey) {
138 LOG(INFO) << "etserver started";
139 } else {
140 LOG(FATAL) << "client/server idpasskey doesn't match: " << id
141 << " != " << returned_id << " or " << passkey
142 << " != " << returned_passkey;
143 }
144 } catch (const runtime_error &err) {
145 cout << "Error initializing connection" << err.what() << endl;
146 }
147 // start jumpclient daemon on jumphost.
148 if (!jumphost.empty()) {
149 /* If jumphost is set, we need to pass dst host and port to jumphost
150 * and connect to jumphost here */
151 int link_jump[2];
152 char buf_jump[4096];
153 if (pipe(link_jump) == -1) {
154 LOG(FATAL) << "pipe";
155 exit(1);
156 }
157 pid_t pid_jump = fork();
158 if (pid_jump < 0) {
159 LOG(FATAL) << "Failed to fork";
160 exit(1);
161 } else if (pid_jump == 0) {
162 dup2(link_jump[1], 1);
163 close(link_jump[0]);
164 close(link_jump[1]);
165 string jump_cmdoptions = cmdoptions + " --jump --dsthost=" + host +
166 " --dstport=" + to_string(port);
167 string SSH_SCRIPT_JUMP = genCommand(passkey, id, clientTerm, user, kill,
168 cmd_prefix, jump_cmdoptions);
169 // start command in interactive mode
170 SSH_SCRIPT_JUMP = "$SHELL -lc \'" + SSH_SCRIPT_JUMP + "\'";
171 execlp("ssh", "ssh", jumphost.c_str(), SSH_SCRIPT_JUMP.c_str(), NULL);
172 } else {
173 close(link_jump[1]);
174 wait(NULL);
175 int nbytes = read(link_jump[0], buf_jump, sizeof(buf_jump));
176 if (nbytes <= 0) {
177 // At this point "ssh -J jumphost dst" already works.
178 cout << "etserver jumpclient failed to start" << endl;
179 exit(1);
180 }
181 try {
182 auto idpasskey = split(string(buf_jump), ':')[1];
183 idpasskey.erase(idpasskey.find_last_not_of(" \n\r\t") + 1);
184 idpasskey = idpasskey.substr(0, 16 + 1 + 32);
185 auto idpasskey_splited = split(idpasskey, '/');
186 string returned_id = idpasskey_splited[0];
187 string returned_passkey = idpasskey_splited[1];
188 if (returned_id == id && returned_passkey == passkey) {
189 LOG(INFO) << "jump client started.";
190 } else {
191 LOG(FATAL) << "client/server idpasskey doesn't match: " << id
192 << " != " << returned_id << " or " << passkey
193 << " != " << returned_passkey;
194 }
195 } catch (const runtime_error &err) {
196 cout << "Error initializing connection" << err.what() << endl;
197 }
198 }
199 }
200 }
201 return id + "/" + passkey;
202 }
203 } // namespace et
204