1
# Tunnel - TODO
2
#
3
# (C) 2010 Luke Slater, Steve 'Ashcrow' Milner
4
#
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Affero General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU Affero General Public License for more details.
14
#
15
# You should have received a copy of the GNU Affero General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
"""
18
Authenticate XMPP user.
19
"""
20
21
import logging
22
import os
23
import struct
24
import sys
25
26
from django.core.management.base import BaseCommand
27
from django.contrib.auth.models import User, check_password
28
from django.conf import settings
29
30
31
class Command(BaseCommand):
32
    """
33
    Acts as an auth service for ejabberd through ejabberds external auth
34
    option. See contrib/ejabberd/ejabber.cfg for an example configuration.
35
    """
36
37
    help = "Runs an ejabberd auth service"
38
39
    def __init__(self, *args, **kwargs):
40
        """
41
        Creation of the ejabberd atuh bridge service.
42
43
        :Parameters:
44
           - `args`: all non-keyword arguments
45
           - `kwargs`: all keyword arguments
46
        """
47
        BaseCommand.__init__(self, *args, **kwargs)
48
        try:
49
            log_level = int(settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG_LEVEL)
50
        except:
51
            log_level = logging.INFO
52
        # If we can write to the log do so, else fail back to the console
53
        if os.access(settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG, os.W_OK):
54
            logging.basicConfig(
55
                level=log_level,
56
                format='%(asctime)s %(levelname)s %(message)s',
57
                filename=settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG,
58
                filemode='a')
59
        else:
60
            logging.basicConfig(
61
                level=log_level,
62
                format='%(asctime)s %(levelname)s %(message)s',
63
                stream=sys.stderr)
64
            logging.warn(('Could not write to ' +
65
                settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG +
66
                '. Falling back to stderr ...'))
67
        logging.info(('ejabberd_auth_bridge process started' +
68
            ' (more than one is common)'))
69
70
    def _generate_response(self, success=False):
71
        """
72
        Creates and sends a response back to the ejabberd server.
73
74
        :Parameters
75
           - `success`: boolean if we should respond successful or not
76
        """
77
        logging.debug('Generating a response ...')
78
        result = 0
79
        if success:
80
            result = 1
81
        logging.debug('Sending response of ' + str(result))
82
        sys.stdout.write(struct.pack('>hh', 2, result))
83
        sys.stdout.flush()
84
        logging.debug('Response of ' + str(result) + ' sent')
85
86
    def _handle_isuser(self, username):
87
        """
88
        Handles the isuer ejabberd command.
89
90
        :Parameters:
91
           - `username`: the user name to verify exists
92
        """
93
        try:
94
            user = User.objects.get(username=username)
95
            logging.debug('Found user with username ' + str(username))
96
            self._generate_response(True)
97
        except User.DoesNotExist:
98
            logging.debug('No username ' + str(username))
99
            self._generate_response(False)
100
        except Exception, ex:
101
            logging.fatal('Unhandled error: ' + str(ex))
102
103
    def _handle_auth(self, username, password):
104
        """
105
        Handles authentication of the user.
106
107
        :Parameters:
108
           - `username`: the username to verify
109
           - `password`: the password to verify with the user
110
        """
111
        logging.debug('Starting auth check')
112
        try:
113
            user = User.objects.get(username=username)
114
            logging.debug('Found username ' + str(username))
115
            if check_password(password, user.password):
116
                self._generate_response(True)
117
                logging.info(username + ' has logged in')
118
                profile = user.get_profile()
119
                # Tunnel specific .....
120
                if not profile.logged_in:
121
                    try:
122
                        profile.logged_in = True
123
                        profile.save()
124
                    except Exception, ex:
125
                        # Couldn't update the profile ...
126
                        logging.warn("Could not save profile:" + str(ex))
127
                    logging.debug('Updated ' + username + ' profile status')
128
                # End Tunnel specific
129
            else:
130
                self._generate_response(False)
131
                logging.info(username + ' failed auth')
132
        except User.DoesNotExist:
133
            logging.info(username + ' is not a valid user')
134
            self._generate_response(False)
135
        except Exception, ex:
136
            logging.fatal('Unhandled error: ' + str(ex))
137
138
    def handle(self, **options):
139
        """
140
        How to check if a user is valid
141
142
        :Parameters:
143
           - `options`: keyword arguments
144
        """
145
        try:
146
            # Serve forever
147
            while True:
148
                logging.debug('Loop restarting')
149
                # Verify the information checks out
150
                try:
151
                    logging.debug('Waiting for data')
152
                    length = sys.stdin.read(2)
153
                    size = struct.unpack('>h', length)[0]
154
                    logging.debug('Data is of size ' + str(size))
155
                    logging.debug('Attempting to read the data')
156
                    input = sys.stdin.read(size).split(':')
157
                    logging.debug('Input: ' + str(input))
158
                    operation = input.pop(0)
159
                except Exception, ex:
160
                    # It wasn't even in the right format if we get here ...
161
                    logging.debug(
162
                        "Data was not in the right format: " + str(ex))
163
                    self._generate_response(False)
164
                    continue
165
                logging.debug('Checking operation ...')
166
                if operation == 'auth':
167
                    logging.info(
168
                        'Auth request being processed for ' + input[1])
169
                    self._handle_auth(input[0], input[2])
170
                elif operation == 'isuser':
171
                    logger.info('Asked if ' + input[0] + ' is a user')
172
                    self._handle_isuser()
173
                elif operation == 'setpass':
174
                    logger.info('Asked if to change password for ' + input[0])
175
                    # Do not support this (Tunnel specific)
176
                    self._generate_repsonse(False)
177
                else:
178
                    logging.warn('Operation "' + operation + '" unknown!')
179
        except KeyboardInterrupt:
180
            logging.debug("Received Keyboard Interrupt")
181
            raise SystemExit(0)
182
183
    def __del__(self):
184
        """
185
        What to do when we are shut off.
186
        """
187
        logging.info('ejabberd_auth_bridge process stopped')