Line 0
Link Here
|
|
|
1 |
/* |
2 |
** Copyright 2009 Marko Njezic |
3 |
** Licensed under the same terms as Courier Authlib AND/OR Courier Maildrop. |
4 |
** |
5 |
** Partially based on authdaemonlib.c from Courier Authlib, which had the following statement: |
6 |
** |
7 |
** Copyright 2000-2006 Double Precision, Inc. See COPYING for |
8 |
** distribution information. |
9 |
** |
10 |
** Code that was taken from authdaemonlib.c is as follows: |
11 |
** - s_connect() function |
12 |
** - opensock() function with modification to accept socket address |
13 |
** - writeauth() function |
14 |
** - readline() function with related support functions (with modification |
15 |
** to time-out after TIMEOUT_READ seconds) |
16 |
*/ |
17 |
|
18 |
#include "dovecotauth.h" |
19 |
#include <stdio.h> |
20 |
#include <stdlib.h> |
21 |
#include <string.h> |
22 |
#include <errno.h> |
23 |
#include <fcntl.h> |
24 |
#include <pwd.h> |
25 |
#include <time.h> |
26 |
#include <unistd.h> |
27 |
#include <sys/types.h> |
28 |
#include <sys/socket.h> |
29 |
#include <sys/un.h> |
30 |
#include <sys/select.h> |
31 |
|
32 |
static const char rcsid[]="$Id$"; |
33 |
|
34 |
static int TIMEOUT_SOCK=10, |
35 |
TIMEOUT_WRITE=10, |
36 |
TIMEOUT_READ=30; |
37 |
|
38 |
static int s_connect(int sockfd, |
39 |
const struct sockaddr *addr, |
40 |
size_t addr_s, |
41 |
time_t connect_timeout) |
42 |
{ |
43 |
fd_set fdr; |
44 |
struct timeval tv; |
45 |
int rc; |
46 |
|
47 |
#ifdef SOL_KEEPALIVE |
48 |
setsockopt(sockfd, SOL_SOCKET, SOL_KEEPALIVE, |
49 |
(const char *)&dummy, sizeof(dummy)); |
50 |
#endif |
51 |
|
52 |
#ifdef SOL_LINGER |
53 |
{ |
54 |
struct linger l; |
55 |
|
56 |
l.l_onoff=0; |
57 |
l.l_linger=0; |
58 |
|
59 |
setsockopt(sockfd, SOL_SOCKET, SOL_LINGER, |
60 |
(const char *)&l, sizeof(l)); |
61 |
} |
62 |
#endif |
63 |
|
64 |
/* |
65 |
** If configuration says to use the kernel's timeout settings, |
66 |
** just call connect, and be done with it. |
67 |
*/ |
68 |
|
69 |
if (connect_timeout == 0) |
70 |
return ( connect(sockfd, addr, addr_s)); |
71 |
|
72 |
/* Asynchronous connect with timeout. */ |
73 |
|
74 |
if (fcntl(sockfd, F_SETFL, O_NONBLOCK) < 0) return (-1); |
75 |
|
76 |
if ( connect(sockfd, addr, addr_s) == 0) |
77 |
{ |
78 |
/* That was easy, we're done. */ |
79 |
|
80 |
if (fcntl(sockfd, F_SETFL, 0) < 0) return (-1); |
81 |
return (0); |
82 |
} |
83 |
|
84 |
if (errno != EINPROGRESS) |
85 |
return -1; |
86 |
|
87 |
/* Wait for the connection to go through, until the timeout expires */ |
88 |
|
89 |
FD_ZERO(&fdr); |
90 |
FD_SET(sockfd, &fdr); |
91 |
tv.tv_sec=connect_timeout; |
92 |
tv.tv_usec=0; |
93 |
|
94 |
rc=select(sockfd+1, 0, &fdr, 0, &tv); |
95 |
if (rc < 0) return (-1); |
96 |
|
97 |
if (!FD_ISSET(sockfd, &fdr)) |
98 |
{ |
99 |
errno=ETIMEDOUT; |
100 |
return (-1); |
101 |
} |
102 |
|
103 |
{ |
104 |
int gserr; |
105 |
socklen_t gslen = sizeof(gserr); |
106 |
|
107 |
if (getsockopt(sockfd, SOL_SOCKET, |
108 |
SO_ERROR, |
109 |
(char *)&gserr, &gslen)==0) |
110 |
{ |
111 |
if (gserr == 0) |
112 |
return 0; |
113 |
|
114 |
errno=gserr; |
115 |
} |
116 |
} |
117 |
return (-1); |
118 |
} |
119 |
|
120 |
static int opensock(const char *addr) |
121 |
{ |
122 |
int s=socket(PF_UNIX, SOCK_STREAM, 0); |
123 |
struct sockaddr_un skun; |
124 |
|
125 |
skun.sun_family=AF_UNIX; |
126 |
strncpy(skun.sun_path, addr, sizeof(skun.sun_path)); |
127 |
|
128 |
if (s < 0) |
129 |
{ |
130 |
perror("CRIT: dovecotauth: socket() failed"); |
131 |
return (-1); |
132 |
} |
133 |
|
134 |
{ |
135 |
const char *p=getenv("TIMEOUT_SOCK"); |
136 |
int n=atoi(p ? p:"0"); |
137 |
|
138 |
if (n > 0) |
139 |
TIMEOUT_SOCK=n; |
140 |
} |
141 |
|
142 |
{ |
143 |
const char *p=getenv("TIMEOUT_READ"); |
144 |
int n=atoi(p ? p:"0"); |
145 |
|
146 |
if (n > 0) |
147 |
TIMEOUT_READ=n; |
148 |
} |
149 |
|
150 |
{ |
151 |
const char *p=getenv("TIMEOUT_WRITE"); |
152 |
int n=atoi(p ? p:"0"); |
153 |
|
154 |
if (n > 0) |
155 |
TIMEOUT_WRITE=n; |
156 |
} |
157 |
|
158 |
if (s_connect(s, (const struct sockaddr *)&skun, sizeof(skun), |
159 |
TIMEOUT_SOCK)) |
160 |
{ |
161 |
perror("ERR: dovecotauth: s_connect() failed"); |
162 |
if (errno == ETIMEDOUT || errno == ECONNREFUSED) |
163 |
fprintf(stderr, "ERR: [Hint: perhaps dovecot-auth daemon is not running?]\n"); |
164 |
close(s); |
165 |
return (-1); |
166 |
} |
167 |
return (s); |
168 |
} |
169 |
|
170 |
static int writeauth(int fd, const char *p, unsigned pl) |
171 |
{ |
172 |
fd_set fds; |
173 |
struct timeval tv; |
174 |
|
175 |
while (pl) |
176 |
{ |
177 |
int n; |
178 |
|
179 |
FD_ZERO(&fds); |
180 |
FD_SET(fd, &fds); |
181 |
tv.tv_sec=TIMEOUT_WRITE; |
182 |
tv.tv_usec=0; |
183 |
if (select(fd+1, 0, &fds, 0, &tv) <= 0 || !FD_ISSET(fd, &fds)) |
184 |
return (-1); |
185 |
n=write(fd, p, pl); |
186 |
if (n <= 0) return (-1); |
187 |
p += n; |
188 |
pl -= n; |
189 |
} |
190 |
return (0); |
191 |
} |
192 |
|
193 |
struct enum_getch { |
194 |
char buffer[BUFSIZ]; |
195 |
char *buf_ptr; |
196 |
size_t buf_left; |
197 |
}; |
198 |
|
199 |
#define getauthc(fd,eg) ((eg)->buf_left-- ? \ |
200 |
(unsigned char)*((eg)->buf_ptr)++:\ |
201 |
fillgetauthc((fd),(eg))) |
202 |
|
203 |
static int fillgetauthc(int fd, struct enum_getch *eg) |
204 |
{ |
205 |
time_t end_time, curtime; |
206 |
|
207 |
time(&end_time); |
208 |
end_time += TIMEOUT_READ; |
209 |
|
210 |
for (;;) |
211 |
{ |
212 |
int n; |
213 |
fd_set fds; |
214 |
struct timeval tv; |
215 |
|
216 |
time(&curtime); |
217 |
if (curtime >= end_time) |
218 |
break; |
219 |
|
220 |
FD_ZERO(&fds); |
221 |
FD_SET(fd, &fds); |
222 |
tv.tv_sec=end_time - curtime; |
223 |
tv.tv_usec=0; |
224 |
if (select(fd+1, &fds, 0, 0, &tv) <= 0 || !FD_ISSET(fd, &fds)) |
225 |
break; |
226 |
|
227 |
n=read(fd, eg->buffer, sizeof(eg->buffer)); |
228 |
if (n <= 0) |
229 |
break; |
230 |
|
231 |
eg->buf_ptr=eg->buffer; |
232 |
eg->buf_left=n; |
233 |
|
234 |
--eg->buf_left; |
235 |
return (unsigned char)*(eg->buf_ptr)++; |
236 |
} |
237 |
return EOF; |
238 |
} |
239 |
|
240 |
static int readline(int fd, struct enum_getch *eg, |
241 |
char *buf, |
242 |
size_t bufsize) |
243 |
{ |
244 |
if (bufsize == 0) |
245 |
return EOF; |
246 |
|
247 |
while (--bufsize) |
248 |
{ |
249 |
int ch=getauthc(fd, eg); |
250 |
|
251 |
if (ch == EOF) |
252 |
return -1; |
253 |
if (ch == '\n') |
254 |
break; |
255 |
|
256 |
*buf++=ch; |
257 |
} |
258 |
*buf=0; |
259 |
return 0; |
260 |
} |
261 |
|
262 |
/* |
263 |
** The actual implementation of Dovecot authentication protocol handling follows. |
264 |
** Full specification of the protocol can be found at: http://wiki.dovecot.org/Authentication%20Protocol |
265 |
** We are only interested in the "master" type requests for user information. |
266 |
*/ |
267 |
|
268 |
int parse_userinfo(const char *user, const char *linebuf, |
269 |
int (*func)(struct dovecotauthinfo *, void *), void *arg) |
270 |
{ |
271 |
int return_value=1; |
272 |
struct dovecotauthinfo a; |
273 |
char *buf, *p; |
274 |
uid_t u; |
275 |
|
276 |
/* Validate input arguments */ |
277 |
if (!user || !linebuf) |
278 |
return (1); |
279 |
|
280 |
/* Try to allocate buffer */ |
281 |
buf = (char *)malloc(strlen(linebuf)+1); |
282 |
if (!buf) |
283 |
return (1); |
284 |
strcpy(buf, linebuf); |
285 |
|
286 |
memset(&a, 0, sizeof(a)); |
287 |
a.homedir=""; |
288 |
|
289 |
p = strtok(buf, "\t"); |
290 |
if (p) |
291 |
a.address=p; |
292 |
else |
293 |
a.address=user; |
294 |
|
295 |
/* Parse any additional parameters */ |
296 |
while ((p = strtok(0, "\t")) != 0) |
297 |
{ |
298 |
if (strncmp(p, "uid=", 4) == 0) |
299 |
{ |
300 |
u=atol(p+4); |
301 |
a.sysuserid = &u; |
302 |
if (u == 0) |
303 |
{ |
304 |
fprintf(stderr, "ERR: dovecotauth: Received invalid uid from auth socket\n"); |
305 |
return_value=1; |
306 |
goto cleanup_parse_userinfo; |
307 |
} |
308 |
} |
309 |
else if (strncmp(p, "gid=", 4) == 0) |
310 |
{ |
311 |
a.sysgroupid=atol(p+4); |
312 |
if (a.sysgroupid == 0) |
313 |
{ |
314 |
fprintf(stderr, "ERR: dovecotauth: Received invalid gid from auth socket\n"); |
315 |
return_value=1; |
316 |
goto cleanup_parse_userinfo; |
317 |
} |
318 |
} |
319 |
else if (strncmp(p, "system_user=", 12) == 0) |
320 |
{ |
321 |
a.sysusername=p+12; |
322 |
if (a.sysusername) |
323 |
{ |
324 |
struct passwd *q=getpwnam(a.sysusername); |
325 |
|
326 |
if (q && q->pw_uid == 0) |
327 |
{ |
328 |
fprintf(stderr, "ERR: dovecotauth: Received invalid system user from auth socket\n"); |
329 |
return_value=1; |
330 |
goto cleanup_parse_userinfo; |
331 |
} |
332 |
} |
333 |
} |
334 |
else if (strncmp(p, "home=", 5) == 0) |
335 |
{ |
336 |
a.homedir=p+5; |
337 |
} |
338 |
else if (strncmp(p, "mail=", 5) == 0) |
339 |
{ |
340 |
a.maildir=p+5; |
341 |
} |
342 |
} |
343 |
|
344 |
return_value = (*func)(&a, arg); |
345 |
|
346 |
cleanup_parse_userinfo: |
347 |
free(buf); |
348 |
return return_value; |
349 |
} |
350 |
|
351 |
#define DOVECOTAUTH_LINEBUFSIZE 8192 |
352 |
|
353 |
int _dovecotauth_getuserinfo(int wrfd, int rdfd, const char *user, |
354 |
int (*func)(struct dovecotauthinfo *, void *), void *arg) |
355 |
{ |
356 |
static char cmdpart1[]="VERSION\t1\t0\nUSER\t1\t"; |
357 |
static char cmdpart2[]="\tservice=maildrop\n"; |
358 |
int return_value=1, handshake=0; |
359 |
struct enum_getch eg; |
360 |
char *cmdbuf, *linebuf; |
361 |
|
362 |
/* Validate input arguments */ |
363 |
if (!user) |
364 |
return (1); |
365 |
|
366 |
/* Try to allocate buffers */ |
367 |
cmdbuf=(char *)malloc(strlen(cmdpart1)+strlen(cmdpart2)+strlen(user)+20); |
368 |
if (!cmdbuf) |
369 |
return (1); |
370 |
|
371 |
linebuf=(char *)malloc(DOVECOTAUTH_LINEBUFSIZE); |
372 |
if (!linebuf) |
373 |
return (1); |
374 |
|
375 |
/* Initial handshake */ |
376 |
eg.buf_left=0; |
377 |
while (readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) |
378 |
{ |
379 |
if (strncmp(linebuf, "VERSION\t", 8) == 0) |
380 |
{ |
381 |
if (strncmp(linebuf+8, "1\t", 2) != 0) |
382 |
{ |
383 |
fprintf(stderr, "ERR: dovecotauth: Authentication protocol version mismatch\n"); |
384 |
return_value=1; |
385 |
goto cleanup_dovecotauth_getuserinfo; |
386 |
} |
387 |
} |
388 |
else if (strncmp(linebuf, "SPID\t", 5) == 0) |
389 |
{ |
390 |
/* End of server side handshake */ |
391 |
handshake=1; |
392 |
break; |
393 |
} |
394 |
} |
395 |
|
396 |
if (!handshake) |
397 |
{ |
398 |
fprintf(stderr, "ERR: dovecotauth: Did not receive proper server handshake from auth socket\n"); |
399 |
return_value=1; |
400 |
goto cleanup_dovecotauth_getuserinfo; |
401 |
} |
402 |
|
403 |
/* |
404 |
** Try to be helpful in case that user tries to connect to the wrong auth socket. |
405 |
** There's a slight chance that this won't execute in case that the previously |
406 |
** returned line ends exactly at the buffer end, but we won't handle that case, |
407 |
** since this is just a hint to the user, and not really neccessary. |
408 |
** Normally, if user tries to communicate with wrong auth socket, |
409 |
** we would simply time-out, while waiting for information. |
410 |
*/ |
411 |
if (eg.buf_left > 0 && readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) |
412 |
{ |
413 |
if (strncmp(linebuf, "CUID\t", 5) == 0) |
414 |
{ |
415 |
fprintf(stderr, "ERR: dovecotauth: Trying to connect to what appears to be a client auth socket, instead of a master auth socket\n"); |
416 |
return_value=1; |
417 |
goto cleanup_dovecotauth_getuserinfo; |
418 |
} |
419 |
} |
420 |
|
421 |
/* Generate our part of communication */ |
422 |
strcat(strcat(strcpy(cmdbuf, cmdpart1), user), cmdpart2); |
423 |
|
424 |
/* Send our part of communication */ |
425 |
if (writeauth(wrfd, cmdbuf, strlen(cmdbuf))) |
426 |
{ |
427 |
return_value=1; |
428 |
goto cleanup_dovecotauth_getuserinfo; |
429 |
} |
430 |
|
431 |
/* Parse returned information */ |
432 |
eg.buf_left=0; |
433 |
if (readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) |
434 |
{ |
435 |
if (strncmp(linebuf, "USER\t1\t", 7) == 0) |
436 |
{ |
437 |
/* User was found in the database and we now parse returned information */ |
438 |
return_value=parse_userinfo(user, linebuf+7, func, arg); |
439 |
goto cleanup_dovecotauth_getuserinfo; |
440 |
} |
441 |
else if (strcmp(linebuf, "NOTFOUND\t1") == 0) |
442 |
{ |
443 |
/* User was not found in the database */ |
444 |
return_value=-1; /* Negative return value means that user is not found! */ |
445 |
goto cleanup_dovecotauth_getuserinfo; |
446 |
} |
447 |
else if (strncmp(linebuf, "FAIL\t1", 6) == 0) |
448 |
{ |
449 |
/* An internal error has occurred on Dovecot's end */ |
450 |
return_value=1; |
451 |
goto cleanup_dovecotauth_getuserinfo; |
452 |
} |
453 |
else |
454 |
{ |
455 |
fprintf(stderr, "ERR: dovecotauth: Received unknown input from auth socket\n"); |
456 |
} |
457 |
} |
458 |
else |
459 |
fprintf(stderr, "ERR: dovecotauth: Did not receive proper input from auth socket\n"); |
460 |
|
461 |
cleanup_dovecotauth_getuserinfo: |
462 |
free(cmdbuf); |
463 |
free(linebuf); |
464 |
return return_value; |
465 |
} |
466 |
|
467 |
int dovecotauth_getuserinfo(const char *addr, const char *user, |
468 |
int (*func)(struct dovecotauthinfo *, void *), void *arg) |
469 |
{ |
470 |
int s=opensock(addr); |
471 |
int rc; |
472 |
|
473 |
if (s < 0) |
474 |
{ |
475 |
return (1); |
476 |
} |
477 |
rc = _dovecotauth_getuserinfo(s, s, user, func, arg); |
478 |
close(s); |
479 |
return rc; |
480 |
} |