Project

General

Profile

RE: User auth against an IMAP server - is it wanted? » mod_auth_imap.c

Sunding Wei, 2013-02-21 10:35

 
1
/*
2
 * mod_auth_imap: authentication via IMAP server
3
 * Version: 2.2 - BETA release.
4
 *
5
 * brillat-apachemodule@mainsheet.org (Ben Brillat), 
6
 *   based on mod_auth_pop by Milos Prodanovic <awl@verat.net>
7
 *
8
 * Jian Zhen <jlz@zhen.org>
9
 *   Modified version 1.1 by Ben to work with Apache 2
10
 *   
11
 * Sunding Wei <swei(at)dingding.me>
12
 *   Added Auth_IMAP_Domain option to login Google Apps, 2013.2.21
13
 *
14
 * Follows the IMAP v4rev1 RFC - RFC 2060
15
 * http://www.ietf.org/rfc/rfc2060.txt
16
 */
17

    
18
#include "apr_lib.h"
19

    
20
#define APR_WANT_STRFUNC
21
#include "apr_want.h"
22
#include "apr_strings.h"
23
#include "apr_dbm.h"
24
#include "apr_md5.h"
25

    
26
#include "httpd.h"
27
#include "http_config.h"
28
#include "http_core.h"
29
#include "http_log.h"
30
#include "http_protocol.h"
31
#include "http_request.h"   /* for ap_hook_(check_user_id | auth_checker)*/
32

    
33

    
34
#include <signal.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#include <errno.h>
39
#include <sys/types.h>
40
#include <sys/socket.h>
41
#include <netinet/in.h>
42
#include <sys/types.h>
43
#include <sys/stat.h>
44
#include <fcntl.h>
45
#include <netdb.h>
46
#include <syslog.h>
47
 
48
#define _OK 1
49

    
50
int Sock;
51

    
52

    
53
/*******************************************************************************
54
 * tcp_gets
55
 *	reads a line from the network (up to \n or EOF)
56
 *     Always returns 0
57
 *******************************************************************************/
58
int tcp_gets(int s, char *res,int len) {
59
    char c;
60
    int cur=0,rc;
61
 
62
    memset(res,0,len+1);
63
  
64
    while( (rc = read(s, &c, 1))!=EOF) {
65
        if(cur<len)
66
        res[cur]=c;
67
        cur++;
68
        if(c=='\n') break;     
69
    }
70

    
71
    return 0;
72
}
73

    
74
/*******************************************************************************
75
 * clean_up
76
 *	closes the socket
77
 *******************************************************************************/
78
void clean_up(int s) { 
79
    close(s);
80
}
81

    
82
/*******************************************************************************
83
 * tcp_puts
84
 *	write a line to the network
85
 *     performs a bzero (via memset) on the memory to be used...
86
 *******************************************************************************/
87
int tcp_puts(int s, char *sttr) {
88
    int rr,len;
89
    char line[512];
90

    
91
    memset(line,0,512);
92
    len=strlen(sttr);
93
    if(len>510) len=510;
94
    strncpy(line,sttr,len);
95
    rr=write(s,line,strlen(line));
96
    return rr;
97
}
98

    
99

    
100
/*******************************************************************************
101
 * imap_tcp_open
102
 *	connects to the remote server
103
 *	returns a socket
104
 *******************************************************************************/
105
int imap_tcp_open(request_rec *r, char *hostname, int port) {
106
    struct hostent *HOST;
107
    struct sockaddr_in sai;
108
    int s;
109

    
110
    if ((HOST = gethostbyname(hostname)) == (struct hostent *)NULL) {
111
        ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Hostname unknown.");
112
        return !_OK;
113
    }
114

    
115
    memset((char *)&sai, '\0', sizeof(sai));
116
    memcpy((char *)&sai.sin_addr, (char *)HOST -> h_addr, HOST -> h_length);
117
    sai.sin_family = HOST -> h_addrtype;
118
    sai.sin_port = htons(port);
119
 
120
    if ((s = socket(HOST -> h_addrtype, SOCK_STREAM, 0)) == -1) {
121
        ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: socket problem");
122
        clean_up(s);
123
        return !_OK;
124
    }
125
 
126
    if(connect(s, (struct sockaddr *)&sai, sizeof(sai)) == -1) {
127
        ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: connect() problem");
128
        clean_up(s);
129
	return !_OK;
130
    }
131

    
132
    return s;
133
}
134
 
135

    
136
/******************************************************************
137
 *imap_do_rfc2060
138
 * Check the U/P against the selected IMAP server
139
 *   according to IMAP v4rev1 RFC
140
 * Return: 1 or 0
141
 ******************************************************************/
142
int imap_do_rfc2060(request_rec *r, char *host, char *username, char *pass, 
143
                    char *cport, int logflag) {
144
    char result[512],buf[512];
145
    int ret=0;
146
    int port;
147

    
148
    port=atoi(cport);
149

    
150
    // Verify that the username and password are of a reasonable length (<100)
151
    if( strlen(username)>100 || strlen(pass)>100 ) {
152
         ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"username/password too long for mod_auth_imap");
153
         printf("Ouch - u/p too long!\n");
154
         return !_OK;
155
    }
156

    
157
    Sock = imap_tcp_open(r, host, port);
158

    
159
    if (!Sock) return !_OK;
160

    
161
    //Just eat the initial response from the server, up to 500 chars.
162
    tcp_gets(Sock,result,500);
163

    
164
    //Send a request for CAPABILITY
165
    memset(buf,0,500);
166
    sprintf(buf,"A001 CAPABILITY\r\n");
167
    tcp_puts(Sock,buf);
168

    
169
    //get the capability line...
170
    tcp_gets(Sock,result,500);
171

    
172
    //get the "A001 OK CAPABILITY completed" line..
173
    tcp_gets(Sock,result,500);
174

    
175
    //skip lines that start with "*"
176
    if (strncmp(result,"* ",2 == 0)) {
177
	tcp_gets(Sock,result,500);
178
    }
179

    
180
    //Verify that it supports the CAPABILITY command
181
    if (strncmp(result,"A001 OK", 7) != 0) {
182
        ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Server does not support imap CAPABILITY.");
183
        ret=!_OK;
184
        clean_up(Sock);
185
        return ret;  //BAIL!
186
    }
187

    
188
    //Log In w/given Username & Password
189
    memset(buf,0,500);
190
    sprintf(buf,"A002 LOGIN %s \"%s\"\r\n", username, pass);
191
    tcp_puts(Sock,buf);
192
    tcp_gets(Sock,result,500);
193

    
194
    if (strncmp(result,"A002 OK",7) == 0) {
195
        if (logflag) {
196
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Verified login for user %s.", username);
197
        }
198

    
199
        ret=_OK;
200
    } else if (strncmp(result,"A002 NO",7) == 0) {
201
        if (logflag) {
202
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Login failed for user %s.", username);
203
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Server said: %s", result);
204
        }
205

    
206
        ret=!_OK;
207
    } else {
208
        //it must have told us BYE and disconnected
209
        if (logflag) {
210
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Premature server disconnect for user %s.", username);
211
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Server said: %s", result);
212
        }
213

    
214
        ret=!_OK;
215
        clean_up(Sock);
216
        return ret;  //BAIL!
217
    }
218

    
219
    //Log out...
220
    memset(buf,0,500);
221
    sprintf(buf,"A003 LOGOUT\r\n");
222
    tcp_puts(Sock,buf);
223

    
224
    //read the BYE line
225
    tcp_gets(Sock,result,500);
226

    
227
    //read the OK LOGOUT
228
    tcp_gets(Sock,result,500);
229

    
230
    if (strncmp(result,"A003 OK",7) == 0) {
231
        if (logflag) {
232
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: OK logout for %s.", username);
233
        }
234
		//don't change the return here - we still need to know if
235
		//the user/pass was good from the LOGIN command!
236
    } else {
237
        if (logflag) {
238
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Error in logout for %s.", username);
239
            ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,"mod_auth_imap: Server said: %s", result);
240
        }
241

    
242
        ret=!_OK;
243
    }
244

    
245
    //close the connection
246
    clean_up(Sock);
247

    
248
    return ret;
249
}
250

    
251
 
252
/******************************************************************
253
* auth_config_struct 
254
* The struct with all data passed from .htaccess or httpd.conf
255
 ******************************************************************/
256
typedef struct auth_config_struct {
257
    char *imap_server;
258
    char *imap_port;
259
    char *imap_domain; /* Using email as username if domain provided */
260
    int   imap_log;
261
    int   imap_authoritative;
262
    int   imap_enabled;
263
} imap_config_rec;
264

    
265

    
266
/******************************************************************
267
 * Defaults for the .htaccess options
268
 ******************************************************************/
269
static void *create_imap_dir_config (apr_pool_t *p, char *d)
270
{
271
    imap_config_rec *sec =
272
	(imap_config_rec *) apr_palloc (p, sizeof(imap_config_rec));
273
    sec->imap_server = "127.0.0.1";     /* localhost is server by default */
274
    sec->imap_port = "143";	/* port is 143 by default */
275
    sec->imap_domain = "";
276
    sec->imap_log = 0; /* Logging is off by default */
277
    sec->imap_authoritative = 1; /* keep the fortress secure by default */
278
    sec->imap_enabled = 0; /* disable this authentication by defualt */
279
    return sec;
280
}
281

    
282

    
283
/*******************************************************************************
284
 * .htaccess and httpd.conf configuration options
285
 *******************************************************************************/
286
static command_rec imap_cmds[] = {
287
    AP_INIT_TAKE1("Auth_IMAP_Server", ap_set_string_slot,
288
     (void *)APR_OFFSETOF(imap_config_rec, imap_server), OR_AUTHCFG, 
289
     "IMAP server . " ),
290

    
291
    AP_INIT_TAKE1("Auth_IMAP_Port", ap_set_string_slot,
292
     (void *)APR_OFFSETOF(imap_config_rec, imap_port), OR_AUTHCFG, 
293
     "IMAP port . " ),
294

    
295
    AP_INIT_TAKE1("Auth_IMAP_Domain", ap_set_string_slot,
296
     (void *)APR_OFFSETOF(imap_config_rec, imap_domain), OR_AUTHCFG, 
297
     "IMAP domain . " ),
298

    
299
    AP_INIT_FLAG("Auth_IMAP_Log", ap_set_flag_slot,
300
     (void *)APR_OFFSETOF(imap_config_rec, imap_log), OR_AUTHCFG, 
301
     "Set to 'yes' to enable some logging in the Apache ErrorLog" ),
302

    
303
    AP_INIT_FLAG("Auth_IMAP_Authoritative", ap_set_flag_slot,
304
     (void *)APR_OFFSETOF(imap_config_rec, imap_authoritative), OR_AUTHCFG, 
305
     "Set to 'no' to allow access control to be passed along to lower modules if the UserID is not known to this module" ),
306

    
307
    AP_INIT_FLAG("Auth_IMAP_Enabled", ap_set_flag_slot, 
308
     (void *)APR_OFFSETOF(imap_config_rec, imap_enabled), OR_AUTHCFG, 
309
     "on|off - determines if IMAP authentication is enabled; default is off" ),
310

    
311
    { NULL }
312
};
313

    
314

    
315

    
316

    
317

    
318

    
319
module AP_MODULE_DECLARE_DATA auth_imap_module;
320

    
321

    
322
/*******************************************************************************
323
 * imap_authenticate_basic_user  -    the main function
324
 *	connects remotely, verifies if the user/pass combo is correct
325
 *	returns "OK", "DECLINED", or "HTTP_UNAUTHORIZED" (see defines)
326
 *******************************************************************************/
327
static int imap_authenticate_basic_user (request_rec *r)
328
{
329
    imap_config_rec *sec =
330
      (imap_config_rec *)ap_get_module_config (r->per_dir_config, &auth_imap_module);
331
    int res,i,u;
332
    char *server = sec->imap_server;
333
    char *port = sec->imap_port;
334
    char *domain = sec->imap_domain;
335
    char *email = NULL;
336
    char *user = r->user;
337
    const char *sent_pw;
338

    
339
    /* is this module disabled? */
340
    if (!sec->imap_enabled) return DECLINED;
341
 
342
    if ((res = ap_get_basic_auth_pw (r, &sent_pw))) return res;
343

    
344
    if(!sec->imap_server) return DECLINED;
345

    
346
    /* Email as username */
347
    if (domain && *domain) {
348
        u = strlen(domain) + strlen(r->user) + 2;
349
        email = malloc(u);
350
        snprintf(email, u, "%s@%s", r->user, domain);
351
        user = email;
352
    }
353

    
354
    /* send IMAP authen, server port, username, sent_pw */
355
    i=imap_do_rfc2060(r,server,user,sent_pw,port,sec->imap_log);    
356
    if (email != NULL)
357
        free(email);
358

    
359
    if (i==1) return (OK);
360

    
361
    ap_note_basic_auth_failure(r);
362
    return HTTP_UNAUTHORIZED;
363
}
364
    
365

    
366
/*******************************************************************************
367
 * imap_check_user_access
368
 *	checks the username against the allowed users in the .htaccess or httpd.conf
369
 *	returns "OK", "DECLINED", or "HTTP_UNAUTHORIZED" (see defines)
370
 *******************************************************************************/    
371
static int imap_check_user_access (request_rec *r) {
372
    imap_config_rec *sec =
373
     (imap_config_rec *)ap_get_module_config (r->per_dir_config, &auth_imap_module);
374
    char *user = r->user;
375
    int m = r->method_number;
376
    int method_restricted = 0;
377
    register int x;
378
    const char *t, *w;
379
    const apr_array_header_t *reqs_arr = ap_requires (r);
380
    require_line *reqs;
381

    
382
    if (!reqs_arr)
383
        return (OK);
384
    reqs = (require_line *)reqs_arr->elts;
385
  
386
    for(x=0; x < reqs_arr->nelts; x++) {
387
      
388
	if (! (reqs[x].method_mask & (1 << m))) continue;
389
	
390
	method_restricted = 1;
391

    
392
        t = reqs[x].requirement;
393
        w = ap_getword(r->pool, &t, ' ');
394
        if(!strcmp(w,"valid-user"))
395
            return OK;
396
        if(!strcmp(w,"user")) {
397
            while(t[0]) {
398
                w = ap_getword_conf (r->pool, &t);
399
                if(!strcmp(user,w))
400
                    return OK;
401
            }
402
        }
403
	else if(!strcmp(w,"group"))
404
		return DECLINED;
405
    }
406
    
407
    if (!method_restricted)
408
      return OK;
409

    
410
    if (!(sec->imap_authoritative))
411
      return DECLINED;
412

    
413
    ap_note_basic_auth_failure (r);
414
    return HTTP_UNAUTHORIZED;
415
}
416

    
417
static void imap_register_hooks(apr_pool_t *p) {
418
    ap_hook_check_user_id(imap_authenticate_basic_user, NULL, NULL, 
419
                          APR_HOOK_MIDDLE);
420
    ap_hook_auth_checker(imap_check_user_access, NULL, NULL, APR_HOOK_MIDDLE);
421
}
422

    
423
/*******************************************************************************
424
 * module function definitions
425
 *	defines the functions called by apache for this module...
426
 *******************************************************************************/
427
module AP_MODULE_DECLARE_DATA auth_imap_module = {
428
   STANDARD20_MODULE_STUFF,
429
   create_imap_dir_config,       /* dir config creater */
430
   NULL,			/* dir merger --- default is to override */
431
   NULL,			/* server config */
432
   NULL,			/* merge server config */
433
   imap_cmds,                    /* command table */
434
   imap_register_hooks		/* register hooks */
435
};
436

    
(3-3/4)