xref: /freebsd/contrib/sendmail/libmilter/comm.c (revision 4b2eaea43fec8e8792be611dea204071a10b655a)
1 /*
2  *  Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  *
5  * By using this file, you agree to the terms and conditions set
6  * forth in the LICENSE file which can be found at the top level of
7  * the sendmail distribution.
8  *
9  */
10 
11 #include <sm/gen.h>
12 SM_RCSID("@(#)$Id: comm.c,v 8.54.2.2 2002/08/16 17:09:13 ca Exp $")
13 
14 #include "libmilter.h"
15 #include <sm/errstring.h>
16 
17 #define FD_Z	FD_ZERO(&readset);			\
18 		FD_SET((unsigned int) sd, &readset);	\
19 		FD_ZERO(&excset);			\
20 		FD_SET((unsigned int) sd, &excset)
21 
22 /*
23 **  MI_RD_CMD -- read a command
24 **
25 **	Parameters:
26 **		sd -- socket descriptor
27 **		timeout -- maximum time to wait
28 **		cmd -- single character command read from sd
29 **		rlen -- pointer to length of result
30 **		name -- name of milter
31 **
32 **	Returns:
33 **		buffer with rest of command
34 **		(malloc()ed here, should be free()d)
35 **		hack: encode error in cmd
36 */
37 
38 char *
39 mi_rd_cmd(sd, timeout, cmd, rlen, name)
40 	socket_t sd;
41 	struct timeval *timeout;
42 	char *cmd;
43 	size_t *rlen;
44 	char *name;
45 {
46 	ssize_t len;
47 	mi_int32 expl;
48 	ssize_t i;
49 	fd_set readset, excset;
50 	int ret;
51 	int save_errno;
52 	char *buf;
53 	char data[MILTER_LEN_BYTES + 1];
54 
55 	*cmd = '\0';
56 	*rlen = 0;
57 
58 	if (sd >= FD_SETSIZE)
59 	{
60 		smi_log(SMI_LOG_ERR, "%s: fd %d is larger than FD_SETSIZE %d",
61 			name, sd, FD_SETSIZE);
62 		*cmd = SMFIC_SELECT;
63 		return NULL;
64 	}
65 
66 	i = 0;
67 	for (;;)
68 	{
69 		FD_Z;
70 		ret = select(sd + 1, &readset, NULL, &excset, timeout);
71 		if (ret == 0)
72 			break;
73 		else if (ret < 0)
74 		{
75 			if (errno == EINTR)
76 				continue;
77 			break;
78 		}
79 		if (FD_ISSET(sd, &excset))
80 		{
81 			*cmd = SMFIC_SELECT;
82 			return NULL;
83 		}
84 
85 		len = MI_SOCK_READ(sd, data + i, sizeof data - i);
86 		if (MI_SOCK_READ_FAIL(len))
87 		{
88 			smi_log(SMI_LOG_ERR,
89 				"%s, mi_rd_cmd: read returned %d: %s",
90 				name, len, sm_errstring(errno));
91 			*cmd = SMFIC_RECVERR;
92 			return NULL;
93 		}
94 		if (len == 0)
95 		{
96 			*cmd = SMFIC_EOF;
97 			return NULL;
98 		}
99 		if (len >= (ssize_t) sizeof data - i)
100 			break;
101 		i += len;
102 	}
103 	if (ret == 0)
104 	{
105 		*cmd = SMFIC_TIMEOUT;
106 		return NULL;
107 	}
108 	else if (ret < 0)
109 	{
110 		smi_log(SMI_LOG_ERR,
111 			"%s: mi_rd_cmd: select returned %d: %s",
112 			name, ret, sm_errstring(errno));
113 		*cmd = SMFIC_RECVERR;
114 		return NULL;
115 	}
116 
117 	*cmd = data[MILTER_LEN_BYTES];
118 	data[MILTER_LEN_BYTES] = '\0';
119 	(void) memcpy((void *) &expl, (void *) &(data[0]), MILTER_LEN_BYTES);
120 	expl = ntohl(expl) - 1;
121 	if (expl <= 0)
122 		return NULL;
123 	if (expl > MILTER_CHUNK_SIZE)
124 	{
125 		*cmd = SMFIC_TOOBIG;
126 		return NULL;
127 	}
128 #if _FFR_ADD_NULL
129 	buf = malloc(expl + 1);
130 #else /* _FFR_ADD_NULL */
131 	buf = malloc(expl);
132 #endif /* _FFR_ADD_NULL */
133 	if (buf == NULL)
134 	{
135 		*cmd = SMFIC_MALLOC;
136 		return NULL;
137 	}
138 
139 	i = 0;
140 	for (;;)
141 	{
142 		FD_Z;
143 		ret = select(sd + 1, &readset, NULL, &excset, timeout);
144 		if (ret == 0)
145 			break;
146 		else if (ret < 0)
147 		{
148 			if (errno == EINTR)
149 				continue;
150 			break;
151 		}
152 		if (FD_ISSET(sd, &excset))
153 		{
154 			*cmd = SMFIC_SELECT;
155 			free(buf);
156 			return NULL;
157 		}
158 		len = MI_SOCK_READ(sd, buf + i, expl - i);
159 		if (MI_SOCK_READ_FAIL(len))
160 		{
161 			smi_log(SMI_LOG_ERR,
162 				"%s: mi_rd_cmd: read returned %d: %s",
163 				name, len, sm_errstring(errno));
164 			ret = -1;
165 			break;
166 		}
167 		if (len == 0)
168 		{
169 			*cmd = SMFIC_EOF;
170 			free(buf);
171 			return NULL;
172 		}
173 		if (len > expl - i)
174 		{
175 			*cmd = SMFIC_RECVERR;
176 			free(buf);
177 			return NULL;
178 		}
179 		if (len >= expl - i)
180 		{
181 			*rlen = expl;
182 #if _FFR_ADD_NULL
183 			/* makes life simpler for common string routines */
184 			buf[expl] = '\0';
185 #endif /* _FFR_ADD_NULL */
186 			return buf;
187 		}
188 		i += len;
189 	}
190 
191 	save_errno = errno;
192 	free(buf);
193 
194 	/* select returned 0 (timeout) or < 0 (error) */
195 	if (ret == 0)
196 	{
197 		*cmd = SMFIC_TIMEOUT;
198 		return NULL;
199 	}
200 	if (ret < 0)
201 	{
202 		smi_log(SMI_LOG_ERR,
203 			"%s: mi_rd_cmd: select returned %d: %s",
204 			name, ret, sm_errstring(save_errno));
205 		*cmd = SMFIC_RECVERR;
206 		return NULL;
207 	}
208 	*cmd = SMFIC_UNKNERR;
209 	return NULL;
210 }
211 /*
212 **  MI_WR_CMD -- write a cmd to sd
213 **
214 **	Parameters:
215 **		sd -- socket descriptor
216 **		timeout -- maximum time to wait (currently unused)
217 **		cmd -- single character command to write
218 **		buf -- buffer with further data
219 **		len -- length of buffer (without cmd!)
220 **
221 **	Returns:
222 **		MI_SUCCESS/MI_FAILURE
223 */
224 
225 /*
226 **  we don't care much about the timeout here, it's very long anyway
227 **  FD_SETSIZE is only checked in mi_rd_cmd.
228 **  XXX l == 0 ?
229 */
230 
231 #define MI_WR(data)	\
232 	while (sl > 0)							\
233 	{								\
234 		FD_ZERO(&wrtset);					\
235 		FD_SET((unsigned int) sd, &wrtset);			\
236 		ret = select(sd + 1, NULL, &wrtset, NULL, timeout);	\
237 		if (ret == 0)						\
238 			return MI_FAILURE;				\
239 		if (ret < 0)						\
240 		{							\
241 			if (errno == EINTR)				\
242 				continue;				\
243 			else						\
244 				return MI_FAILURE;			\
245 		}							\
246 		l = MI_SOCK_WRITE(sd, (void *) ((data) + i), sl);	\
247 		if (l < 0)						\
248 		{							\
249 			if (errno == EINTR)				\
250 				continue;				\
251 			else						\
252 				return MI_FAILURE;			\
253 		}							\
254 		i += l;							\
255 		sl -= l;						\
256 	}
257 
258 int
259 mi_wr_cmd(sd, timeout, cmd, buf, len)
260 	socket_t sd;
261 	struct timeval *timeout;
262 	int cmd;
263 	char *buf;
264 	size_t len;
265 {
266 	size_t sl, i;
267 	ssize_t l;
268 	mi_int32 nl;
269 	int ret;
270 	fd_set wrtset;
271 	char data[MILTER_LEN_BYTES + 1];
272 
273 	if (len > MILTER_CHUNK_SIZE)
274 		return MI_FAILURE;
275 	nl = htonl(len + 1);	/* add 1 for the cmd char */
276 	(void) memcpy(data, (void *) &nl, MILTER_LEN_BYTES);
277 	data[MILTER_LEN_BYTES] = (char) cmd;
278 	i = 0;
279 	sl = MILTER_LEN_BYTES + 1;
280 
281 	/* use writev() instead to send the whole stuff at once? */
282 
283 	MI_WR(data);
284 	if (len > 0 && buf == NULL)
285 		return MI_FAILURE;
286 	if (len == 0 || buf == NULL)
287 		return MI_SUCCESS;
288 	i = 0;
289 	sl = len;
290 	MI_WR(buf);
291 	return MI_SUCCESS;
292 }
293