libt2n: (gerd) doxygenize
[libt2n] / src / socket_server.cpp
1 /***************************************************************************
2  *   Copyright (C) 2006 by Gerd v. Egidy                                   *
3  *   gve@intra2net.com                                                     *
4  *                                                                         *
5  *   This library is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU Lesser General Public License version   *
7  *   2.1 as published by the Free Software Foundation.                     *
8  *                                                                         *
9  *   This library is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU Lesser General Public License for more details.                   *
13  *                                                                         *
14  *   You should have received a copy of the GNU Lesser General Public      *
15  *   License along with this program; if not, write to the                 *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19
20 #include <stdio.h>
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <sys/time.h>
28 #include <arpa/inet.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <fcntl.h>
32 #include <time.h>
33 #include <pwd.h>
34 #include <grp.h>
35
36 #include <sstream>
37
38 #include "socket_server.hxx"
39 #include "t2n_exception.hxx"
40
41 using namespace std;
42
43 namespace libt2n
44 {
45
46 /** @brief create a new tcp-based server
47     @param port tcp port you want to listen on
48     @param ip the local ip you want to listen on. "0.0.0.0" means all local ips (default).
49 */
50 socket_server::socket_server(int port, const std::string& ip)
51     : server(), socket_handler(0,tcp_s)
52 {
53     // TODO
54 }
55
56 /** @brief create a new unix-socked-based server
57     @param path path of the socket
58     @param filemode permissions you want to open the socket with
59     @param user local username for the socket
60     @param group local groupname for the socket
61 */
62 socket_server::socket_server(const std::string& path, mode_t filemode, const std::string& user, const std::string& group)
63     : server(), socket_handler(0,unix_s)
64 {
65     unix_path=path;
66
67     /* Create the socket. */
68     sock = socket (PF_UNIX, SOCK_STREAM, 0);
69     if (sock < 0)
70     {
71         string err="error opening socket: ";
72         err+=strerror(errno);
73         log(error, err);
74         throw t2n_server_error(err);
75     }
76
77     set_socket_options(sock);
78
79     /* Give the socket a name. */
80     struct sockaddr_un unix_name;
81     unix_name.sun_family = AF_UNIX;
82     strncpy (unix_name.sun_path, unix_path.c_str(),sizeof(unix_name.sun_path));
83
84     /* just to make sure there is no other socket file */
85     unlink (unix_name.sun_path);
86
87     if (bind (sock, (struct sockaddr *) &unix_name, sizeof (unix_name)) < 0)
88     {
89         string err="error binding socket: ";
90         err+=strerror(errno);
91         log(error, err);
92         throw t2n_server_error(err);
93     }
94
95     /* change permissions */
96     if (chmod (unix_name.sun_path, filemode) != 0) 
97     {
98         string err="error changing permission: ";
99         err+=strerror(errno);
100         log(error, err);
101         throw t2n_server_error(err);
102     }
103
104     if (!user.empty() && !group.empty())
105     {
106         // TODO maybe use current user/group if one of them is empty
107
108         struct passwd *socket_user = getpwnam (user.c_str());
109         if (socket_user == NULL) 
110         {
111             string err="error getting socket user: ";
112             err+=strerror(errno);
113             log(error, err);
114             throw t2n_server_error(err);
115         }
116     
117         struct group *socket_group = getgrnam (group.c_str());
118         if (socket_group == NULL) 
119         {
120             string err="error getting socket group: ";
121             err+=strerror(errno);
122             log(error, err);
123             throw t2n_server_error(err);
124         }
125     
126         if (chown (unix_name.sun_path, socket_user->pw_uid, socket_group->gr_gid) != 0) 
127         {
128             string err="error changing socket ownership: ";
129             err+=strerror(errno);
130             log(error, err);
131             throw t2n_server_error(err);
132         }
133     }
134
135     if (listen (sock, 5) < 0)
136     {
137         string err="error listening to socket: ";
138         err+=strerror(errno);
139         log(error, err);
140         throw t2n_server_error(err);
141     }
142
143     /* clear & insert server sock into the fd_tab to prepare select */
144     FD_ZERO(&connection_set);
145     FD_SET (sock, &connection_set);
146 }
147
148 socket_server::~socket_server()
149 {
150     socket_handler::close();
151
152     if (get_type()==unix_s)
153         unlink(unix_path.c_str());
154 }
155
156 void socket_server::new_connection()
157 {
158     struct sockaddr_un clientname;
159
160     unsigned int size = sizeof (clientname);
161     int newsock = accept (sock,(struct sockaddr *) &clientname,&size);
162     if (newsock < 0)
163     {
164         if (errno == EAGAIN)
165         {
166             log(error, "accept error (EAGAIN): no connection waiting");
167             return;
168         }
169
170         /* default: break */
171         string err="error accepting connection: ";
172         err+=strerror(errno);
173         log(error, err);
174         throw t2n_server_error(err);
175     }
176
177     FD_SET (newsock, &connection_set);
178
179     socket_server_connection *nc=new socket_server_connection(newsock, get_type(), get_default_timeout());
180     nc->set_socket_options(newsock);
181
182     add_connection(nc);
183
184     return;
185 }
186
187 bool socket_server::fill_buffer(long long usec_timeout)
188 {
189     fd_set used_fdset=connection_set;
190
191     /* set timeout */
192     struct timeval tval;
193     struct timeval *timeout_ptr;
194
195     if (usec_timeout == -1)
196         timeout_ptr = NULL;
197     else
198     {
199         timeout_ptr = &tval;
200
201         // timeout von long long usec in int sec + int usec umrechnen
202         tval.tv_sec = usec_timeout / 1000000;
203         tval.tv_usec = usec_timeout % 1000000;
204     }
205
206     int ret=select (FD_SETSIZE, &used_fdset, NULL, NULL, timeout_ptr);
207
208     if (ret < 0)
209     {
210         if (errno == EINTR)
211         {
212             // select interrupted by signal
213             ret=0;
214         }
215         else
216         {
217             string err="select error: ";
218             err+=strerror(errno);
219             log(error, err);
220             throw t2n_server_error(err);
221         }
222     }
223
224     if (ret > 0)
225     {
226         // we have data pending
227
228         // check for new connection
229         if (FD_ISSET (sock, &used_fdset))
230         {
231             new_connection();
232         }
233
234         // check all connections for pending data
235         return fill_connection_buffers();
236     }
237
238     return false;
239 }
240
241 bool socket_server::fill_connection_buffers()
242 {
243     bool data_found;
244
245     std::map<unsigned int, server_connection*>::iterator ie=connections.end();
246     for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie; i++)
247         if (!i->second->server_connection::is_closed())
248             if (i->second->fill_buffer(0))
249                 data_found=true;
250
251     return data_found;
252 }
253
254 void socket_server::remove_connection_socket(int sock)
255 {
256     FD_CLR(sock, &connection_set);
257 }
258
259 void socket_server_connection::log(log_level_values level, const char* message)
260 {
261     if(my_server)
262     {
263         ostringstream msg;
264         msg << "connection id " << get_id() << ": " << message;
265         my_server->log(level,msg.str().c_str());
266     }
267 }
268
269 void socket_server_connection::close()
270 {
271     if (!server_connection::is_closed())
272     {
273         socket_handler::close();
274         server_connection::close();
275     }
276
277     if (my_server)
278     {
279         dynamic_cast<socket_server*>(my_server)->remove_connection_socket(sock);
280     }
281 }
282
283 }