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