1 /*  $Id: inndcomm.c 10283 2018-05-14 12:43:05Z iulius $
2 **
3 **  Library routines to let other programs control innd.
4 */
5 
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/socket.h"
9 #include <ctype.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <signal.h>
13 #include <sys/stat.h>
14 
15 #ifdef HAVE_SYS_TIME_H
16 # include <sys/time.h>
17 #endif
18 #include <time.h>
19 
20 /* Needed on AIX 4.1 to get fd_set and friends. */
21 #ifdef HAVE_SYS_SELECT_H
22 # include <sys/select.h>
23 #endif
24 
25 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
26 # include "portable/socket-unix.h"
27 #endif
28 
29 #include "inn/innconf.h"
30 #include "inn/inndcomm.h"
31 #include "inn/libinn.h"
32 #include "inn/paths.h"
33 
34 static char			*ICCsockname = NULL;
35 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
36 static struct sockaddr_un	ICCserv;
37 static struct sockaddr_un	ICCclient;
38 #endif
39 static int			ICCfd;
40 static int			ICCtimeout;
41 const char			*ICCfailure;
42 
43 
44 /*
45 **  Set the timeout.
46 */
47 void
ICCsettimeout(int i)48 ICCsettimeout(int i)
49 {
50     ICCtimeout = i;
51 }
52 
53 
54 /*
55 **  Get ready to talk to the server.
56 */
57 int
ICCopen(void)58 ICCopen(void)
59 {
60     int mask, oerrno, fd;
61 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
62     int size = 65535;
63 #endif
64 
65     if (innconf == NULL) {
66 	if (!innconf_read(NULL)) {
67 	    ICCfailure = "innconf";
68 	    return -1;
69 	}
70     }
71     /* Create a temporary name.  mkstemp is complete overkill here and is used
72        only because it's convenient.  We don't use it properly, since we
73        actually need to create a socket or named pipe, so there is a race
74        condition here.  It doesn't matter, since pathrun isn't world-writable
75        (conceivably two processes could end up using the same temporary name
76        at the same time, but the worst that will happen is that one process
77        will delete the other's temporary socket). */
78     if (ICCsockname == NULL)
79 	ICCsockname = concatpath(innconf->pathrun, INN_PATH_TEMPSOCK);
80     fd = mkstemp(ICCsockname);
81     if (fd < 0) {
82         ICCfailure = "mkstemp";
83         return -1;
84     }
85     close(fd);
86     if (unlink(ICCsockname) < 0 && errno != ENOENT) {
87 	ICCfailure = "unlink";
88 	return -1;
89     }
90 
91 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
92 
93     /* Make a socket and give it the name. */
94     if ((ICCfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
95 	ICCfailure = "socket";
96 	return -1;
97     }
98 
99     /* Adjust the socket buffer size to accommodate large responses.  Ignore
100        failure; the message may fit anyway, and if not, we'll fail below. */
101     setsockopt(ICCfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
102 
103     memset(&ICCclient, 0, sizeof ICCclient);
104     ICCclient.sun_family = AF_UNIX;
105     strlcpy(ICCclient.sun_path, ICCsockname, sizeof(ICCclient.sun_path));
106     mask = umask(0);
107     if (bind(ICCfd, (struct sockaddr *) &ICCclient, SUN_LEN(&ICCclient)) < 0) {
108 	oerrno = errno;
109 	umask(mask);
110 	errno = oerrno;
111 	ICCfailure = "bind";
112 	return -1;
113     }
114     umask(mask);
115 
116     /* Name the server's socket. */
117     memset(&ICCserv, 0, sizeof ICCserv);
118     ICCserv.sun_family = AF_UNIX;
119     strlcpy(ICCserv.sun_path, innconf->pathrun, sizeof(ICCserv.sun_path));
120     strlcat(ICCserv.sun_path, "/", sizeof(ICCserv.sun_path));
121     strlcat(ICCserv.sun_path, INN_PATH_NEWSCONTROL, sizeof(ICCserv.sun_path));
122 
123 #else /* !HAVE_UNIX_DOMAIN_SOCKETS */
124 
125     /* Make a named pipe and open it. */
126     mask = umask(0);
127     if (mkfifo(ICCsockname, 0666) < 0) {
128 	oerrno = errno;
129 	umask(mask);
130 	errno = oerrno;
131 	ICCfailure = "mkfifo";
132 	return -1;
133     }
134     umask(mask);
135     if ((ICCfd = open(ICCsockname, O_RDWR)) < 0) {
136 	ICCfailure = "open";
137 	return -1;
138     }
139 #endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
140 
141     ICCfailure = NULL;
142     return 0;
143 }
144 
145 
146 /*
147 **  Close down.
148 */
149 int
ICCclose(void)150 ICCclose(void)
151 {
152     int		i;
153 
154     ICCfailure = NULL;
155     i = 0;
156     if (close(ICCfd) < 0) {
157 	ICCfailure = "close";
158 	i = -1;
159     }
160     if (unlink(ICCsockname) < 0 && errno != ENOENT) {
161 	ICCfailure = "unlink";
162 	i = -1;
163     }
164     return i;
165 }
166 
167 
168 /*
169 **  Get the server's pid.
170 */
171 static pid_t
ICCserverpid(void)172 ICCserverpid(void)
173 {
174     pid_t		pid;
175     FILE		*F;
176     char                *path;
177     char		buff[SMBUF];
178 
179     pid = 1;
180     path = concatpath(innconf->pathrun, INN_PATH_SERVERPID);
181     F = fopen(path, "r");
182     free(path);
183     if (F != NULL) {
184 	if (fgets(buff, sizeof buff, F) != NULL)
185 	    pid = atol(buff);
186 	fclose(F);
187     }
188     return pid;
189 }
190 
191 
192 /*
193 **  See if the server is still there.  When in doubt, assume yes.  Cache the
194 **  PID since a rebooted server won't know about our pending message.
195 */
196 static bool
ICCserveralive(pid_t pid)197 ICCserveralive(pid_t pid)
198 {
199     if (kill(pid, 0) > 0 || errno != ESRCH)
200 	return true;
201     return false;
202 }
203 
204 
205 /*
206 **  Send an arbitrary command to the server.
207 **
208 **  There is a protocol version (one-byte) on the front of the message,
209 **  followed by a two byte length count.  The length includes the protocol
210 **  byte and the length itself.  This differs from the protocol in much
211 **  earlier versions of INN.
212 */
213 int
ICCcommand(char cmd,const char * argv[],char ** replyp)214 ICCcommand(char cmd, const char *argv[], char **replyp)
215 {
216     char		*buff;
217     char		*p;
218     const char		*q;
219     char		save;
220     int			i ;
221 #ifndef HAVE_UNIX_DOMAIN_SOCKETS
222     int			fd;
223     char                *path;
224 #endif
225     int			len;
226     fd_set		Rmask;
227     struct timeval	T;
228     pid_t		pid;
229     ICC_MSGLENTYPE      rlen;
230     ICC_PROTOCOLTYPE    protocol;
231     size_t bufsiz = 64 * 1024 - 1;
232 
233     /* Is server there? */
234     pid = ICCserverpid();
235     if (!ICCserveralive(pid)) {
236 	ICCfailure = "dead server";
237 	return -1;
238     }
239 
240     /* Get the length of the buffer. */
241     buff = xmalloc(bufsiz);
242     if (replyp)
243 	*replyp = NULL;
244 
245     /* Advance to leave space for length + protocol version info. */
246     buff += HEADER_SIZE;
247     bufsiz -= HEADER_SIZE;
248 
249     /* Format the message. */
250     snprintf(buff, bufsiz, "%s%c%c", ICCsockname, SC_SEP, cmd);
251     for (p = buff + strlen(buff), i = 0; (q = argv[i]) != NULL; i++) {
252 	*p++ = SC_SEP;
253         *p = '\0';
254         strlcat(buff, q, bufsiz);
255         p += strlen(q);
256     }
257 
258     /* Send message. */
259     ICCfailure = NULL;
260     len = p - buff + HEADER_SIZE;
261     rlen = htons(len);
262 
263     /* now stick in the protocol version and the length. */
264     buff -= HEADER_SIZE;
265     bufsiz += HEADER_SIZE;
266     protocol = ICC_PROTOCOL_1;
267     memcpy(buff, &protocol, sizeof(protocol));
268     memcpy(buff + sizeof(protocol), &rlen, sizeof(rlen));
269 
270 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
271     if (sendto(ICCfd, buff, len, 0,(struct sockaddr *) &ICCserv,
272                SUN_LEN(&ICCserv)) < 0) {
273 	free(buff);
274 	ICCfailure = "sendto";
275 	return -1;
276     }
277 #else /* !HAVE_UNIX_DOMAIN_SOCKETS */
278     path = concatpath(innconf->pathrun, INN_PATH_NEWSCONTROL);
279     fd = open(path, O_WRONLY);
280     free(path);
281     if (fd < 0) {
282 	free(buff);
283 	ICCfailure = "open";
284 	return -1;
285     }
286     if (write(fd, buff, len) != len) {
287 	i = errno;
288 	free(buff);
289 	close(fd);
290 	errno = i;
291 	ICCfailure = "write";
292 	return -1;
293     }
294     close(fd);
295 #endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
296 
297     /* Possibly get a reply. */
298     switch (cmd) {
299     default:
300 	if (ICCtimeout >= 0)
301 	    break;
302 	/* FALLTHROUGH */
303     case SC_SHUTDOWN:
304     case SC_XABORT:
305     case SC_XEXEC:
306 	free(buff);
307 	return 0;
308     }
309 
310     /* Wait for the reply. */
311     for ( ; ; ) {
312 	FD_ZERO(&Rmask);
313 	FD_SET(ICCfd, &Rmask);
314 	T.tv_sec = ICCtimeout ? ICCtimeout : 120;
315 	T.tv_usec = 0;
316 	i = select(ICCfd + 1, &Rmask, NULL, NULL, &T);
317 	if (i < 0) {
318 	    free(buff);
319 	    ICCfailure = "select";
320 	    return -1;
321 	}
322 	if (i > 0 && FD_ISSET(ICCfd, &Rmask))
323 	    /* Server reply is there; go handle it. */
324 	    break;
325 
326 	/* No data -- if we timed out, return. */
327 	if (ICCtimeout) {
328 	    free(buff);
329 	    errno = ETIMEDOUT;
330 	    ICCfailure = "timeout";
331 	    return -1;
332 	}
333 
334 	if (!ICCserveralive(pid)) {
335 	    free(buff);
336 	    ICCfailure = "dead server";
337 	    return -1;
338 	}
339     }
340 
341 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
342 
343     /* Read the reply. */
344     i = RECVorREAD(ICCfd, buff, bufsiz);
345     if ((unsigned int) i < HEADER_SIZE) {
346         free(buff);
347         ICCfailure = "read";
348         return -1;
349     }
350     memcpy(&protocol, buff, sizeof(protocol));
351     memcpy(&rlen, buff + sizeof(protocol), sizeof(rlen));
352     rlen = ntohs(rlen);
353 
354     if (i != rlen) {
355         free(buff);
356         ICCfailure = "short read";
357         return -1;
358     }
359 
360     if (protocol != ICC_PROTOCOL_1) {
361         free(buff);
362         ICCfailure = "protocol mismatch";
363         return -1;
364     }
365 
366     memmove(buff, buff + HEADER_SIZE, rlen - HEADER_SIZE);
367     i -= HEADER_SIZE;
368 
369     buff[i] = '\0';
370 
371 #else /* !HAVE_UNIX_DOMAIN_SOCKETS */
372 
373     i = RECVorREAD(ICCfd, buff, HEADER_SIZE);
374     if (i != HEADER_SIZE)
375 	return -1;
376 
377     memcpy(&protocol, buff, sizeof(protocol));
378     memcpy(&rlen, buff + sizeof(protocol), sizeof(rlen));
379     rlen = ntohs(rlen) - HEADER_SIZE;
380     if (rlen > bufsiz) {
381         ICCfailure = "bad length";
382         return -1;
383     }
384 
385     i = RECVorREAD(ICCfd, buff, rlen);
386     if (i != rlen) {
387         ICCfailure = "short read";
388 	return -1;
389     }
390 
391     buff[i] = '\0';
392 
393     if (protocol != ICC_PROTOCOL_1) {
394         ICCfailure = "protocol mismatch";
395         return -1;
396     }
397 
398 #endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
399 
400     /* Parse the rest of the reply; expected to be like
401        <exitcode><space><text>" */
402     i = 0;
403     if (isdigit((unsigned char) buff[0])) {
404 	for (p = buff; *p && isdigit((unsigned char) *p); p++)
405 	    continue;
406 	if (*p) {
407 	    save = *p;
408 	    *p = '\0';
409 	    i = atoi(buff);
410 	    *p = save;
411 	}
412     }
413     if (replyp)
414 	*replyp = buff;
415     else
416 	free(buff);
417 
418     return i;
419 }
420 
421 
422 /*
423 **  Send a "cancel" command.
424 */
425 int
ICCcancel(const char * msgid)426 ICCcancel(const char *msgid)
427 {
428     const char	*args[2];
429 
430     args[0] = msgid;
431     args[1] = NULL;
432     return ICCcommand(SC_CANCEL, args, NULL);
433 }
434 
435 
436 /*
437 **  Send a "go" command.
438 */
439 int
ICCgo(const char * why)440 ICCgo(const char *why)
441 {
442     const char	*args[2];
443 
444     args[0] = why;
445     args[1] = NULL;
446     return ICCcommand(SC_GO, args, NULL);
447 }
448 
449 
450 /*
451 **  Send a "pause" command.
452 */
453 int
ICCpause(const char * why)454 ICCpause(const char *why)
455 {
456     const char	*args[2];
457 
458     args[0] = why;
459     args[1] = NULL;
460     return ICCcommand(SC_PAUSE, args, NULL);
461 }
462 
463 
464 /*
465 **  Send a "reserve" command.
466 */
467 int
ICCreserve(const char * why)468 ICCreserve(const char *why)
469 {
470     const char	*args[2];
471 
472     args[0] = why;
473     args[1] = NULL;
474     return ICCcommand(SC_RESERVE, args, NULL);
475 }
476