Initial revision
[riece] / dcc / dcc.c
1 /* dcc.c
2  * Copyright (C) 1998-2002  Daiki Ueno
3  *
4  * This file is part of Liece.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  */
20
21 #include <sys/types.h>
22 #include <sys/time.h>
23 #include <sys/socket.h>
24 #include <sys/file.h>
25 #include <sys/ioctl.h>
26 #include <sys/stat.h>
27 #include <sys/time.h>
28 #include <netdb.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <signal.h>
32 #include <fcntl.h>
33 #include <netinet/in.h>
34 #define _GNU_SOURCE
35 #include <getopt.h>
36
37 #ifndef MAXHOSTNAMELEN
38 # define MAXHOSTNAMELEN 31
39 #endif
40
41 #ifdef HAVE_SYS_SELECT_H
42 # include <sys/select.h>
43 #endif
44
45 #ifdef HAVE_MEMMOVE
46 # ifdef HAVE_LIBGEN_H
47 #  include <libgen.h>
48 #  ifdef basename
49 #   undef basename
50 #  endif
51 # endif
52 # include <string.h>
53 #else
54 # define memmove(x,y,z) bcopy((y), (x), (z))
55 #endif
56
57 #ifndef HAVE_BASENAME
58 # define basename(path) (rindex((path), '/') + 1)
59 #endif
60
61 static void usage();
62 static int prepare_listen_port();
63 static int prepare_connect_port();
64
65 static int receive_file();
66 static int send_file();
67 static int select_loop();
68 static int chat_listen();
69 static int chat_connect();
70
71 static u_long primary_address_of();
72 static u_long extract_addr_of_string();
73 static u_long get_address_externally();
74
75 static char *progname;
76
77 void version () {
78         printf("%s (Liece) 1.4.0\n"
79                                  "Copyright (C) 1998, 1999 Daiki Ueno\n"
80                                  "This is free software; see the source for copying conditions.  There is NO\n"
81                                  "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", 
82                                  progname);
83 }
84
85 void usage() {
86   printf("Usage: %s [global-options] command [command-options-and-arguments]\n"
87                                  "where global-options are -v, -h, etc.\n"
88                                  "where command is one of send, receive, chat, resolve.\n"
89                                  "where command-options-and-arguments depend on the specific command.\n\n"
90                                  "send <host> <port> <filename>\n"
91                                  "receive <host> <port> <size> <filename>\n"
92                                  "chat listen <port>\n"
93                                  "chat connect <host> <port>\n"
94                                  "resolve [hosts ...]\n",
95                                  progname);
96 }
97
98 int prepare_listen_port (int ip_port) {
99   int sock, tries;
100   int opt = 1;
101   static struct sockaddr_in server;
102
103   
104   if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
105     perror("opening stream socket");
106     exit(1);
107   }
108
109 #ifdef SO_REUSEADDR
110   if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 
111                  (char *)&opt, sizeof (opt)) < 0) {
112     perror ("setsockopt SO_REUSEADDR");
113   }
114 #endif
115
116   /* Bind a port to listen for new connections */
117
118   server.sin_family = AF_INET;
119   server.sin_addr.s_addr = INADDR_ANY;
120   server.sin_port = htons (ip_port);
121   for (tries = 0; tries < 10; tries++) {
122     if (bind (sock, (struct sockaddr *) &server, sizeof (server))) {
123       if (tries >= 9) {
124         perror ("binding stream socket");
125         exit (1);
126       }
127       perror ("binding stream socket. retry in 20 seconds");
128       sleep (20);               /* wait 20 seconds and try again */
129     } else
130       break;
131   }
132   listen (sock, 64);
133   return (sock);
134 }
135
136 u_long get_address_externally(char *ircserver) {
137   int i, len, dummy;
138   u_long addr;
139   struct hostent *hp;
140   struct sockaddr_in server, client;
141
142   addr = 0xc6290004;                          /* dummy addr --- rootA */
143   if (ircserver && (hp = gethostbyname(ircserver)) != NULL) {
144     addr = ntohl(((struct in_addr *)hp->h_addr_list[0])->s_addr);
145   }
146   if ((dummy = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
147     perror("opening stream socket");
148     return -1;
149   }
150   server.sin_family = AF_INET;
151   server.sin_addr.s_addr = htonl(addr);
152   server.sin_port = htons(7);                 /* dummy port --- echo */
153   for (i = 0; i < 8; i++) {
154     server.sin_zero[i] = 0;
155   }
156   if (connect(dummy, (struct sockaddr *)&server, sizeof(server)) < 0) {
157     perror ("connecting remote socket");
158     return -1;
159   }
160   len = sizeof(client);
161   if (getsockname(dummy, (struct sockaddr *)&client, &len) < 0) 
162     return -1;
163   close(dummy);
164   return ntohl(client.sin_addr.s_addr);
165 }
166
167 \f
168 /*
169  * send_file(int port, char *ifile)
170  * listens to connections to port, and when connection established
171  * sends ifile to that socket
172  */
173 int send_file (int port, char *ifile) {
174   int sock, ifd, ofd, len;
175   u_long addr, bytessent = 0;
176   char buf[ BUFSIZ * 8 ];
177   fd_set readfds, writefds, fdset;
178   struct stat statbuf;
179   char namebuf[ MAXHOSTNAMELEN ];
180   struct hostent *hp;
181   struct sockaddr_in sin;
182
183   if ((ifd = open (ifile, O_RDONLY)) < 0) { 
184     /* error in opening file to send */
185     close(ofd);
186     return 1;
187   }
188
189   gethostname(namebuf, sizeof (namebuf));
190   fstat (ifd, &statbuf);
191
192   sock = prepare_listen_port(port);
193   len = sizeof (struct sockaddr_in);
194   if (getsockname(sock, (struct sockaddr *)&sin, &len) == 0) 
195     port = ntohs(sin.sin_port);
196  
197         if ((addr = get_address_externally (NULL)) < 0) {
198                 gethostname(namebuf, sizeof (namebuf));
199                 if (hp = gethostbyname(namebuf)) 
200                         addr = ((struct in_addr *) (hp->h_addr_list)[0])->s_addr;
201                 else
202                         return 2;
203         }
204
205         printf ("DCC send %s %d %u %d\n", ifile, port, addr, statbuf.st_size);
206   
207   ofd = accept(sock, (struct sockaddr *) 0, (int *) 0);
208   
209   while ((len = read (ifd, buf, sizeof (buf))) > 0) {
210     write (ofd, buf, len);      
211     bytessent += len;
212     while ((len = read (ofd, buf, sizeof (u_long))) &&
213            ntohl (*(u_long *) buf) != bytessent);
214   }
215   close (ofd);
216   close (ifd);
217   printf ("*** DCC file %s sent\n", ifile);
218
219   return 0;
220 }
221
222 /*
223  * receive_file(u_long host, int port, char *ifile)
224  * connects to (host,port) and reads everything send from there
225  * for every packet received gives back how much actually got
226  * puts everything in ifile
227  */
228 int receive_file (u_long host, int port, int size, char *ifile) {
229   int sock, ifd, ofd, len, bytesreceived = 0, toread, prev = 0;
230   char buf[ BUFSIZ * 8 ];
231   fd_set readfds, writefds, fdset;
232   u_long netsize;
233     
234   if ((ofd = open(ifile, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
235     fprintf(stderr, "open: opening file: %s\n", ifile);
236     return 1;
237   }
238   ifd = prepare_connect_port (host, port);
239   if ((toread = sizeof (buf)) > size)
240     toread = size;
241   while (bytesreceived < size && (len = read (ifd, buf, toread)) > 0) {
242     write (ofd, buf, len);
243     bytesreceived += len;
244     netsize = htonl (bytesreceived);
245     lseek (ifd, 0, 2);
246     write (ifd, &netsize, 4);
247     lseek (ifd, 0, 2);
248     if (toread > size - bytesreceived)
249       toread = size - bytesreceived;
250     if (bytesreceived - prev > size / 5) {
251       printf ("DCC %s %d%% (%d/%d bytes) received\n", ifile,
252               100 * bytesreceived / size, bytesreceived, size);
253       prev = bytesreceived;
254     }
255   }
256   printf ("*** DCC file %s received\n", ifile);
257   close (ifd);
258   close (ofd);
259
260   return 0;
261 }
262
263 /*
264  * select_loop(int sfd)
265  * listens fd given, reads stdin and sends it to socket 
266  * anything read from socket is send to stdout
267  */
268 int select_loop (int sfd) {
269   int ofd, len, bytesreceived = 0;
270   char buf[ BUFSIZ * 8 ];
271   fd_set readfds, writefds, fdset;
272
273   for (;;) {
274     FD_ZERO (&readfds);
275     FD_SET (sfd, &readfds);
276     FD_SET (0, &readfds);
277     if (select (32, &readfds, 0, 0, 0) < 0) {
278       perror ("select");
279       close (sfd);
280       return 1;
281     }
282         
283     if (FD_ISSET (sfd, &readfds)) {
284       if ((len = read(sfd, buf, sizeof (buf))) == 0) {
285         close (sfd);
286         return 0;
287       }
288       write (1, buf, len);
289       FD_CLR (sfd, &readfds);
290     }
291     if (FD_ISSET (0, &readfds)) {
292       if ((len = read (0, buf, sizeof (buf))) == 0) {
293         close (sfd);
294         return 0;
295       }
296       write(sfd, buf, len);
297       FD_CLR (ofd, &readfds);
298     }
299   }
300 }
301
302 int prepare_connect_port (u_long host, int port) {
303   int sock;
304   static struct hostent *hp;
305   static struct sockaddr_in server;
306     
307   sock = socket (AF_INET, SOCK_STREAM, 0);
308   if (sock < 0) {
309     perror ("opening stream socket");
310     exit (1);
311   }
312   server.sin_family = AF_INET;
313   
314   server.sin_addr.s_addr = ntohl (host);
315   server.sin_port = htons (port);
316     
317   if (connect(sock, (struct sockaddr *) &server, sizeof (server)) < 0) {
318     perror ("connecting remote socket");
319     return 0;
320   }
321   
322   return sock;
323 }
324
325 u_long extract_addr_of_string (char *str) {
326   u_long result = 0;
327
328 #ifndef HAVE_STRTOUL
329   while (*str++) 
330     result = result * 10 + *str - '0';
331 #else /* !HAVE_STRTOUL */
332   result = strtoul(str, NULL, 10);
333 #endif /* HAVE_STRTOUL */
334   return result;
335 }
336
337 u_long primary_address_of (char *host) {   
338   struct hostent *hp;
339   u_long addr;
340   
341   if ((hp = gethostbyname(host)) == NULL)
342     addr = inet_addr(host);
343   else
344     memmove(&addr, hp->h_addr_list[ 0 ], 4);
345   
346   return ntohl(addr);
347 }
348
349 int chat_listen(int port) {
350   struct sockaddr_in sin;
351   struct hostent *hp;
352         u_long addr;
353   int sock, len;
354   char namebuf[ MAXHOSTNAMELEN ];
355     
356   sock = prepare_listen_port (port);
357   
358   len = sizeof (struct sockaddr_in);
359   if (getsockname(sock, (struct sockaddr *)&sin, &len) == 0) 
360     port = ntohs(sin.sin_port);
361
362         if ((addr = get_address_externally (NULL)) < 0) {
363                 gethostname(namebuf, sizeof (namebuf));
364                 if (hp = gethostbyname(namebuf)) 
365                         addr = ((struct in_addr *) (hp->h_addr_list)[0])->s_addr;
366                 else
367                         return 2;
368         }
369
370         printf("DCC chat %u %d\n", addr, port);
371   
372   if ((sock = accept(sock, (struct sockaddr *) 0, (int *) 0)) > -1) {
373                 printf("DCC chat established\n");
374     return select_loop(sock);
375         }
376   
377   return 1;
378 }
379
380 int chat_connect(u_long host, int port) {
381   int sock;
382   
383   if ((sock = prepare_connect_port(host, port)) > -1) {
384     printf("DCC chat established\n");
385     return select_loop(sock);
386   }
387   
388   return 1;
389 }
390
391 \f
392 int main (int argc, char **argv) {
393   char *host = "localhost";
394   char *action;
395   int c, status = 0;
396
397   progname = (char *)basename(argv[ 0 ]);
398
399         while (1)       {
400                 int this_option_optind = optind ? optind : 1;
401                 int option_index = 0;
402                 static struct option long_options[] =   {
403                         {"version", 0, 0, 'v'},
404                         {"help", 0, 0, 'h'},
405                         {0, 0, 0, 0}
406                 };
407                         
408                 c = getopt_long (argc, argv, "vh", long_options, &option_index);
409                 if (c == -1)
410                         break;
411                 
412                 switch (c) {
413                 case 'v':
414                         version();
415                         exit(1);
416                         break;
417                 case 'h':
418                         usage();
419                         exit(1);
420                         break;
421                 default:
422                         break;
423                 }
424         }
425
426   if (argc > 1) {
427     action = argv[ 1 ];
428   } else {
429     usage();
430     exit(1);
431   }
432
433   if (!strcmp(action, "resolve")) {
434     if (argc < 3) {
435       usage();
436       exit(1);
437     } else {
438       u_long i, addr;
439       for (i = 2; i < argc; i++) {
440         addr = primary_address_of(argv[i]);
441         if (addr != -1)
442           printf("%u\n", addr);
443         else
444           printf("0\n");
445       }
446       status = 0;
447     }
448   }
449
450   if (!strcmp(action, "send")) {
451     if (argc != 4) {
452       usage();
453       exit(1);
454     }
455     status = send_file (atoi(argv[ 2 ]), argv[ 3 ]);
456   } else if (!strcmp(action, "receive")) {
457     if (argc != 6) {
458       usage();
459       exit(1);
460     }
461     status = 
462       receive_file (extract_addr_of_string(argv[ 2 ]),
463                     atoi(argv[ 3 ]), atoi(argv[ 4 ]), argv[ 5 ]);
464   } else if (!strcmp(action, "chat")) {
465     if (argc > 3) {
466       if (!strcmp(argv[ 2 ], "listen")) {
467         if (argc != 4) {
468           usage();
469           exit(1);
470         }
471         status = chat_listen(atoi(argv[ 3 ]));
472       } else if (!strcmp(argv[ 2 ], "connect")) {
473         if (argc != 5) {
474           usage();
475           exit(1);
476         }
477         status = chat_connect(extract_addr_of_string(argv[ 3 ]), 
478                               atoi(argv[ 4 ]));
479       } else {
480         usage();
481         exit(1);
482       } 
483     }
484   } else {
485     usage();
486     exit(1);
487   }
488
489   return status;
490 }
491
492 /*
493  * Local variables:
494  *  compile-command: "gcc -DHAVE_STRTOUL -Wall -O6 -o dcc dcc.c"
495  *  c-indent-level: 2
496  *  c-basic-offset: 2
497  *  tab-width: 2
498  * End:
499  */