ssh-socks-restart example: factor out cfg_gotosection
[rofl0r-rocksock.git] / examples / ssh-socks-restart.c
blobf75ffdb2d14f36e58edb1221b017a3cb1b754a49
1 /*
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:
17 [default]
18 # parameters that apply to all configurations
19 SOCKSIF=127.0.0.1:8080
21 [server1]
22 KEY=/path/to/my_rsa_key
23 LOGIN=user@server1.mynet.com
25 [server2]
26 KEY=/path/to/my_ed25519_key
27 LOGIN=joe@server2.mynet.com
28 PORT=222
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
42 configuration item,
43 i.e. ./ssh-socks-restart my.conf server1
46 #include "../rocksock.h"
47 #include <sys/wait.h>
48 #include <unistd.h>
49 #include <stdlib.h>
50 #include <stdio.h>
51 #include <string.h>
52 #include <signal.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))
58 return 1;
60 return 0;
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] == '=') {
69 size_t x = l;
70 while(buf[++x] != '\n');
71 buf[x] = 0;
72 memmove(buf, buf + l + 1, x - l);
73 return buf;
74 } else if(buf[0] == '[') break;
76 *buf = 0;
77 return 0;
80 static char *try_cfg_getstr(FILE *f, const char* section, const char *key, char* buf, size_t bufsize) {
81 char *p;
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");
88 FILE* f;
89 if(!(f = fopen(fn, "r"))) {
90 printf("error: config file %s not found\n", fn);
91 return 0;
93 int err = 0;
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++;
98 if(err) {
99 printf("error: SOCKSIF, KEY or LOGIN line missing in config\n");
100 fclose(f);
101 return 0;
103 if(!try_cfg_getstr(f, section, "PORT", port, 128)) strcpy(port, "22");
104 try_cfg_getstr(f, section, "EXTRA", extra, 1024);
106 fclose(f);
107 return 1;
110 static char** build_argv(char* key, char* login, char* port, char* socksif, char* extra) {
111 size_t e_items = 0, el = 0;
112 char *p = extra;
113 if(*p) e_items++;
114 while(*p) {
115 if(*p == ' ') e_items++;
116 p++;
117 el++;
119 size_t asz = (1+1+2+2+2+1+e_items+1)*sizeof(char*);
120 char **res = malloc(asz+el+1);
121 if(!res) return 0;
122 char *ecopy = ((char*)(void*)res)+asz;
123 memcpy(ecopy, extra, el+1);
124 char **ret = res;
125 *(res++)="ssh";
126 *(res++)=login;
127 *(res++)="-i";
128 *(res++)=key;
129 *(res++)="-p";
130 *(res++)=port;
131 *(res++)="-D";
132 *(res++)=socksif;
133 *(res++)="-N";
134 p = ecopy;
135 char *s = ecopy;
136 while(*p) {
137 if(*p == ' ') {
138 *p = 0;
139 *(res++)=s;
140 s=p+1;
142 p++;
144 if(s < p) *(res++)=s;
145 *res=0;
146 return ret;
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);
153 return 1;
156 #define PROCWAIT_SEC 10
157 #define TIMEOUT_SEC 20
159 static pid_t child = 0;
161 void sighandler(int sig) {
162 if(child) {
163 int foo;
164 kill(child, sig);
165 waitpid(child, &foo, 0);
167 _exit(1);
170 int main(int argc, char**argv) {
171 if(argc != 3) return syntax(argv[0]);
172 int fails = 0;
173 signal(SIGTERM, sighandler);
174 signal(SIGINT, sighandler);
175 while(1) {
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);
181 if(!nargs) {
182 dprintf(2, "out of memory, retrying later...\n");
183 e_cont:
184 sleep(PROCWAIT_SEC);
185 continue;
187 if(execvp("ssh", nargs)) {
188 perror("exec");
189 free(nargs);
190 goto e_cont;
193 dprintf(2, "%d\n", (int) child);
194 sleep(PROCWAIT_SEC);
196 int connected = 0;
197 while(1) {
198 int ret, loc;
199 ret = waitpid(child, &loc, WNOHANG);
200 dprintf(2, "got waitpid result %d, stat %d\n", ret, loc);
201 if(ret == child) {
202 dprintf(2, "child == ret, break\n");
203 break;
205 sleep(connected ? PROCWAIT_SEC : 2);
206 rocksock rs, *r = &rs;
207 rs_proxy proxies[1];
208 rocksock_init(r, proxies);
209 //rocksock_set_timeout(r, (TIMEOUT_SEC / (fails+1)) * 1000);
210 rocksock_set_timeout(r, (TIMEOUT_SEC / 1) * 1000);
211 char socksbuf[128];
212 strcpy(socksbuf, socksif);
213 char *p = strchr(socksbuf, ':');
214 *p = 0;
215 rocksock_add_proxy(r, RS_PT_SOCKS5, socksbuf, atoi(p+1), 0, 0);
216 static const char* testservers[] = {
217 "google.com",
218 "4.68.80.110" /*www.level3.net*/,
219 "msn.com",
220 "15.48.80.55"/*redirect.hp.com*/,
221 "cnn.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);
230 rocksock_clear(r);
231 if(ret) {
232 fails++;
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);
238 child = 0;
239 fails = 0;
240 break;
242 } else {
243 dprintf(2, "success.\n");
244 fails = 0;
245 connected = 1;
247 sleep(TIMEOUT_SEC / (fails+1));
249 sleep(1);