/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2009-01-04 22:15:01 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090104221501-23vutw4cnmb575rq
* debian/mandos.README.Debian: Updated to reflect code changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
 
36
36
import SocketServer
37
37
import socket
38
 
import optparse
 
38
from optparse import OptionParser
39
39
import datetime
40
40
import errno
41
41
import gnutls.crypto
66
66
import ctypes
67
67
import ctypes.util
68
68
 
69
 
version = "1.0.10"
 
69
version = "1.0.2"
70
70
 
71
71
logger = logging.Logger('mandos')
72
72
syslogger = (logging.handlers.SysLogHandler
73
73
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
74
74
              address = "/dev/log"))
75
75
syslogger.setFormatter(logging.Formatter
76
 
                       ('Mandos [%(process)d]: %(levelname)s:'
77
 
                        ' %(message)s'))
 
76
                       ('Mandos: %(levelname)s: %(message)s'))
78
77
logger.addHandler(syslogger)
79
78
 
80
79
console = logging.StreamHandler()
81
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
82
 
                                       ' %(levelname)s: %(message)s'))
 
80
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
 
81
                                       ' %(message)s'))
83
82
logger.addHandler(console)
84
83
 
85
84
class AvahiError(Exception):
114
113
    """
115
114
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
116
115
                 servicetype = None, port = None, TXT = None,
117
 
                 domain = "", host = "", max_renames = 32768,
118
 
                 protocol = avahi.PROTO_UNSPEC):
 
116
                 domain = "", host = "", max_renames = 32768):
119
117
        self.interface = interface
120
118
        self.name = name
121
119
        self.type = servicetype
125
123
        self.host = host
126
124
        self.rename_count = 0
127
125
        self.max_renames = max_renames
128
 
        self.protocol = protocol
129
126
    def rename(self):
130
127
        """Derived from the Avahi example code"""
131
128
        if self.rename_count >= self.max_renames:
160
157
                     service.name, service.type)
161
158
        group.AddService(
162
159
                self.interface,         # interface
163
 
                self.protocol,          # protocol
 
160
                avahi.PROTO_INET6,      # protocol
164
161
                dbus.UInt32(0),         # flags
165
162
                self.name, self.type,
166
163
                self.domain, self.host,
181
178
class Client(dbus.service.Object):
182
179
    """A representation of a client host served by this server.
183
180
    Attributes:
184
 
    name:       string; from the config file, used in log messages and
185
 
                        D-Bus identifiers
 
181
    name:       string; from the config file, used in log messages
186
182
    fingerprint: string (40 or 32 hexadecimal digits); used to
187
183
                 uniquely identify the client
188
184
    secret:     bytestring; sent verbatim (over TLS) to client
205
201
                     client lives.  %() expansions are done at
206
202
                     runtime with vars(self) as dict, so that for
207
203
                     instance %(name)s can be used in the command.
208
 
    current_checker_command: string; current running checker_command
209
204
    use_dbus: bool(); Whether to provide D-Bus interface and signals
210
205
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
211
206
    """
230
225
        if config is None:
231
226
            config = {}
232
227
        logger.debug(u"Creating client %r", self.name)
233
 
        self.use_dbus = False   # During __init__
 
228
        self.use_dbus = use_dbus
 
229
        if self.use_dbus:
 
230
            self.dbus_object_path = (dbus.ObjectPath
 
231
                                     ("/Mandos/clients/"
 
232
                                      + self.name.replace(".", "_")))
 
233
            dbus.service.Object.__init__(self, bus,
 
234
                                         self.dbus_object_path)
234
235
        # Uppercase and remove spaces from fingerprint for later
235
236
        # comparison purposes with return value from the fingerprint()
236
237
        # function
260
261
        self.disable_initiator_tag = None
261
262
        self.checker_callback_tag = None
262
263
        self.checker_command = config["checker"]
263
 
        self.current_checker_command = None
264
 
        self.last_connect = None
265
 
        # Only now, when this client is initialized, can it show up on
266
 
        # the D-Bus
267
 
        self.use_dbus = use_dbus
268
 
        if self.use_dbus:
269
 
            self.dbus_object_path = (dbus.ObjectPath
270
 
                                     ("/clients/"
271
 
                                      + self.name.replace(".", "_")))
272
 
            dbus.service.Object.__init__(self, bus,
273
 
                                         self.dbus_object_path)
274
264
    
275
265
    def enable(self):
276
266
        """Start this client's checker and timeout hooks"""
329
319
            # Emit D-Bus signal
330
320
            self.PropertyChanged(dbus.String(u"checker_running"),
331
321
                                 dbus.Boolean(False, variant_level=1))
332
 
        if os.WIFEXITED(condition):
333
 
            exitstatus = os.WEXITSTATUS(condition)
334
 
            if exitstatus == 0:
335
 
                logger.info(u"Checker for %(name)s succeeded",
336
 
                            vars(self))
337
 
                self.checked_ok()
338
 
            else:
339
 
                logger.info(u"Checker for %(name)s failed",
340
 
                            vars(self))
 
322
        if (os.WIFEXITED(condition)
 
323
            and (os.WEXITSTATUS(condition) == 0)):
 
324
            logger.info(u"Checker for %(name)s succeeded",
 
325
                        vars(self))
341
326
            if self.use_dbus:
342
327
                # Emit D-Bus signal
343
 
                self.CheckerCompleted(dbus.Int16(exitstatus),
344
 
                                      dbus.Int64(condition),
 
328
                self.CheckerCompleted(dbus.Boolean(True),
 
329
                                      dbus.UInt16(condition),
345
330
                                      dbus.String(command))
346
 
        else:
 
331
            self.bump_timeout()
 
332
        elif not os.WIFEXITED(condition):
347
333
            logger.warning(u"Checker for %(name)s crashed?",
348
334
                           vars(self))
349
335
            if self.use_dbus:
350
336
                # Emit D-Bus signal
351
 
                self.CheckerCompleted(dbus.Int16(-1),
352
 
                                      dbus.Int64(condition),
 
337
                self.CheckerCompleted(dbus.Boolean(False),
 
338
                                      dbus.UInt16(condition),
 
339
                                      dbus.String(command))
 
340
        else:
 
341
            logger.info(u"Checker for %(name)s failed",
 
342
                        vars(self))
 
343
            if self.use_dbus:
 
344
                # Emit D-Bus signal
 
345
                self.CheckerCompleted(dbus.Boolean(False),
 
346
                                      dbus.UInt16(condition),
353
347
                                      dbus.String(command))
354
348
    
355
 
    def checked_ok(self):
 
349
    def bump_timeout(self):
356
350
        """Bump up the timeout for this client.
357
351
        This should only be called when the client has been seen,
358
352
        alive and well.
381
375
        # checkers alone, the checker would have to take more time
382
376
        # than 'timeout' for the client to be declared invalid, which
383
377
        # is as it should be.
384
 
        
385
 
        # If a checker exists, make sure it is not a zombie
386
 
        if self.checker is not None:
387
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
388
 
            if pid:
389
 
                logger.warning("Checker was a zombie")
390
 
                gobject.source_remove(self.checker_callback_tag)
391
 
                self.checker_callback(pid, status,
392
 
                                      self.current_checker_command)
393
 
        # Start a new checker if needed
394
378
        if self.checker is None:
395
379
            try:
396
380
                # In case checker_command has exactly one % operator
406
390
                    logger.error(u'Could not format string "%s":'
407
391
                                 u' %s', self.checker_command, error)
408
392
                    return True # Try again later
409
 
                self.current_checker_command = command
410
393
            try:
411
394
                logger.info(u"Starting checker %r for %s",
412
395
                            command, self.name)
427
410
                                             (self.checker.pid,
428
411
                                              self.checker_callback,
429
412
                                              data=command))
430
 
                # The checker may have completed before the gobject
431
 
                # watch was added.  Check for this.
432
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
433
 
                if pid:
434
 
                    gobject.source_remove(self.checker_callback_tag)
435
 
                    self.checker_callback(pid, status, command)
436
413
            except OSError, error:
437
414
                logger.error(u"Failed to start subprocess: %s",
438
415
                             error)
471
448
            return now < (self.last_checked_ok + self.timeout)
472
449
    
473
450
    ## D-Bus methods & signals
474
 
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
451
    _interface = u"org.mandos_system.Mandos.Client"
475
452
    
476
 
    # CheckedOK - method
477
 
    CheckedOK = dbus.service.method(_interface)(checked_ok)
478
 
    CheckedOK.__name__ = "CheckedOK"
 
453
    # BumpTimeout - method
 
454
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
 
455
    BumpTimeout.__name__ = "BumpTimeout"
479
456
    
480
457
    # CheckerCompleted - signal
481
 
    @dbus.service.signal(_interface, signature="nxs")
482
 
    def CheckerCompleted(self, exitcode, waitstatus, command):
 
458
    @dbus.service.signal(_interface, signature="bqs")
 
459
    def CheckerCompleted(self, success, condition, command):
483
460
        "D-Bus signal"
484
461
        pass
485
462
    
526
503
                dbus.String("checker_running"):
527
504
                    dbus.Boolean(self.checker is not None,
528
505
                                 variant_level=1),
529
 
                dbus.String("object_path"):
530
 
                    dbus.ObjectPath(self.dbus_object_path,
531
 
                                    variant_level=1)
532
506
                }, signature="sv")
533
507
    
534
508
    # IsStillValid - method
617
591
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
618
592
        # ...do the normal thing
619
593
        return session.peer_certificate
620
 
    list_size = ctypes.c_uint(1)
 
594
    list_size = ctypes.c_uint()
621
595
    cert_list = (gnutls.library.functions
622
596
                 .gnutls_certificate_get_peers
623
597
                 (session._c_object, ctypes.byref(list_size)))
624
 
    if not bool(cert_list) and list_size.value != 0:
625
 
        raise gnutls.errors.GNUTLSError("error getting peer"
626
 
                                        " certificate")
627
598
    if list_size.value == 0:
628
599
        return None
629
600
    cert = cert_list[0]
698
669
        # using OpenPGP certificates.
699
670
        
700
671
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
701
 
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
702
 
        #                     "+DHE-DSS"))
 
672
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
673
        #                "+DHE-DSS"))
703
674
        # Use a fallback default, since this MUST be set.
704
675
        priority = self.server.settings.get("priority", "NORMAL")
705
676
        (gnutls.library.functions
713
684
            # Do not run session.bye() here: the session is not
714
685
            # established.  Just abandon the request.
715
686
            return
716
 
        logger.debug(u"Handshake succeeded")
717
687
        try:
718
688
            fpr = fingerprint(peer_certificate(session))
719
689
        except (TypeError, gnutls.errors.GNUTLSError), error:
721
691
            session.bye()
722
692
            return
723
693
        logger.debug(u"Fingerprint: %s", fpr)
724
 
        
725
694
        for c in self.server.clients:
726
695
            if c.fingerprint == fpr:
727
696
                client = c
740
709
            session.bye()
741
710
            return
742
711
        ## This won't work here, since we're in a fork.
743
 
        # client.checked_ok()
 
712
        # client.bump_timeout()
744
713
        sent_size = 0
745
714
        while sent_size < len(client.secret):
746
715
            sent = session.send(client.secret[sent_size:])
753
722
 
754
723
class IPv6_TCPServer(SocketServer.ForkingMixIn,
755
724
                     SocketServer.TCPServer, object):
756
 
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
 
725
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
757
726
    Attributes:
758
727
        settings:       Server settings
759
728
        clients:        Set() of Client objects
767
736
        if "clients" in kwargs:
768
737
            self.clients = kwargs["clients"]
769
738
            del kwargs["clients"]
770
 
        if "use_ipv6" in kwargs:
771
 
            if not kwargs["use_ipv6"]:
772
 
                self.address_family = socket.AF_INET
773
 
            del kwargs["use_ipv6"]
774
739
        self.enabled = False
775
740
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
776
741
    def server_bind(self):
790
755
                                 u" bind to interface %s",
791
756
                                 self.settings["interface"])
792
757
                else:
793
 
                    raise
 
758
                    raise error
794
759
        # Only bind(2) the socket if we really need to.
795
760
        if self.server_address[0] or self.server_address[1]:
796
761
            if not self.server_address[0]:
797
 
                if self.address_family == socket.AF_INET6:
798
 
                    any_address = "::" # in6addr_any
799
 
                else:
800
 
                    any_address = socket.INADDR_ANY
801
 
                self.server_address = (any_address,
 
762
                in6addr_any = "::"
 
763
                self.server_address = (in6addr_any,
802
764
                                       self.server_address[1])
803
765
            elif not self.server_address[1]:
804
766
                self.server_address = (self.server_address[0],
820
782
 
821
783
def string_to_delta(interval):
822
784
    """Parse a string and return a datetime.timedelta
823
 
    
 
785
 
824
786
    >>> string_to_delta('7d')
825
787
    datetime.timedelta(7)
826
788
    >>> string_to_delta('60s')
927
889
 
928
890
 
929
891
def main():
930
 
    parser = optparse.OptionParser(version = "%%prog %s" % version)
 
892
    parser = OptionParser(version = "%%prog %s" % version)
931
893
    parser.add_option("-i", "--interface", type="string",
932
894
                      metavar="IF", help="Bind to interface IF")
933
895
    parser.add_option("-a", "--address", type="string",
949
911
                      " files")
950
912
    parser.add_option("--no-dbus", action="store_false",
951
913
                      dest="use_dbus",
952
 
                      help=optparse.SUPPRESS_HELP) # XXX: Not done yet
953
 
    parser.add_option("--no-ipv6", action="store_false",
954
 
                      dest="use_ipv6", help="Do not use IPv6")
 
914
                      help="Do not provide D-Bus system bus"
 
915
                      " interface")
955
916
    options = parser.parse_args()[0]
956
917
    
957
918
    if options.check:
968
929
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
969
930
                        "servicename": "Mandos",
970
931
                        "use_dbus": "True",
971
 
                        "use_ipv6": "True",
972
932
                        }
973
933
    
974
934
    # Parse config file for server-global settings
977
937
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
978
938
    # Convert the SafeConfigParser object to a dict
979
939
    server_settings = server_config.defaults()
980
 
    # Use the appropriate methods on the non-string config options
981
 
    server_settings["debug"] = server_config.getboolean("DEFAULT",
982
 
                                                        "debug")
983
 
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
984
 
                                                           "use_dbus")
985
 
    server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
986
 
                                                           "use_ipv6")
987
 
    if server_settings["port"]:
988
 
        server_settings["port"] = server_config.getint("DEFAULT",
989
 
                                                       "port")
 
940
    # Use getboolean on the boolean config options
 
941
    server_settings["debug"] = (server_config.getboolean
 
942
                                ("DEFAULT", "debug"))
 
943
    server_settings["use_dbus"] = (server_config.getboolean
 
944
                                   ("DEFAULT", "use_dbus"))
990
945
    del server_config
991
946
    
992
947
    # Override the settings from the config file with command line
993
948
    # options, if set.
994
949
    for option in ("interface", "address", "port", "debug",
995
950
                   "priority", "servicename", "configdir",
996
 
                   "use_dbus", "use_ipv6"):
 
951
                   "use_dbus"):
997
952
        value = getattr(options, option)
998
953
        if value is not None:
999
954
            server_settings[option] = value
1003
958
    # For convenience
1004
959
    debug = server_settings["debug"]
1005
960
    use_dbus = server_settings["use_dbus"]
1006
 
    use_dbus = False            # XXX: Not done yet
1007
 
    use_ipv6 = server_settings["use_ipv6"]
1008
961
    
1009
962
    if not debug:
1010
963
        syslogger.setLevel(logging.WARNING)
1019
972
    # Parse config file with clients
1020
973
    client_defaults = { "timeout": "1h",
1021
974
                        "interval": "5m",
1022
 
                        "checker": "fping -q -- %%(host)s",
 
975
                        "checker": "fping -q -- %(host)s",
1023
976
                        "host": "",
1024
977
                        }
1025
978
    client_config = ConfigParser.SafeConfigParser(client_defaults)
1031
984
                                 server_settings["port"]),
1032
985
                                TCP_handler,
1033
986
                                settings=server_settings,
1034
 
                                clients=clients, use_ipv6=use_ipv6)
 
987
                                clients=clients)
1035
988
    pidfilename = "/var/run/mandos.pid"
1036
989
    try:
1037
990
        pidfile = open(pidfilename, "w")
1038
 
    except IOError:
 
991
    except IOError, error:
1039
992
        logger.error("Could not open file %r", pidfilename)
1040
993
    
1041
994
    try:
1042
995
        uid = pwd.getpwnam("_mandos").pw_uid
 
996
    except KeyError:
 
997
        try:
 
998
            uid = pwd.getpwnam("mandos").pw_uid
 
999
        except KeyError:
 
1000
            try:
 
1001
                uid = pwd.getpwnam("nobody").pw_uid
 
1002
            except KeyError:
 
1003
                uid = 65534
 
1004
    try:
1043
1005
        gid = pwd.getpwnam("_mandos").pw_gid
1044
1006
    except KeyError:
1045
1007
        try:
1046
 
            uid = pwd.getpwnam("mandos").pw_uid
1047
1008
            gid = pwd.getpwnam("mandos").pw_gid
1048
1009
        except KeyError:
1049
1010
            try:
1050
 
                uid = pwd.getpwnam("nobody").pw_uid
1051
1011
                gid = pwd.getpwnam("nogroup").pw_gid
1052
1012
            except KeyError:
1053
 
                uid = 65534
1054
1013
                gid = 65534
1055
1014
    try:
 
1015
        os.setuid(uid)
1056
1016
        os.setgid(gid)
1057
 
        os.setuid(uid)
1058
1017
    except OSError, error:
1059
1018
        if error[0] != errno.EPERM:
1060
1019
            raise error
1061
1020
    
1062
 
    # Enable all possible GnuTLS debugging
1063
 
    if debug:
1064
 
        # "Use a log level over 10 to enable all debugging options."
1065
 
        # - GnuTLS manual
1066
 
        gnutls.library.functions.gnutls_global_set_log_level(11)
1067
 
        
1068
 
        @gnutls.library.types.gnutls_log_func
1069
 
        def debug_gnutls(level, string):
1070
 
            logger.debug("GnuTLS: %s", string[:-1])
1071
 
        
1072
 
        (gnutls.library.functions
1073
 
         .gnutls_global_set_log_function(debug_gnutls))
1074
 
    
1075
1021
    global service
1076
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1077
1022
    service = AvahiService(name = server_settings["servicename"],
1078
 
                           servicetype = "_mandos._tcp",
1079
 
                           protocol = protocol)
 
1023
                           servicetype = "_mandos._tcp", )
1080
1024
    if server_settings["interface"]:
1081
1025
        service.interface = (if_nametoindex
1082
1026
                             (server_settings["interface"]))
1093
1037
                            avahi.DBUS_INTERFACE_SERVER)
1094
1038
    # End of Avahi example code
1095
1039
    if use_dbus:
1096
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1040
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
 
1041
                                        bus)
1097
1042
    
1098
1043
    clients.update(Set(Client(name = section,
1099
1044
                              config
1153
1098
        class MandosServer(dbus.service.Object):
1154
1099
            """A D-Bus proxy object"""
1155
1100
            def __init__(self):
1156
 
                dbus.service.Object.__init__(self, bus, "/")
1157
 
            _interface = u"se.bsnet.fukt.Mandos"
1158
 
            
 
1101
                dbus.service.Object.__init__(self, bus,
 
1102
                                             "/Mandos")
 
1103
            _interface = u"org.mandos_system.Mandos"
 
1104
 
1159
1105
            @dbus.service.signal(_interface, signature="oa{sv}")
1160
1106
            def ClientAdded(self, objpath, properties):
1161
1107
                "D-Bus signal"
1162
1108
                pass
1163
 
            
1164
 
            @dbus.service.signal(_interface, signature="os")
1165
 
            def ClientRemoved(self, objpath, name):
 
1109
 
 
1110
            @dbus.service.signal(_interface, signature="o")
 
1111
            def ClientRemoved(self, objpath):
1166
1112
                "D-Bus signal"
1167
1113
                pass
1168
 
            
 
1114
 
1169
1115
            @dbus.service.method(_interface, out_signature="ao")
1170
1116
            def GetAllClients(self):
1171
 
                "D-Bus method"
1172
1117
                return dbus.Array(c.dbus_object_path for c in clients)
1173
 
            
 
1118
 
1174
1119
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1175
1120
            def GetAllClientsWithProperties(self):
1176
 
                "D-Bus method"
1177
1121
                return dbus.Dictionary(
1178
1122
                    ((c.dbus_object_path, c.GetAllProperties())
1179
1123
                     for c in clients),
1180
1124
                    signature="oa{sv}")
1181
 
            
 
1125
 
1182
1126
            @dbus.service.method(_interface, in_signature="o")
1183
1127
            def RemoveClient(self, object_path):
1184
 
                "D-Bus method"
1185
1128
                for c in clients:
1186
1129
                    if c.dbus_object_path == object_path:
1187
1130
                        clients.remove(c)
1189
1132
                        c.use_dbus = False
1190
1133
                        c.disable()
1191
1134
                        # Emit D-Bus signal
1192
 
                        self.ClientRemoved(object_path, c.name)
 
1135
                        self.ClientRemoved(object_path)
1193
1136
                        return
1194
1137
                raise KeyError
1195
 
            
 
1138
            @dbus.service.method(_interface)
 
1139
            def Quit(self):
 
1140
                main_loop.quit()
 
1141
 
1196
1142
            del _interface
1197
 
        
 
1143
    
1198
1144
        mandos_server = MandosServer()
1199
1145
    
1200
1146
    for client in clients:
1209
1155
    
1210
1156
    # Find out what port we got
1211
1157
    service.port = tcp_server.socket.getsockname()[1]
1212
 
    if use_ipv6:
1213
 
        logger.info(u"Now listening on address %r, port %d,"
1214
 
                    " flowinfo %d, scope_id %d"
1215
 
                    % tcp_server.socket.getsockname())
1216
 
    else:                       # IPv4
1217
 
        logger.info(u"Now listening on address %r, port %d"
1218
 
                    % tcp_server.socket.getsockname())
 
1158
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
1159
                u" scope_id %d" % tcp_server.socket.getsockname())
1219
1160
    
1220
1161
    #service.interface = tcp_server.socket.getsockname()[3]
1221
1162
    
1241
1182
        sys.exit(1)
1242
1183
    except KeyboardInterrupt:
1243
1184
        if debug:
1244
 
            print >> sys.stderr
1245
 
        logger.debug("Server received KeyboardInterrupt")
1246
 
    logger.debug("Server exiting")
 
1185
            print
1247
1186
 
1248
1187
if __name__ == '__main__':
1249
1188
    main()