2 SSH proxy "daemon" (C) 2011-2016 rofl0r.
3 licensed under the MIT license.
5 starts ssh client with parameters taken from a config file, then
6 assures the connection is alive by doing cyclic connection checks
7 using the SOCKS proxy port requested from the SSH server.
8 if the connection is found dead, the ssh process is killed and
9 a new connection established.
10 the SOCKS proxy functionality is required and the server must be
11 configured to allow it.
12 additionally we require key-based authentication without user
13 interaction (i.e. ssh keys without password).
15 the config file has the following form:
18 # parameters that apply to all configurations
19 SOCKSIF=127.0.0.1:8080
22 KEY=/path/to/my_rsa_key
23 LOGIN=user@server1.mynet.com
26 KEY=/path/to/my_ed25519_key
27 LOGIN=joe@server2.mynet.com
29 EXTRA=-R 0.0.0.0:2222:127.0.0.1:22 -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
31 "EXTRA" is a field that allows you to specify additional
32 stuff to append to the ssh commandline.
34 (The example above creates a reverse tunnel to localhost's ssh port
35 and binds it on the remote server on port 2222.
36 Additionally it enables quiet mode and turns off known_hosts questions
37 and checks. this is critical if this runs on a remote host you have
38 no physical access to and rely on this program to work and not ask
39 questions if something in your setup changed.)
41 the program is started with the name of the config file and a
43 i.e. ./ssh-socks-restart my.conf server1
46 #include "../rocksock.h"
54 static int cfg_gotosection(FILE *f
, const char* section
, char *buf
, size_t bufsize
) {
55 size_t s
= strlen(section
);
56 while(fgets(buf
, bufsize
, f
)) {
57 if(buf
[0] == '[' && bufsize
> s
+2 && buf
[1+s
] == ']' && !strncmp(buf
+1,section
,s
))
63 static char* cfg_getstr(FILE *f
, const char* section
, const char *key
, char* buf
, size_t bufsize
) {
64 fseek(f
, 0, SEEK_SET
);
65 if(!cfg_gotosection(f
, section
, buf
, bufsize
)) return 0;
66 size_t l
= strlen(key
);
67 while(fgets(buf
, bufsize
, f
)) {
68 if(!strncmp(buf
, key
, l
) && buf
[l
] == '=') {
70 while(buf
[++x
] != '\n');
72 memmove(buf
, buf
+ l
+ 1, x
- l
);
74 } else if(buf
[0] == '[') break;
80 static char *try_cfg_getstr(FILE *f
, const char* section
, const char *key
, char* buf
, size_t bufsize
) {
82 if((p
= cfg_getstr(f
, section
, key
, buf
, bufsize
))) return p
;
83 else return cfg_getstr(f
, "default", key
, buf
, bufsize
);
86 static int read_config(const char* fn
, char *section
, char* key
, char* login
, char* port
, char* socksif
, char* extra
) {
87 printf("reading config...\n");
89 if(!(f
= fopen(fn
, "r"))) {
90 printf("error: config file %s not found\n", fn
);
94 if(getenv("SOCKSIF")) strcpy(socksif
, getenv("SOCKSIF"));
95 else if(!try_cfg_getstr(f
, section
, "SOCKSIF", socksif
, 128)) err
++;
96 if(!try_cfg_getstr(f
, section
, "KEY", key
, 128)) err
++;
97 if(!try_cfg_getstr(f
, section
, "LOGIN", login
, 128)) err
++;
99 printf("error: SOCKSIF, KEY or LOGIN line missing in config\n");
103 if(!try_cfg_getstr(f
, section
, "PORT", port
, 128)) strcpy(port
, "22");
104 try_cfg_getstr(f
, section
, "EXTRA", extra
, 1024);
110 static char** build_argv(char* key
, char* login
, char* port
, char* socksif
, char* extra
) {
111 size_t e_items
= 0, el
= 0;
115 if(*p
== ' ') e_items
++;
119 size_t asz
= (1+1+2+2+2+1+e_items
+1)*sizeof(char*);
120 char **res
= malloc(asz
+el
+1);
122 char *ecopy
= ((char*)(void*)res
)+asz
;
123 memcpy(ecopy
, extra
, el
+1);
144 if(s
< p
) *(res
++)=s
;
149 static int syntax(char* argv0
) {
150 printf("usage: %s configfile sectionname\n"
151 "establishes ssh connection with connectivity supervision.\n"
152 "read comment in source code for more info.\n", argv0
);
156 #define PROCWAIT_SEC 10
157 #define TIMEOUT_SEC 20
159 static pid_t child
= 0;
161 void sighandler(int sig
) {
165 waitpid(child
, &foo
, 0);
170 int main(int argc
, char**argv
) {
171 if(argc
!= 3) return syntax(argv
[0]);
173 signal(SIGTERM
, sighandler
);
174 signal(SIGINT
, sighandler
);
176 char key
[128], login
[128], port
[128], socksif
[128], extra
[1024];
177 if(!read_config(argv
[1], argv
[2], key
, login
, port
, socksif
, extra
)) return 1;
178 dprintf(2, "starting process...");
179 if(!(child
= fork())) {
180 char**nargs
=build_argv(key
, login
, port
, socksif
, extra
);
182 dprintf(2, "out of memory, retrying later...\n");
187 if(execvp("ssh", nargs
)) {
193 dprintf(2, "%d\n", (int) child
);
199 ret
= waitpid(child
, &loc
, WNOHANG
);
200 dprintf(2, "got waitpid result %d, stat %d\n", ret
, loc
);
202 dprintf(2, "child == ret, break\n");
205 sleep(connected
? PROCWAIT_SEC
: 2);
206 rocksock rs
, *r
= &rs
;
208 rocksock_init(r
, proxies
);
209 //rocksock_set_timeout(r, (TIMEOUT_SEC / (fails+1)) * 1000);
210 rocksock_set_timeout(r
, (TIMEOUT_SEC
/ 1) * 1000);
212 strcpy(socksbuf
, socksif
);
213 char *p
= strchr(socksbuf
, ':');
215 rocksock_add_proxy(r
, RS_PT_SOCKS5
, socksbuf
, atoi(p
+1), 0, 0);
216 static const char* testservers
[] = {
218 "4.68.80.110" /*www.level3.net*/,
220 "15.48.80.55"/*redirect.hp.com*/,
222 "18.7.27.14" /*libraries.mit.edu*/,
223 "38.100.128.10" /*www.psinet.com*/
225 static const unsigned srvcnt
= sizeof(testservers
) / sizeof(testservers
[0]);
226 static unsigned srvno
= 0;
227 dprintf(2, "connecting...\n");
228 ret
= rocksock_connect(r
, testservers
[srvno
++ % srvcnt
], 80, 0);
229 rocksock_disconnect(r
);
233 dprintf(2, "fail %d\n", fails
);
234 if(!connected
|| fails
> 3) {
235 dprintf(2, "connection failed, killing %d\n", (int) child
);
236 kill(child
, SIGKILL
);
237 ret
= waitpid(child
, &loc
, 0);
243 dprintf(2, "success.\n");
247 sleep(TIMEOUT_SEC
/ (fails
+1));