[script] Remove arbitrary limit on script line lengths
[people/dverkamp/gpxe.git] / contrib / p910nd-0.8 / p910nd.c
1 /*
2  *      Port 9100+n daemon
3  *      Accepts a connection from port 9100+n and copy stream to
4  *      /dev/lpn, where n = 0,1,2.
5  *
6  *      Run standalone as: p910nd [0|1|2]
7  *
8  *      Run under inetd as:
9  *      p910n stream tcp nowait root /usr/sbin/tcpd p910nd [0|1|2]
10  *       where p910n is an /etc/services entry for
11  *       port 9100, 9101 or 9102 as the case may be.
12  *       root can be replaced by any uid with rw permission on /dev/lpn
13  *
14  *      Port 9100+n will then be passively opened
15  *      n defaults to 0
16  *
17  *      Version 0.8
18  *      Allow specifying address to bind to
19  *
20  *      Version 0.7
21  *      Bidirectional data transfer
22  *
23  *      Version 0.6
24  *      Arne Bernin fixed some cast warnings, corrected the version number
25  *      and added a -v option to print the version.
26  *
27  *      Version 0.5
28  *      -DUSE_LIBWRAP and -lwrap enables hosts_access (tcpwrappers) checking.
29  *
30  *      Version 0.4
31  *      Ken Yap (ken_yap@users.sourceforge.net), April 2001
32  *      Placed under GPL.
33  *
34  *      Added -f switch to specify device which overrides /dev/lpn.
35  *      But number is still required get distinct ports and locks.
36  *
37  *      Added locking so that two invocations of the daemon under inetd
38  *      don't try to open the printer at the same time. This can happen
39  *      even if there is one host running clients because the previous
40  *      client can exit after it has sent all data but the printer has not
41  *      finished printing and inetd starts up a new daemon when the next
42  *      request comes in too soon.
43  *
44  *      Various things could be Linux specific. I don't
45  *      think there is much demand for this program outside of PCs,
46  *      but if you port it to other distributions or platforms,
47  *      I'd be happy to receive your patches.
48  */
49
50 #include        <unistd.h>
51 #include        <stdlib.h>
52 #include        <stdio.h>
53 #include        <getopt.h>
54 #include        <ctype.h>
55 #include        <string.h>
56 #include        <fcntl.h>
57 #include        <netdb.h>
58 #include        <syslog.h>
59 #include        <errno.h>
60 #include        <sys/types.h>
61 #include        <sys/time.h>
62 #include        <sys/resource.h>
63 #include        <sys/stat.h>
64 #include        <sys/socket.h>
65 #include        <netinet/in.h>
66 #include        <arpa/inet.h>
67
68 #ifdef  USE_LIBWRAP
69 #include        "tcpd.h"
70 int             allow_severity, deny_severity;
71 extern          int hosts_ctl(char *daemon, char *client_name,
72                 char *client_addr, char *client_user);
73 #endif
74
75 #define         BASEPORT        9100
76 #define         PIDFILE         "/var/run/p910%cd.pid"
77 #ifdef          LOCKFILE_DIR
78 #define         LOCKFILE        LOCKFILE_DIR "/p910%cd"
79 #else
80 #define         LOCKFILE        "/var/lock/subsys/p910%cd"
81 #endif
82 #define         PRINTERFILE     "/dev/lp%c"
83 #define         LOGOPTS         LOG_ERR
84
85 static char     *progname;
86 static char     version[] = "p910nd Version 0.8";
87 static int      lockfd = -1;
88 static char     *device = 0;
89 static int      bidir = 0;
90 static char     *bindaddr = 0;
91
92 void usage(void)
93 {
94         fprintf(stderr, "Usage: %s [-f device] [-i bindaddr] [-bv] [0|1|2]\n", progname);
95         exit(1);
96 }
97
98 void show_version (void)
99 {
100         fprintf(stdout, "%s \n", version);
101 }
102
103 FILE *open_printer(int lpnumber)
104 {
105         FILE            *f;
106         char            lpname[sizeof(PRINTERFILE)];
107
108 #ifdef  TESTING
109         (void)snprintf(lpname, sizeof(lpname), "/dev/tty");
110 #else
111         (void)snprintf(lpname, sizeof(lpname), PRINTERFILE, lpnumber);
112 #endif
113         if (device == 0)
114                 device = lpname;
115         if ((f = fopen(device, bidir ? "w+" : "w")) == NULL)
116         {
117                 syslog(LOGOPTS, "%s: %m\n", device);
118                 exit(1);
119         }
120         return (f);
121 }
122
123 int get_lock(int lpnumber)
124 {
125         char            lockname[sizeof(LOCKFILE)];
126         struct flock    lplock;
127
128         (void)snprintf(lockname, sizeof(lockname), LOCKFILE, lpnumber);
129         if ((lockfd = open(lockname, O_CREAT|O_RDWR)) < 0)
130         {
131                 syslog(LOGOPTS, "%s: %m\n", lockname);
132                 return (0);
133         }
134         memset(&lplock, 0, sizeof(lplock));
135         lplock.l_type = F_WRLCK;
136         lplock.l_pid = getpid();
137         if (fcntl(lockfd, F_SETLKW, &lplock) < 0)
138         {
139                 syslog(LOGOPTS, "%s: %m\n", lockname);
140                 return (0);
141         }
142         return (1);
143 }
144
145 void free_lock(void)
146 {
147         if (lockfd >= 0)
148                 (void)close(lockfd);
149 }
150
151 /* Copy network socket to FILE f until EOS */
152 int copy_stream(int fd, FILE *f)
153 {
154         int             nread;
155         char            buffer[8192];
156
157         if (bidir) {
158                 FILE    *nf;
159
160                 if ((nf = fdopen(fd, "w")) == NULL) {
161                         syslog(LOGOPTS, "fdopen: %m\n");
162                 }
163                 for (;;) {
164                         fd_set  readfds;
165                         int result;
166                         int maxfd = fileno(f) > fd ? fileno(f) : fd;
167                         FD_ZERO(&readfds);
168                         FD_SET(fileno(f), &readfds);
169                         FD_SET(fd, &readfds);
170                         result = select(maxfd + 1, &readfds, 0, 0, 0);
171                         if (result < 0)
172                                 return (result);
173                         if (result == 0)
174                                 continue;
175                         if (FD_ISSET(fd, &readfds)) {
176                                 nread = read(fd, buffer, sizeof(buffer));
177                                 if (nread <= 0)
178                                         break;
179                                 (void)fwrite(buffer, sizeof(char), nread, f);
180                         }
181                         if (FD_ISSET(fileno(f), &readfds)) {
182                                 nread = read(fileno(f), buffer, sizeof(buffer));
183                                 if (nread > 0 && nf != NULL) {
184                                         (void)fwrite(buffer, sizeof(char), nread, nf);
185                                         (void)fflush(nf);
186                                 }
187                         }
188                 }
189                 (void)fflush(f);
190                 (void)fclose(nf);
191                 return (0);
192         } else {
193                 while ((nread = read(fd, buffer, sizeof(buffer))) > 0)
194                         (void)fwrite(buffer, sizeof(char), nread, f);
195                 (void)fflush(f);
196                 return (nread);
197         }
198 }
199
200 void one_job(int lpnumber)
201 {
202         FILE            *f;
203         struct sockaddr_in      client;
204         socklen_t       clientlen = sizeof(client);
205
206         if (getpeername(0, (struct sockaddr*) &client, &clientlen) >= 0)
207                 syslog(LOGOPTS, "Connection from %s port %hu\n",
208                         inet_ntoa(client.sin_addr),
209                         ntohs(client.sin_port));
210         if (get_lock(lpnumber) == 0)
211                 return;
212         f = open_printer(lpnumber);
213         if (copy_stream(0, f) < 0)
214                 syslog(LOGOPTS, "copy_stream: %m\n");
215         fclose(f);
216         free_lock();
217 }
218
219 void server(int lpnumber)
220 {
221         struct rlimit   resourcelimit;
222 #ifdef  USE_GETPROTOBYNAME
223         struct protoent *proto;
224 #endif
225         int             netfd, fd, one = 1;
226         socklen_t       clientlen;
227         struct sockaddr_in      netaddr, client;
228         char            pidfilename[sizeof(PIDFILE)];
229         FILE            *f;
230         int             ipret;
231
232 #ifndef TESTING
233         switch (fork())
234         {
235         case -1:
236                 syslog(LOGOPTS, "fork: %m\n");
237                 exit (1);
238         case 0:         /* child */
239                 break;
240         default:        /* parent */
241                 exit(0);
242         }
243         /* Now in child process */
244         resourcelimit.rlim_max = 0;
245         if (getrlimit(RLIMIT_NOFILE, &resourcelimit) < 0)
246         {
247                 syslog(LOGOPTS, "getrlimit: %m\n");
248                 exit(1);
249         }
250         for (fd = 0; fd < resourcelimit.rlim_max; ++fd)
251                 (void)close(fd);
252         if (setsid() < 0)
253         {
254                 syslog(LOGOPTS, "setsid: %m\n");
255                 exit(1);
256         }
257         (void)chdir("/");
258         (void)umask(022);
259         fd = open("/dev/null", O_RDWR); /* stdin */
260         (void)dup(fd);                  /* stdout */
261         (void)dup(fd);                  /* stderr */
262         (void)snprintf(pidfilename, sizeof(pidfilename), PIDFILE, lpnumber);
263         if ((f = fopen(pidfilename, "w")) == NULL)
264         {
265                 syslog(LOGOPTS, "%s: %m\n", pidfilename);
266                 exit(1);
267         }
268         (void)fprintf(f, "%d\n", getpid());
269         (void)fclose(f);
270         if (get_lock(lpnumber) == 0)
271                 exit(1);
272 #endif
273         f = open_printer(lpnumber);
274 #ifdef  USE_GETPROTOBYNAME
275         if ((proto = getprotobyname("tcp")) == NULL)
276         {
277                 syslog(LOGOPTS, "Cannot find protocol for TCP!\n");
278                 exit(1);
279         }
280         if ((netfd = socket(AF_INET, SOCK_STREAM, proto->p_proto)) < 0)
281 #else
282         if ((netfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0)
283 #endif
284         {
285                 syslog(LOGOPTS, "socket: %m\n");
286                 exit(1);
287         }
288         if (setsockopt(netfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0)
289         {
290                 syslog(LOGOPTS, "setsocketopt: %m\n");
291                 exit(1);
292         }
293         netaddr.sin_port = htons(BASEPORT + lpnumber - '0');
294         if (bindaddr == 0) {
295                 netaddr.sin_addr.s_addr = htonl(INADDR_ANY);
296         } else {
297                 ipret = inet_pton(AF_INET, bindaddr, &netaddr.sin_addr.s_addr);
298                 if (ipret < 0) {
299                         syslog(LOGOPTS, "inet_pton: %m\n");
300                         exit(1);
301                 } else if (ipret == 0) {
302                         syslog(LOGOPTS, "inet_pton: invalid bind IP address\n");
303                         exit(1);
304                 }
305         }
306         memset(netaddr.sin_zero, 0, sizeof(netaddr.sin_zero));
307         if (bind(netfd, (struct sockaddr*) &netaddr, sizeof(netaddr)) < 0)
308         {
309                 syslog(LOGOPTS, "bind: %m\n");
310                 exit(1);
311         }
312         if (listen(netfd, 5) < 0)
313         {
314                 syslog(LOGOPTS, "listen: %m\n");
315                 exit(1);
316         }
317         clientlen = sizeof(client);
318         memset(&client, 0, sizeof(client));
319         while ((fd = accept(netfd, (struct sockaddr*) &client, &clientlen)) >= 0)
320         {
321 #ifdef  USE_LIBWRAP
322                 if (hosts_ctl("p910nd", STRING_UNKNOWN,
323                         inet_ntoa(client.sin_addr), STRING_UNKNOWN) == 0) {
324                         syslog(LOGOPTS, "Connection from %s port %hd rejected\n",
325                                 inet_ntoa(client.sin_addr),
326                                 ntohs(client.sin_port));
327                         close(fd);
328                         continue;
329                 }
330 #endif
331                 syslog(LOGOPTS, "Connection from %s port %hd accepted\n",
332                         inet_ntoa(client.sin_addr),
333                         ntohs(client.sin_port));
334                 /*write(fd, "Printing", 8);*/
335                 if (copy_stream(fd, f) < 0)
336                         syslog(LOGOPTS, "copy_stream: %m\n");
337                 (void)close(fd);
338         }
339         syslog(LOGOPTS, "accept: %m\n");
340         free_lock();
341         exit(1);
342 }
343
344 int is_standalone(void)
345 {
346         struct sockaddr_in      bind_addr;
347         socklen_t               ba_len;
348
349         /*
350          * Check to see if a socket was passed to us from inetd.
351          *
352          * Use getsockname() to determine if descriptor 0 is indeed a socket
353          * (and thus we are probably a child of inetd) or if it is instead
354          * something else and we are running standalone.
355          */
356         ba_len = sizeof(bind_addr);
357         if (getsockname(0, (struct sockaddr*) &bind_addr, &ba_len) == 0)
358                 return (0);             /* under inetd */
359         if (errno != ENOTSOCK)          /* strange... */
360                 syslog(LOGOPTS, "getsockname: %m\n");
361         return (1);
362 }
363
364 int main(int argc, char *argv[])
365 {
366         int             c, lpnumber;
367         char            *p;
368
369         if (argc <= 0)          /* in case not provided in inetd.conf */
370                 progname = "p910nd";
371         else
372         {
373                 progname = argv[0];
374                 if ((p = strrchr(progname, '/')) != 0)
375                         progname = p + 1;
376         }
377         lpnumber = '0';
378         while ((c = getopt(argc, argv, "bi:f:v")) != EOF)
379         {
380                 switch (c)
381                 {
382                 case 'b':
383                         bidir = 1;
384                         break;
385                 case 'f':
386                         device = optarg;
387                         break;
388                 case 'i':
389                         bindaddr = optarg;
390                         break;
391                 case 'v':
392                         show_version();
393                         break;
394                 default:
395                         usage();
396                         break;
397                 }
398         }
399         argc -= optind;
400         argv += optind;
401         if (argc > 0)
402         {
403                 if (isdigit(argv[0][0]))
404                         lpnumber = argv[0][0];
405         }
406         /* change the n in argv[0] to match the port so ps will show that */
407         if ((p = strstr(progname, "p910n")) != NULL)
408                 p[4] = lpnumber;
409         
410         /* We used to pass (LOG_PERROR|LOG_PID|LOG_LPR|LOG_ERR) to syslog, but
411          * syslog ignored the LOG_PID and LOG_PERROR option.  I.e. the intention
412          * was to add both options but the effect was to have neither.
413          * I disagree with the intention to add PERROR.  --Stef  */
414         openlog (p, LOG_PID, LOG_LPR);
415         if (is_standalone())
416                 server(lpnumber);
417         else
418                 one_job(lpnumber);
419         return (0);
420 }