1 /*
2  *  tcpproxy
3  *
4  *  tcpproxy is a simple tcp connection proxy which combines the
5  *  features of rinetd and 6tunnel. tcpproxy supports IPv4 and
6  *  IPv6 and also supports connections from IPv6 to IPv4
7  *  endpoints and vice versa.
8  *
9  *
10  *  Copyright (C) 2010-2015 Christian Pointner <equinox@spreadspace.org>
11  *
12  *  This file is part of tcpproxy.
13  *
14  *  tcpproxy is free software: you can redistribute it and/or modify
15  *  it under the terms of the GNU General Public License as published by
16  *  the Free Software Foundation, either version 3 of the License, or
17  *  any later version.
18  *
19  *  tcpproxy is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *  GNU General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public License
25  *  along with tcpproxy. If not, see <http://www.gnu.org/licenses/>.
26  */
27 
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <sys/select.h>
33 #include <sys/types.h>
34 #include <unistd.h>
35 #include <signal.h>
36 
37 #include "datatypes.h"
38 #include "options.h"
39 #include "string_list.h"
40 #include "sig_handler.h"
41 #include "log.h"
42 #include "daemon.h"
43 
44 #include "listener.h"
45 #include "clients.h"
46 #include "cfg_parser.h"
47 
main_loop(options_t * opt,listeners_t * listeners)48 int main_loop(options_t* opt, listeners_t* listeners)
49 {
50   log_printf(INFO, "entering main loop");
51 
52   int sig_fd = signal_init();
53   if(sig_fd < 0)
54     return -1;
55 
56   clients_t clients;
57   int return_value = clients_init(&clients, opt->buffer_size_);
58 
59   while(!return_value) {
60     fd_set readfds, writefds;
61     FD_ZERO(&readfds);
62     FD_ZERO(&writefds);
63     FD_SET(sig_fd, &readfds);
64     int nfds = sig_fd;
65     listeners_read_fds(listeners, &readfds, &nfds);
66     clients_read_fds(&clients, &readfds, &nfds);
67     clients_write_fds(&clients, &writefds, &nfds);
68     int ret = select(nfds + 1, &readfds, &writefds, NULL, NULL);
69     if(ret == -1 && errno != EINTR) {
70       log_printf(ERROR, "select returned with error: %s", strerror(errno));
71       return_value = -1;
72       break;
73     }
74     if(!ret || ret == -1)
75       continue;
76 
77     if(FD_ISSET(sig_fd, &readfds)) {
78       return_value = signal_handle();
79       if(return_value == SIGINT || return_value == SIGQUIT || return_value == SIGTERM) break;
80       if(return_value == SIGHUP) {
81         if(opt->config_file_) {
82           log_printf(NOTICE, "re-reading config file: %s", opt->config_file_);
83           read_configfile(opt->config_file_, listeners);
84         } else
85           log_printf(NOTICE, "ignoring SIGHUP: no config file specified");
86 
87         return_value = 0;
88       } else if(return_value == SIGUSR1) {
89         listeners_print(listeners);
90       } else if(return_value == SIGUSR2) {
91         clients_print(&clients);
92       }
93     }
94 
95     return_value = listeners_handle_accept(listeners, &clients, &readfds);
96     if(return_value) break;
97 
98     return_value = clients_write(&clients, &writefds);
99     if(return_value) break;
100 
101     return_value = clients_read(&clients, &readfds);
102   }
103 
104   clients_clear(&clients);
105   signal_stop();
106   return return_value;
107 }
108 
main(int argc,char * argv[])109 int main(int argc, char* argv[])
110 {
111   log_init();
112 
113   options_t opt;
114   int ret = options_parse(&opt, argc, argv);
115   if(ret) {
116     if(ret > 0) {
117       fprintf(stderr, "syntax error near: %s\n\n", argv[ret]);
118     }
119     if(ret == -2) {
120       fprintf(stderr, "memory error on options_parse, exitting\n");
121     }
122     if(ret == -3) {
123       options_print_version();
124     }
125 
126     if(ret != -2 && ret != -3)
127       options_print_usage();
128 
129     if(ret == -1 || ret == -3)
130       ret = 0;
131 
132     options_clear(&opt);
133     log_close();
134     exit(ret);
135   }
136   slist_element_t* tmp = opt.log_targets_.first_;
137   while(tmp) {
138     ret = log_add_target(tmp->data_);
139     if(ret) {
140       switch(ret) {
141       case -2: fprintf(stderr, "memory error on log_add_target, exitting\n"); break;
142       case -3: fprintf(stderr, "unknown log target: '%s', exitting\n", (char*)(tmp->data_)); break;
143       case -4: fprintf(stderr, "this log target is only allowed once: '%s', exitting\n", (char*)(tmp->data_)); break;
144       default: fprintf(stderr, "syntax error near: '%s', exitting\n", (char*)(tmp->data_)); break;
145       }
146 
147       options_clear(&opt);
148       log_close();
149       exit(ret);
150     }
151     tmp = tmp->next_;
152   }
153 
154   log_printf(NOTICE, "just started...");
155   options_parse_post(&opt);
156 
157   listeners_t listeners;
158   ret = listeners_init(&listeners);
159   if(ret) {
160     options_clear(&opt);
161     log_close();
162     exit(-1);
163   }
164 
165   if(opt.local_port_) {
166     ret = listeners_add(&listeners, opt.local_addr_, opt.lresolv_type_, opt.local_port_, opt.remote_addr_, opt.rresolv_type_, opt.remote_port_, opt.source_addr_);
167     if(!ret) ret = listeners_update(&listeners);
168     if(ret) {
169       listeners_clear(&listeners);
170       options_clear(&opt);
171       log_close();
172       exit(-1);
173     }
174   } else {
175     ret = read_configfile(opt.config_file_, &listeners);
176     if(ret || !slist_length(&listeners)) {
177       if(!ret)
178         log_printf(ERROR, "no listeners defined in config file %s", opt.config_file_);
179       listeners_clear(&listeners);
180       options_clear(&opt);
181       log_close();
182       exit(-1);
183     }
184   }
185 
186   priv_info_t priv;
187   if(opt.username_)
188     if(priv_init(&priv, opt.username_, opt.groupname_)) {
189       listeners_clear(&listeners);
190       options_clear(&opt);
191       log_close();
192       exit(-1);
193     }
194 
195   FILE* pid_file = NULL;
196   if(opt.pid_file_) {
197     pid_file = fopen(opt.pid_file_, "w");
198     if(!pid_file) {
199       log_printf(WARNING, "unable to open pid file: %s", strerror(errno));
200     }
201   }
202 
203   if(opt.chroot_dir_)
204     if(do_chroot(opt.chroot_dir_)) {
205       listeners_clear(&listeners);
206       options_clear(&opt);
207       log_close();
208       exit(-1);
209     }
210   if(opt.username_)
211     if(priv_drop(&priv)) {
212       listeners_clear(&listeners);
213       options_clear(&opt);
214       log_close();
215       exit(-1);
216     }
217 
218   if(opt.daemonize_) {
219     pid_t oldpid = getpid();
220     daemonize();
221     log_printf(INFO, "running in background now (old pid: %d)", oldpid);
222   }
223 
224   if(pid_file) {
225     pid_t pid = getpid();
226     fprintf(pid_file, "%d", pid);
227     fclose(pid_file);
228   }
229 
230   ret = main_loop(&opt, &listeners);
231 
232   listeners_clear(&listeners);
233   options_clear(&opt);
234 
235   if(!ret)
236     log_printf(NOTICE, "normal shutdown");
237   else if(ret < 0)
238     log_printf(NOTICE, "shutdown after error");
239   else {
240     log_printf(NOTICE, "shutdown after signal");
241     log_close();
242     kill(getpid(), ret);
243   }
244 
245   log_close();
246 
247   return ret;
248 }
249