From 641204dfbdb9fc870cdd2e7f9e3169a44693e7bf Mon Sep 17 00:00:00 2001 From: Maxim Mamontov Date: Sun, 7 Nov 2010 11:20:26 +0200 Subject: [PATCH] =?utf8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?utf8?q?=D0=B8=D0=B5=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D0=B8=D0=BA?= =?utf8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 + include/Makefile | 14 + include/admin_conf.h | 77 + include/admin_conf.inc.h | 35 + include/base_auth.h | 47 + include/base_db.h | 75 + include/base_plugin.h | 71 + include/base_settings.h | 55 + include/base_store.h | 121 + include/corp_conf.h | 10 + include/ia_packets.h | 307 ++ include/ibpp.h | 929 ++++ include/lp2_blocks.h | 82 + include/mimetype.h | 37 + include/noncopyable.h | 14 + include/notifer.h | 29 + include/os_int.h | 61 + include/rad_packets.h | 41 + include/raw_ip_packet.h | 214 + include/resetable.h | 98 + include/rs_packets.h | 36 + include/service_conf.h | 13 + include/stdstring.h | 3737 +++++++++++++++++ include/stg_common.h | 23 + include/stg_comp_stat.h | 41 + include/stg_const.h | 87 + include/stg_int.h | 11 + include/stg_message.h | 54 + include/tariff_conf.h | 225 + include/user_conf.h | 158 + include/user_ips.h | 280 ++ include/user_stat.h | 173 + include/user_traff.h | 111 + include/utime.h | 179 + include/version.h | 26 + include/vpn_stg_packets.h | 58 + projects/convertor/Makefile | 74 + projects/convertor/build | 259 ++ projects/convertor/convertor.conf | 66 + projects/convertor/main.cpp | 444 ++ projects/convertor/settings.cpp | 267 ++ projects/convertor/settings.h | 78 + projects/make_tarball/get_from_cvs | 58 + projects/make_tarball/mt.sh | 59 + projects/rlm_stg/Makefile | 81 + projects/rlm_stg/build | 135 + projects/rlm_stg/build_check.c | 1 + projects/rlm_stg/conf.h | 38 + projects/rlm_stg/conffile.h | 126 + projects/rlm_stg/event.h | 57 + projects/rlm_stg/libradius.h | 470 +++ projects/rlm_stg/modules.h | 91 + projects/rlm_stg/radiusd.h | 633 +++ projects/rlm_stg/realms.h | 142 + projects/rlm_stg/rlm_stg.cpp | 348 ++ projects/rlm_stg/stats.h | 104 + projects/rlm_stg/stg_client.cpp | 320 ++ projects/rlm_stg/stg_client.h | 98 + projects/rscriptd/Makefile | 94 + projects/rscriptd/build | 144 + projects/rscriptd/listener.cpp | 499 +++ projects/rscriptd/listener.h | 148 + projects/rscriptd/main.cpp | 439 ++ projects/rscriptd/pidfile.cpp | 53 + projects/rscriptd/pidfile.h | 44 + projects/rscriptd/rscriptd.conf | 8 + projects/sgauth/Makefile | 97 + projects/sgauth/build | 142 + projects/sgauth/main.cpp | 570 +++ projects/sgauth/make_css.sh | 10 + projects/sgauth/readme | 13 + projects/sgauth/sgauth.conf | 37 + projects/sgauth/sgauth.css | 78 + projects/sgauth/web.cpp | 446 ++ projects/sgauth/web.h | 101 + projects/sgconf/CHANGES | 0 projects/sgconf/Makefile | 99 + projects/sgconf/README.txt | 3 + projects/sgconf/build | 161 + projects/sgconf/common_sg.cpp | 479 +++ projects/sgconf/common_sg.h | 64 + projects/sgconf/main.cpp | 1095 +++++ projects/sgconf/parser.cpp | 128 + projects/sgconf/request.h | 104 + projects/sgconf/sg_error_codes.h | 51 + projects/sgconf/sgconfg | 4 + projects/sgconf/sgconfs | 4 + projects/sgconf/sginfo.cpp | 1084 +++++ projects/sgconf/version_sg.h | 35 + projects/stargazer/.#Makefile.1.45 | 141 + projects/stargazer/BUGS | 2 + projects/stargazer/CHANGES | 9 + projects/stargazer/Makefile | 140 + projects/stargazer/README | 6 + projects/stargazer/TODO | 7 + projects/stargazer/actions.h | 87 + projects/stargazer/actions.inl.h | 111 + projects/stargazer/admin.cpp | 115 + projects/stargazer/admin.h | 78 + projects/stargazer/admins.cpp | 321 ++ projects/stargazer/admins.h | 88 + projects/stargazer/build | 413 ++ projects/stargazer/devbuild | 5 + projects/stargazer/eventloop.cpp | 109 + projects/stargazer/eventloop.h | 63 + .../inst/freebsd/etc/stargazer/OnChange | 6 + .../inst/freebsd/etc/stargazer/OnConnect | 22 + .../inst/freebsd/etc/stargazer/OnDisconnect | 27 + .../inst/freebsd/etc/stargazer/OnUserAdd | 12 + .../inst/freebsd/etc/stargazer/OnUserDel | 5 + .../inst/freebsd/etc/stargazer/rules | 3 + .../inst/freebsd/etc/stargazer/stargazer.conf | 396 ++ .../linux/etc/init.d/stargazer.gentoo.2007 | 31 + .../inst/linux/etc/init.d/stargazer.suse.9.3 | 70 + .../linux/etc/init.d/stargazer.ubuntu.7.10 | 152 + .../inst/linux/etc/stargazer/OnChange | 6 + .../inst/linux/etc/stargazer/OnConnect | 22 + .../inst/linux/etc/stargazer/OnDisconnect | 27 + .../inst/linux/etc/stargazer/OnUserAdd | 12 + .../inst/linux/etc/stargazer/OnUserDel | 5 + .../stargazer/inst/linux/etc/stargazer/rules | 3 + .../inst/linux/etc/stargazer/stargazer.conf | 394 ++ .../inst/var/00-alter-01.postgresql.sql | 54 + projects/stargazer/inst/var/00-alter-01.sql | 25 + .../inst/var/00-base-00.postgresql.sql | 638 +++ projects/stargazer/inst/var/00-base-00.sql | 731 ++++ projects/stargazer/inst/var/00-mysql-01.sql | 5 + projects/stargazer/inst/var/base.dia | Bin 0 -> 4546 bytes .../inst/var/stargazer/admins/admin.adm | 8 + .../inst/var/stargazer/tariffs/tariff.tf | 84 + .../inst/var/stargazer/users/test/conf | 19 + .../inst/var/stargazer/users/test/stat | 26 + projects/stargazer/main.cpp | 780 ++++ projects/stargazer/pidfile.cpp | 53 + projects/stargazer/pidfile.h | 44 + projects/stargazer/plugin_runner.cpp | 234 ++ projects/stargazer/plugin_runner.h | 95 + projects/stargazer/plugins/Makefile | 20 + projects/stargazer/plugins/Makefile.in | 43 + .../plugins/authorization/ao/Makefile | 14 + .../stargazer/plugins/authorization/ao/ao.cpp | 348 ++ .../stargazer/plugins/authorization/ao/ao.h | 173 + .../plugins/authorization/inetaccess/Makefile | 16 + .../authorization/inetaccess/antiflood.cpp | 190 + .../authorization/inetaccess/antiflood.h | 71 + .../authorization/inetaccess/inetaccess.cpp | 1840 ++++++++ .../authorization/inetaccess/inetaccess.h | 365 ++ .../plugins/authorization/stress/Makefile | 14 + .../plugins/authorization/stress/stress.cpp | 422 ++ .../plugins/authorization/stress/stress.h | 175 + .../plugins/capture/cap_debug/Makefile | 24 + .../plugins/capture/cap_debug/checksum.c | 47 + .../plugins/capture/cap_debug/checksum.h | 20 + .../plugins/capture/cap_debug/constants.h | 106 + .../plugins/capture/cap_debug/debug_cap.cpp | 519 +++ .../plugins/capture/cap_debug/debug_cap.h | 102 + .../plugins/capture/cap_debug/icmp.c | 154 + .../stargazer/plugins/capture/cap_debug/ip.c | 124 + .../plugins/capture/cap_debug/libpal.h | 130 + .../plugins/capture/cap_debug/misc.c | 41 + .../plugins/capture/cap_debug/packet.c | 161 + .../plugins/capture/cap_debug/socket.c | 163 + .../stargazer/plugins/capture/cap_debug/tcp.c | 154 + .../plugins/capture/cap_debug/types.h | 35 + .../stargazer/plugins/capture/cap_debug/udp.c | 82 + .../stargazer/plugins/capture/cap_nf/Makefile | 15 + .../plugins/capture/cap_nf/cap_nf.cpp | 440 ++ .../stargazer/plugins/capture/cap_nf/cap_nf.h | 135 + .../plugins/capture/divert_freebsd/Makefile | 16 + .../capture/divert_freebsd/divert_cap.cpp | 373 ++ .../capture/divert_freebsd/divert_cap.h | 96 + .../plugins/capture/ether_freebsd/Makefile | 16 + .../capture/ether_freebsd/ether_cap.cpp | 450 ++ .../plugins/capture/ether_freebsd/ether_cap.h | 158 + .../plugins/capture/ether_linux/Makefile | 15 + .../plugins/capture/ether_linux/ether_cap.cpp | 292 ++ .../plugins/capture/ether_linux/ether_cap.h | 94 + .../capture/ipq_linux/.#ipq_cap.cpp.1.3 | 190 + .../plugins/capture/ipq_linux/Makefile | 16 + .../plugins/capture/ipq_linux/ipq_cap.cpp | 211 + .../plugins/capture/ipq_linux/ipq_cap.h | 56 + .../plugins/capture/ipq_linux/libipq.c | 408 ++ .../plugins/capture/ipq_linux/libipq.h | 89 + .../plugins/configuration/rpcconfig/Makefile | 30 + .../rpcconfig/admins_methods.cpp | 256 ++ .../configuration/rpcconfig/admins_methods.h | 92 + .../plugins/configuration/rpcconfig/deps | 362 ++ .../configuration/rpcconfig/info_methods.cpp | 89 + .../configuration/rpcconfig/info_methods.h | 62 + .../rpcconfig/messages_methods.cpp | 93 + .../rpcconfig/messages_methods.h | 27 + .../configuration/rpcconfig/rpcconfig.cpp | 420 ++ .../configuration/rpcconfig/rpcconfig.h | 107 + .../configuration/rpcconfig/tariff_helper.cpp | 92 + .../configuration/rpcconfig/tariff_helper.h | 23 + .../rpcconfig/tariffs_methods.cpp | 192 + .../configuration/rpcconfig/tariffs_methods.h | 103 + .../configuration/rpcconfig/user_helper.cpp | 412 ++ .../configuration/rpcconfig/user_helper.h | 29 + .../configuration/rpcconfig/users_methods.cpp | 511 +++ .../configuration/rpcconfig/users_methods.h | 191 + .../plugins/configuration/rpcconfig/utils.cpp | 78 + .../plugins/configuration/rpcconfig/utils.h | 10 + .../plugins/configuration/sgconfig/Makefile | 22 + .../configuration/sgconfig/configproto.cpp | 299 ++ .../configuration/sgconfig/configproto.h | 143 + .../sgconfig/net_configurator.cpp | 133 + .../configuration/sgconfig/net_configurator.h | 65 + .../plugins/configuration/sgconfig/parser.cpp | 1466 +++++++ .../plugins/configuration/sgconfig/parser.h | 257 ++ .../configuration/sgconfig/parser_admin.cpp | 265 ++ .../configuration/sgconfig/parser_tariff.cpp | 492 +++ .../plugins/configuration/sgconfig/rsconf.cpp | 647 +++ .../configuration/sgconfig/stgconfig.cpp | 245 ++ .../configuration/sgconfig/stgconfig.h | 104 + .../plugins/configuration/sgconfig2/Makefile | 22 + .../configuration/sgconfig2/configproto.cpp | 301 ++ .../configuration/sgconfig2/configproto.h | 144 + .../sgconfig2/net_configurator.cpp | 133 + .../sgconfig2/net_configurator.h | 65 + .../configuration/sgconfig2/parser.cpp | 1466 +++++++ .../plugins/configuration/sgconfig2/parser.h | 257 ++ .../configuration/sgconfig2/parser_admin.cpp | 265 ++ .../configuration/sgconfig2/parser_tariff.cpp | 489 +++ .../plugins/configuration/sgconfig2/proto.h | 61 + .../configuration/sgconfig2/rsconf.cpp | 651 +++ .../configuration/sgconfig2/stgconfig.cpp | 313 ++ .../configuration/sgconfig2/stgconfig.h | 80 + .../plugins/configuration/xrconfig/Makefile | 64 + .../configuration/xrconfig/xrconfig.cpp | 248 ++ .../plugins/configuration/xrconfig/xrconfig.h | 78 + .../stargazer/plugins/other/ping/Makefile | 16 + .../stargazer/plugins/other/ping/ping.cpp | 421 ++ projects/stargazer/plugins/other/ping/ping.h | 155 + .../stargazer/plugins/other/radius/Makefile | 16 + .../stargazer/plugins/other/radius/radius.cpp | 728 ++++ .../stargazer/plugins/other/radius/radius.h | 194 + .../stargazer/plugins/other/rscript/Makefile | 17 + .../plugins/other/rscript/nrmap_parser.cpp | 229 + .../plugins/other/rscript/nrmap_parser.h | 58 + .../plugins/other/rscript/rscript.cpp | 695 +++ .../stargazer/plugins/other/rscript/rscript.h | 237 ++ .../plugins/other/rscript/send_functor.h | 62 + .../plugins/other/rscript/ur_functor.h | 101 + .../stargazer/plugins/other/userstat/Makefile | 29 + .../plugins/other/userstat/datathread.cpp | 193 + .../plugins/other/userstat/datathread.h | 58 + .../plugins/other/userstat/userstat.cpp | 296 ++ .../plugins/other/userstat/userstat.h | 124 + .../plugins/store/db/.libs/pg_driver.la | 1 + projects/stargazer/plugins/store/db/Makefile | 20 + .../stargazer/plugins/store/db/pg_driver.cpp | 107 + .../stargazer/plugins/store/db/pg_driver.h | 27 + .../stargazer/plugins/store/db/pg_driver.la | 35 + .../stargazer/plugins/store/db/pg_driver.lo | 12 + .../plugins/store/db/test_pg_driver.cpp | 147 + .../stargazer/plugins/store/files/Makefile | 14 + .../plugins/store/files/file_store.cpp | 2217 ++++++++++ .../plugins/store/files/file_store.h | 205 + .../stargazer/plugins/store/firebird/Makefile | 23 + .../stargazer/plugins/store/firebird/deps | 180 + .../plugins/store/firebird/firebird_store.cpp | 156 + .../plugins/store/firebird/firebird_store.h | 136 + .../store/firebird/firebird_store_admins.cpp | 258 ++ .../firebird/firebird_store_corporations.cpp | 185 + .../firebird/firebird_store_messages.cpp | 230 + .../firebird/firebird_store_services.cpp | 196 + .../store/firebird/firebird_store_tariffs.cpp | 328 ++ .../store/firebird/firebird_store_users.cpp | 830 ++++ .../store/firebird/firebird_store_utils.cpp | 65 + .../store/firebird/mod_store_firebird.so | Bin 0 -> 1347636 bytes .../plugins/store/mysql/.#mysql_store.cpp.1.3 | 2065 +++++++++ .../store/mysql/.#mysql_store.h.1.1.1.1 | 143 + .../stargazer/plugins/store/mysql/Makefile | 20 + projects/stargazer/plugins/store/mysql/deps | 30 + .../plugins/store/mysql/mod_store_mysql.so | Bin 0 -> 523709 bytes .../plugins/store/mysql/mysql_store.cpp | 2063 +++++++++ .../plugins/store/mysql/mysql_store.h | 144 + .../plugins/store/postgresql/Makefile | 29 + .../store/postgresql/postgresql_store.cpp | 228 + .../store/postgresql/postgresql_store.h | 162 + .../postgresql/postgresql_store_admins.cpp | 452 ++ .../postgresql_store_corporations.cpp | 374 ++ .../postgresql/postgresql_store_messages.cpp | 470 +++ .../postgresql/postgresql_store_services.cpp | 393 ++ .../postgresql/postgresql_store_tariffs.cpp | 578 +++ .../postgresql/postgresql_store_users.cpp | 1565 +++++++ .../postgresql/postgresql_store_utils.cpp | 176 + .../store/postgresql/postgresql_store_utils.h | 11 + projects/stargazer/sandboxstart | 17 + projects/stargazer/script_executer.cpp | 121 + projects/stargazer/scripts/clean_db | 44 + projects/stargazer/scripts/monitor | 66 + projects/stargazer/scripts/shaper/OnConnect | 54 + .../stargazer/scripts/shaper/OnDisconnect | 37 + projects/stargazer/scripts/shaper/Readme.txt | 59 + projects/stargazer/scripts/shaper/shaper.sh | 9 + .../stargazer/scripts/shaper/shaper.stop.sh | 8 + .../scripts/shaper_vpn_radius/Readme | 58 + .../shaper_vpn_radius/firewall/firewall | 94 + .../shaper_vpn_radius/freeradius/clients.conf | 78 + .../shaper_vpn_radius/freeradius/radiusd.conf | 1119 +++++ .../shaper_vpn_radius/ppp/ip-down.d/stg | 3 + .../scripts/shaper_vpn_radius/ppp/ip-up.d/stg | 3 + .../shaper_vpn_radius/ppp/pptpd-options | 97 + .../scripts/shaper_vpn_radius/pptpd.conf | 82 + .../shaper_vpn_radius/radiusclient/servers | 1 + .../shaper_vpn_radius/stargazer/OnChange | 6 + .../shaper_vpn_radius/stargazer/OnConnect | 64 + .../shaper_vpn_radius/stargazer/OnDisconnect | 69 + .../shaper_vpn_radius/stargazer/OnUserAdd | 12 + .../shaper_vpn_radius/stargazer/OnUserDel | 5 + .../scripts/shaper_vpn_radius/stargazer/rules | 3 + .../stargazer/stargazer.conf | 298 ++ projects/stargazer/settings.cpp | 558 +++ projects/stargazer/settings.h | 142 + projects/stargazer/stargazer.vpj | 319 ++ projects/stargazer/stargazer.vpw | 7 + projects/stargazer/stargazer.vpwhistu | 52 + projects/stargazer/startstg | 13 + projects/stargazer/stg_timer.cpp | 120 + projects/stargazer/stg_timer.h | 21 + projects/stargazer/store_loader.cpp | 127 + projects/stargazer/store_loader.h | 62 + projects/stargazer/tariff.cpp | 201 + projects/stargazer/tariff.h | 93 + projects/stargazer/tariffs.cpp | 242 ++ projects/stargazer/tariffs.h | 79 + projects/stargazer/traffcounter.cpp | 935 +++++ projects/stargazer/traffcounter.h | 262 ++ projects/stargazer/user.cpp | 1376 ++++++ projects/stargazer/user.h | 309 ++ projects/stargazer/user_property.cpp | 46 + projects/stargazer/user_property.h | 479 +++ projects/stargazer/users.cpp | 768 ++++ projects/stargazer/users.h | 199 + projects/traffcounter/Makefile | 48 + projects/traffcounter/capturer_tc_iface.h | 26 + projects/traffcounter/configure | 78 + projects/traffcounter/lock.cpp | 17 + projects/traffcounter/lock.h | 17 + projects/traffcounter/logger.cpp | 31 + projects/traffcounter/logger.h | 23 + projects/traffcounter/rf_tester.cpp | 241 ++ projects/traffcounter/rules | 1427 +++++++ projects/traffcounter/rules.cpp | 442 ++ projects/traffcounter/rules.h | 94 + projects/traffcounter/rules_finder.cpp | 155 + projects/traffcounter/rules_finder.h | 56 + projects/traffcounter/rules_tester.cpp | 148 + projects/traffcounter/table | 14 + projects/traffcounter/tc_packets.h | 176 + projects/traffcounter/tc_tester.cpp | 303 ++ projects/traffcounter/test_rules | 12 + projects/traffcounter/test_rules_bad_address | 7 + projects/traffcounter/test_rules_bad_dir | 7 + .../traffcounter/test_rules_bad_dir_prefix | 8 + .../traffcounter/test_rules_bad_dir_range | 8 + projects/traffcounter/test_rules_bad_mask | 7 + projects/traffcounter/test_rules_bad_port | 7 + projects/traffcounter/test_rules_bad_proto | 7 + projects/traffcounter/traffcounter.cpp | 460 ++ projects/traffcounter/traffcounter.h | 165 + projects/traffcounter/user_tc_iface.h | 30 + projects/traffcounter/utils.cpp | 55 + projects/traffcounter/utils.h | 68 + stglibs/Makefile | 22 + stglibs/Makefile.in | 94 + stglibs/common.lib/Makefile | 23 + stglibs/common.lib/common.bpf | 9 + stglibs/common.lib/common.bpr | 130 + stglibs/common.lib/common.cpp | 885 ++++ stglibs/common.lib/common.h | 227 + stglibs/common.lib/debug.c | 49 + stglibs/common.lib/debug.h | 28 + stglibs/common.lib/stg_common.h | 23 + stglibs/common.lib/stg_error.c | 166 + stglibs/common.lib/stg_error.h | 70 + stglibs/common.lib/stg_strptime.cpp | 17 + stglibs/common.lib/test.cpp | 383 ++ stglibs/common.lib/test.h | 53 + stglibs/common_settings.lib/Makefile | 12 + .../common_settings.lib/common_settings.cpp | 128 + stglibs/common_settings.lib/common_settings.h | 68 + stglibs/conffiles.lib/Makefile | 12 + stglibs/conffiles.lib/conffiles.cpp | 455 ++ stglibs/conffiles.lib/conffiles.h | 94 + stglibs/crypto.lib/Makefile | 14 + stglibs/crypto.lib/ag_md5.cpp | 457 ++ stglibs/crypto.lib/ag_md5.h | 30 + stglibs/crypto.lib/blowfish.cpp | 503 +++ stglibs/crypto.lib/blowfish.h | 29 + stglibs/crypto.lib/crypto.bpf | 9 + stglibs/crypto.lib/crypto.bpr | 144 + stglibs/dotconfpp.lib/Makefile | 14 + stglibs/dotconfpp.lib/dotconfpp.cpp | 661 +++ stglibs/dotconfpp.lib/dotconfpp.h | 122 + stglibs/dotconfpp.lib/mempool.cpp | 116 + stglibs/dotconfpp.lib/mempool.h | 60 + stglibs/hostallow.lib/Makefile | 12 + stglibs/hostallow.lib/hostallow.cpp | 294 ++ stglibs/hostallow.lib/hostallow.h | 83 + stglibs/ia_auth_c.lib/Makefile | 16 + stglibs/ia_auth_c.lib/ia_auth_c.bpf | 9 + stglibs/ia_auth_c.lib/ia_auth_c.bpr | 129 + stglibs/ia_auth_c.lib/ia_auth_c.cpp | 868 ++++ stglibs/ia_auth_c.lib/ia_auth_c.h | 209 + stglibs/ibpp.lib/Makefile | 37 + stglibs/ibpp.lib/_dpb.cpp | 120 + stglibs/ibpp.lib/_ibpp.cpp | 367 ++ stglibs/ibpp.lib/_ibpp.h | 1414 +++++++ stglibs/ibpp.lib/_ibs.cpp | 109 + stglibs/ibpp.lib/_rb.cpp | 205 + stglibs/ibpp.lib/_spb.cpp | 135 + stglibs/ibpp.lib/_tpb.cpp | 100 + stglibs/ibpp.lib/all_in_one.cpp | 56 + stglibs/ibpp.lib/array.cpp | 1046 +++++ stglibs/ibpp.lib/blob.cpp | 381 ++ stglibs/ibpp.lib/database.cpp | 483 +++ stglibs/ibpp.lib/date.cpp | 209 + stglibs/ibpp.lib/dbkey.cpp | 120 + stglibs/ibpp.lib/deps | 76 + stglibs/ibpp.lib/events.cpp | 372 ++ stglibs/ibpp.lib/exception.cpp | 351 ++ stglibs/ibpp.lib/ibase.h | 2851 +++++++++++++ stglibs/ibpp.lib/iberror.h | 771 ++++ stglibs/ibpp.lib/ibpp.h | 929 ++++ stglibs/ibpp.lib/libibpp.so | Bin 0 -> 4113890 bytes stglibs/ibpp.lib/row.cpp | 1580 +++++++ stglibs/ibpp.lib/service.cpp | 774 ++++ stglibs/ibpp.lib/statement.cpp | 1307 ++++++ stglibs/ibpp.lib/time.cpp | 208 + stglibs/ibpp.lib/transaction.cpp | 409 ++ stglibs/ibpp.lib/user.cpp | 70 + stglibs/pinger.lib/Makefile | 18 + stglibs/pinger.lib/pinger.cpp | 388 ++ stglibs/pinger.lib/pinger.h | 139 + stglibs/pinger.lib/test.cpp | 145 + stglibs/script_executer.lib/Makefile | 12 + .../script_executer.lib/script_executer.cpp | 120 + stglibs/script_executer.lib/script_executer.h | 12 + stglibs/srvconf.lib/Makefile | 19 + stglibs/srvconf.lib/netunit.cpp | 506 +++ stglibs/srvconf.lib/netunit.h | 130 + stglibs/srvconf.lib/parser.cpp | 961 +++++ stglibs/srvconf.lib/servconf.cpp | 398 ++ stglibs/srvconf.lib/servconf.h | 309 ++ stglibs/srvconf.lib/servconf.vpj | 191 + stglibs/srvconf.lib/servconf.vpw | 4 + stglibs/srvconf.lib/test.cpp | 171 + stglibs/srvconf.lib/test.sh | 1 + stglibs/stg_locker.lib/Makefile | 14 + stglibs/stg_locker.lib/stg_locker.cpp | 38 + stglibs/stg_locker.lib/stg_locker.h | 92 + stglibs/stg_logger.lib/Makefile | 12 + stglibs/stg_logger.lib/stg_logger.cpp | 94 + stglibs/stg_logger.lib/stg_logger.h | 40 + 457 files changed, 99858 insertions(+) create mode 100644 .gitignore create mode 100644 include/Makefile create mode 100644 include/admin_conf.h create mode 100644 include/admin_conf.inc.h create mode 100644 include/base_auth.h create mode 100644 include/base_db.h create mode 100644 include/base_plugin.h create mode 100644 include/base_settings.h create mode 100644 include/base_store.h create mode 100644 include/corp_conf.h create mode 100644 include/ia_packets.h create mode 100644 include/ibpp.h create mode 100644 include/lp2_blocks.h create mode 100644 include/mimetype.h create mode 100644 include/noncopyable.h create mode 100644 include/notifer.h create mode 100644 include/os_int.h create mode 100644 include/rad_packets.h create mode 100644 include/raw_ip_packet.h create mode 100644 include/resetable.h create mode 100644 include/rs_packets.h create mode 100644 include/service_conf.h create mode 100644 include/stdstring.h create mode 100644 include/stg_common.h create mode 100644 include/stg_comp_stat.h create mode 100644 include/stg_const.h create mode 100644 include/stg_int.h create mode 100644 include/stg_message.h create mode 100644 include/tariff_conf.h create mode 100644 include/user_conf.h create mode 100644 include/user_ips.h create mode 100644 include/user_stat.h create mode 100644 include/user_traff.h create mode 100644 include/utime.h create mode 100644 include/version.h create mode 100644 include/vpn_stg_packets.h create mode 100644 projects/convertor/Makefile create mode 100755 projects/convertor/build create mode 100644 projects/convertor/convertor.conf create mode 100644 projects/convertor/main.cpp create mode 100644 projects/convertor/settings.cpp create mode 100644 projects/convertor/settings.h create mode 100755 projects/make_tarball/get_from_cvs create mode 100755 projects/make_tarball/mt.sh create mode 100644 projects/rlm_stg/Makefile create mode 100755 projects/rlm_stg/build create mode 100644 projects/rlm_stg/build_check.c create mode 100644 projects/rlm_stg/conf.h create mode 100644 projects/rlm_stg/conffile.h create mode 100644 projects/rlm_stg/event.h create mode 100644 projects/rlm_stg/libradius.h create mode 100644 projects/rlm_stg/modules.h create mode 100644 projects/rlm_stg/radiusd.h create mode 100644 projects/rlm_stg/realms.h create mode 100644 projects/rlm_stg/rlm_stg.cpp create mode 100644 projects/rlm_stg/stats.h create mode 100644 projects/rlm_stg/stg_client.cpp create mode 100644 projects/rlm_stg/stg_client.h create mode 100644 projects/rscriptd/Makefile create mode 100755 projects/rscriptd/build create mode 100644 projects/rscriptd/listener.cpp create mode 100644 projects/rscriptd/listener.h create mode 100644 projects/rscriptd/main.cpp create mode 100644 projects/rscriptd/pidfile.cpp create mode 100644 projects/rscriptd/pidfile.h create mode 100644 projects/rscriptd/rscriptd.conf create mode 100644 projects/sgauth/Makefile create mode 100755 projects/sgauth/build create mode 100644 projects/sgauth/main.cpp create mode 100755 projects/sgauth/make_css.sh create mode 100644 projects/sgauth/readme create mode 100644 projects/sgauth/sgauth.conf create mode 100644 projects/sgauth/sgauth.css create mode 100644 projects/sgauth/web.cpp create mode 100644 projects/sgauth/web.h create mode 100644 projects/sgconf/CHANGES create mode 100644 projects/sgconf/Makefile create mode 100644 projects/sgconf/README.txt create mode 100755 projects/sgconf/build create mode 100644 projects/sgconf/common_sg.cpp create mode 100644 projects/sgconf/common_sg.h create mode 100644 projects/sgconf/main.cpp create mode 100644 projects/sgconf/parser.cpp create mode 100644 projects/sgconf/request.h create mode 100644 projects/sgconf/sg_error_codes.h create mode 100755 projects/sgconf/sgconfg create mode 100755 projects/sgconf/sgconfs create mode 100644 projects/sgconf/sginfo.cpp create mode 100644 projects/sgconf/version_sg.h create mode 100644 projects/stargazer/.#Makefile.1.45 create mode 100644 projects/stargazer/BUGS create mode 100644 projects/stargazer/CHANGES create mode 100644 projects/stargazer/Makefile create mode 100644 projects/stargazer/README create mode 100644 projects/stargazer/TODO create mode 100644 projects/stargazer/actions.h create mode 100644 projects/stargazer/actions.inl.h create mode 100644 projects/stargazer/admin.cpp create mode 100644 projects/stargazer/admin.h create mode 100644 projects/stargazer/admins.cpp create mode 100644 projects/stargazer/admins.h create mode 100755 projects/stargazer/build create mode 100755 projects/stargazer/devbuild create mode 100644 projects/stargazer/eventloop.cpp create mode 100644 projects/stargazer/eventloop.h create mode 100755 projects/stargazer/inst/freebsd/etc/stargazer/OnChange create mode 100755 projects/stargazer/inst/freebsd/etc/stargazer/OnConnect create mode 100755 projects/stargazer/inst/freebsd/etc/stargazer/OnDisconnect create mode 100755 projects/stargazer/inst/freebsd/etc/stargazer/OnUserAdd create mode 100755 projects/stargazer/inst/freebsd/etc/stargazer/OnUserDel create mode 100644 projects/stargazer/inst/freebsd/etc/stargazer/rules create mode 100644 projects/stargazer/inst/freebsd/etc/stargazer/stargazer.conf create mode 100755 projects/stargazer/inst/linux/etc/init.d/stargazer.gentoo.2007 create mode 100755 projects/stargazer/inst/linux/etc/init.d/stargazer.suse.9.3 create mode 100755 projects/stargazer/inst/linux/etc/init.d/stargazer.ubuntu.7.10 create mode 100755 projects/stargazer/inst/linux/etc/stargazer/OnChange create mode 100755 projects/stargazer/inst/linux/etc/stargazer/OnConnect create mode 100755 projects/stargazer/inst/linux/etc/stargazer/OnDisconnect create mode 100755 projects/stargazer/inst/linux/etc/stargazer/OnUserAdd create mode 100755 projects/stargazer/inst/linux/etc/stargazer/OnUserDel create mode 100644 projects/stargazer/inst/linux/etc/stargazer/rules create mode 100644 projects/stargazer/inst/linux/etc/stargazer/stargazer.conf create mode 100644 projects/stargazer/inst/var/00-alter-01.postgresql.sql create mode 100644 projects/stargazer/inst/var/00-alter-01.sql create mode 100644 projects/stargazer/inst/var/00-base-00.postgresql.sql create mode 100644 projects/stargazer/inst/var/00-base-00.sql create mode 100644 projects/stargazer/inst/var/00-mysql-01.sql create mode 100644 projects/stargazer/inst/var/base.dia create mode 100644 projects/stargazer/inst/var/stargazer/admins/admin.adm create mode 100644 projects/stargazer/inst/var/stargazer/tariffs/tariff.tf create mode 100644 projects/stargazer/inst/var/stargazer/users/test/conf create mode 100644 projects/stargazer/inst/var/stargazer/users/test/stat create mode 100644 projects/stargazer/main.cpp create mode 100644 projects/stargazer/pidfile.cpp create mode 100644 projects/stargazer/pidfile.h create mode 100644 projects/stargazer/plugin_runner.cpp create mode 100644 projects/stargazer/plugin_runner.h create mode 100644 projects/stargazer/plugins/Makefile create mode 100644 projects/stargazer/plugins/Makefile.in create mode 100644 projects/stargazer/plugins/authorization/ao/Makefile create mode 100644 projects/stargazer/plugins/authorization/ao/ao.cpp create mode 100644 projects/stargazer/plugins/authorization/ao/ao.h create mode 100644 projects/stargazer/plugins/authorization/inetaccess/Makefile create mode 100644 projects/stargazer/plugins/authorization/inetaccess/antiflood.cpp create mode 100644 projects/stargazer/plugins/authorization/inetaccess/antiflood.h create mode 100644 projects/stargazer/plugins/authorization/inetaccess/inetaccess.cpp create mode 100644 projects/stargazer/plugins/authorization/inetaccess/inetaccess.h create mode 100644 projects/stargazer/plugins/authorization/stress/Makefile create mode 100644 projects/stargazer/plugins/authorization/stress/stress.cpp create mode 100644 projects/stargazer/plugins/authorization/stress/stress.h create mode 100644 projects/stargazer/plugins/capture/cap_debug/Makefile create mode 100644 projects/stargazer/plugins/capture/cap_debug/checksum.c create mode 100644 projects/stargazer/plugins/capture/cap_debug/checksum.h create mode 100644 projects/stargazer/plugins/capture/cap_debug/constants.h create mode 100644 projects/stargazer/plugins/capture/cap_debug/debug_cap.cpp create mode 100644 projects/stargazer/plugins/capture/cap_debug/debug_cap.h create mode 100644 projects/stargazer/plugins/capture/cap_debug/icmp.c create mode 100644 projects/stargazer/plugins/capture/cap_debug/ip.c create mode 100644 projects/stargazer/plugins/capture/cap_debug/libpal.h create mode 100644 projects/stargazer/plugins/capture/cap_debug/misc.c create mode 100644 projects/stargazer/plugins/capture/cap_debug/packet.c create mode 100644 projects/stargazer/plugins/capture/cap_debug/socket.c create mode 100644 projects/stargazer/plugins/capture/cap_debug/tcp.c create mode 100644 projects/stargazer/plugins/capture/cap_debug/types.h create mode 100644 projects/stargazer/plugins/capture/cap_debug/udp.c create mode 100644 projects/stargazer/plugins/capture/cap_nf/Makefile create mode 100644 projects/stargazer/plugins/capture/cap_nf/cap_nf.cpp create mode 100644 projects/stargazer/plugins/capture/cap_nf/cap_nf.h create mode 100644 projects/stargazer/plugins/capture/divert_freebsd/Makefile create mode 100644 projects/stargazer/plugins/capture/divert_freebsd/divert_cap.cpp create mode 100644 projects/stargazer/plugins/capture/divert_freebsd/divert_cap.h create mode 100644 projects/stargazer/plugins/capture/ether_freebsd/Makefile create mode 100644 projects/stargazer/plugins/capture/ether_freebsd/ether_cap.cpp create mode 100644 projects/stargazer/plugins/capture/ether_freebsd/ether_cap.h create mode 100644 projects/stargazer/plugins/capture/ether_linux/Makefile create mode 100644 projects/stargazer/plugins/capture/ether_linux/ether_cap.cpp create mode 100644 projects/stargazer/plugins/capture/ether_linux/ether_cap.h create mode 100644 projects/stargazer/plugins/capture/ipq_linux/.#ipq_cap.cpp.1.3 create mode 100644 projects/stargazer/plugins/capture/ipq_linux/Makefile create mode 100644 projects/stargazer/plugins/capture/ipq_linux/ipq_cap.cpp create mode 100644 projects/stargazer/plugins/capture/ipq_linux/ipq_cap.h create mode 100644 projects/stargazer/plugins/capture/ipq_linux/libipq.c create mode 100644 projects/stargazer/plugins/capture/ipq_linux/libipq.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/Makefile create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/admins_methods.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/admins_methods.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/deps create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/info_methods.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/info_methods.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/messages_methods.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/messages_methods.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/user_helper.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/user_helper.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/users_methods.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/users_methods.h create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/utils.cpp create mode 100644 projects/stargazer/plugins/configuration/rpcconfig/utils.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig/Makefile create mode 100644 projects/stargazer/plugins/configuration/sgconfig/configproto.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig/configproto.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig/net_configurator.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig/net_configurator.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig/parser.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig/parser.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig/parser_admin.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig/parser_tariff.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig/rsconf.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig/stgconfig.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig/stgconfig.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/Makefile create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/configproto.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/configproto.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/net_configurator.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/net_configurator.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/parser.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/parser.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/parser_admin.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/parser_tariff.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/proto.h create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/rsconf.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/stgconfig.cpp create mode 100644 projects/stargazer/plugins/configuration/sgconfig2/stgconfig.h create mode 100644 projects/stargazer/plugins/configuration/xrconfig/Makefile create mode 100644 projects/stargazer/plugins/configuration/xrconfig/xrconfig.cpp create mode 100644 projects/stargazer/plugins/configuration/xrconfig/xrconfig.h create mode 100644 projects/stargazer/plugins/other/ping/Makefile create mode 100644 projects/stargazer/plugins/other/ping/ping.cpp create mode 100644 projects/stargazer/plugins/other/ping/ping.h create mode 100644 projects/stargazer/plugins/other/radius/Makefile create mode 100644 projects/stargazer/plugins/other/radius/radius.cpp create mode 100644 projects/stargazer/plugins/other/radius/radius.h create mode 100644 projects/stargazer/plugins/other/rscript/Makefile create mode 100644 projects/stargazer/plugins/other/rscript/nrmap_parser.cpp create mode 100644 projects/stargazer/plugins/other/rscript/nrmap_parser.h create mode 100644 projects/stargazer/plugins/other/rscript/rscript.cpp create mode 100644 projects/stargazer/plugins/other/rscript/rscript.h create mode 100644 projects/stargazer/plugins/other/rscript/send_functor.h create mode 100644 projects/stargazer/plugins/other/rscript/ur_functor.h create mode 100644 projects/stargazer/plugins/other/userstat/Makefile create mode 100644 projects/stargazer/plugins/other/userstat/datathread.cpp create mode 100644 projects/stargazer/plugins/other/userstat/datathread.h create mode 100644 projects/stargazer/plugins/other/userstat/userstat.cpp create mode 100644 projects/stargazer/plugins/other/userstat/userstat.h create mode 120000 projects/stargazer/plugins/store/db/.libs/pg_driver.la create mode 100644 projects/stargazer/plugins/store/db/Makefile create mode 100644 projects/stargazer/plugins/store/db/pg_driver.cpp create mode 100644 projects/stargazer/plugins/store/db/pg_driver.h create mode 100644 projects/stargazer/plugins/store/db/pg_driver.la create mode 100644 projects/stargazer/plugins/store/db/pg_driver.lo create mode 100644 projects/stargazer/plugins/store/db/test_pg_driver.cpp create mode 100644 projects/stargazer/plugins/store/files/Makefile create mode 100644 projects/stargazer/plugins/store/files/file_store.cpp create mode 100644 projects/stargazer/plugins/store/files/file_store.h create mode 100644 projects/stargazer/plugins/store/firebird/Makefile create mode 100644 projects/stargazer/plugins/store/firebird/deps create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store.cpp create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store.h create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store_admins.cpp create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store_corporations.cpp create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store_messages.cpp create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store_services.cpp create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store_tariffs.cpp create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store_users.cpp create mode 100644 projects/stargazer/plugins/store/firebird/firebird_store_utils.cpp create mode 100755 projects/stargazer/plugins/store/firebird/mod_store_firebird.so create mode 100644 projects/stargazer/plugins/store/mysql/.#mysql_store.cpp.1.3 create mode 100644 projects/stargazer/plugins/store/mysql/.#mysql_store.h.1.1.1.1 create mode 100644 projects/stargazer/plugins/store/mysql/Makefile create mode 100644 projects/stargazer/plugins/store/mysql/deps create mode 100755 projects/stargazer/plugins/store/mysql/mod_store_mysql.so create mode 100644 projects/stargazer/plugins/store/mysql/mysql_store.cpp create mode 100644 projects/stargazer/plugins/store/mysql/mysql_store.h create mode 100644 projects/stargazer/plugins/store/postgresql/Makefile create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store.h create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_admins.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_corporations.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_messages.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_services.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_tariffs.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_users.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_utils.cpp create mode 100644 projects/stargazer/plugins/store/postgresql/postgresql_store_utils.h create mode 100755 projects/stargazer/sandboxstart create mode 100644 projects/stargazer/script_executer.cpp create mode 100755 projects/stargazer/scripts/clean_db create mode 100755 projects/stargazer/scripts/monitor create mode 100755 projects/stargazer/scripts/shaper/OnConnect create mode 100755 projects/stargazer/scripts/shaper/OnDisconnect create mode 100644 projects/stargazer/scripts/shaper/Readme.txt create mode 100755 projects/stargazer/scripts/shaper/shaper.sh create mode 100755 projects/stargazer/scripts/shaper/shaper.stop.sh create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/Readme create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/firewall/firewall create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/freeradius/clients.conf create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/freeradius/radiusd.conf create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-down.d/stg create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-up.d/stg create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/ppp/pptpd-options create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/pptpd.conf create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/radiusclient/servers create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnChange create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnConnect create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnDisconnect create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserAdd create mode 100755 projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserDel create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/stargazer/rules create mode 100644 projects/stargazer/scripts/shaper_vpn_radius/stargazer/stargazer.conf create mode 100644 projects/stargazer/settings.cpp create mode 100644 projects/stargazer/settings.h create mode 100644 projects/stargazer/stargazer.vpj create mode 100644 projects/stargazer/stargazer.vpw create mode 100644 projects/stargazer/stargazer.vpwhistu create mode 100755 projects/stargazer/startstg create mode 100644 projects/stargazer/stg_timer.cpp create mode 100644 projects/stargazer/stg_timer.h create mode 100644 projects/stargazer/store_loader.cpp create mode 100644 projects/stargazer/store_loader.h create mode 100644 projects/stargazer/tariff.cpp create mode 100644 projects/stargazer/tariff.h create mode 100644 projects/stargazer/tariffs.cpp create mode 100644 projects/stargazer/tariffs.h create mode 100644 projects/stargazer/traffcounter.cpp create mode 100644 projects/stargazer/traffcounter.h create mode 100644 projects/stargazer/user.cpp create mode 100644 projects/stargazer/user.h create mode 100644 projects/stargazer/user_property.cpp create mode 100644 projects/stargazer/user_property.h create mode 100644 projects/stargazer/users.cpp create mode 100644 projects/stargazer/users.h create mode 100644 projects/traffcounter/Makefile create mode 100644 projects/traffcounter/capturer_tc_iface.h create mode 100755 projects/traffcounter/configure create mode 100644 projects/traffcounter/lock.cpp create mode 100644 projects/traffcounter/lock.h create mode 100644 projects/traffcounter/logger.cpp create mode 100644 projects/traffcounter/logger.h create mode 100644 projects/traffcounter/rf_tester.cpp create mode 100644 projects/traffcounter/rules create mode 100644 projects/traffcounter/rules.cpp create mode 100644 projects/traffcounter/rules.h create mode 100644 projects/traffcounter/rules_finder.cpp create mode 100644 projects/traffcounter/rules_finder.h create mode 100644 projects/traffcounter/rules_tester.cpp create mode 100644 projects/traffcounter/table create mode 100644 projects/traffcounter/tc_packets.h create mode 100644 projects/traffcounter/tc_tester.cpp create mode 100644 projects/traffcounter/test_rules create mode 100644 projects/traffcounter/test_rules_bad_address create mode 100644 projects/traffcounter/test_rules_bad_dir create mode 100644 projects/traffcounter/test_rules_bad_dir_prefix create mode 100644 projects/traffcounter/test_rules_bad_dir_range create mode 100644 projects/traffcounter/test_rules_bad_mask create mode 100644 projects/traffcounter/test_rules_bad_port create mode 100644 projects/traffcounter/test_rules_bad_proto create mode 100644 projects/traffcounter/traffcounter.cpp create mode 100644 projects/traffcounter/traffcounter.h create mode 100644 projects/traffcounter/user_tc_iface.h create mode 100644 projects/traffcounter/utils.cpp create mode 100644 projects/traffcounter/utils.h create mode 100644 stglibs/Makefile create mode 100644 stglibs/Makefile.in create mode 100644 stglibs/common.lib/Makefile create mode 100644 stglibs/common.lib/common.bpf create mode 100644 stglibs/common.lib/common.bpr create mode 100644 stglibs/common.lib/common.cpp create mode 100644 stglibs/common.lib/common.h create mode 100644 stglibs/common.lib/debug.c create mode 100644 stglibs/common.lib/debug.h create mode 100644 stglibs/common.lib/stg_common.h create mode 100644 stglibs/common.lib/stg_error.c create mode 100644 stglibs/common.lib/stg_error.h create mode 100644 stglibs/common.lib/stg_strptime.cpp create mode 100644 stglibs/common.lib/test.cpp create mode 100644 stglibs/common.lib/test.h create mode 100644 stglibs/common_settings.lib/Makefile create mode 100644 stglibs/common_settings.lib/common_settings.cpp create mode 100644 stglibs/common_settings.lib/common_settings.h create mode 100644 stglibs/conffiles.lib/Makefile create mode 100644 stglibs/conffiles.lib/conffiles.cpp create mode 100644 stglibs/conffiles.lib/conffiles.h create mode 100644 stglibs/crypto.lib/Makefile create mode 100644 stglibs/crypto.lib/ag_md5.cpp create mode 100644 stglibs/crypto.lib/ag_md5.h create mode 100644 stglibs/crypto.lib/blowfish.cpp create mode 100644 stglibs/crypto.lib/blowfish.h create mode 100644 stglibs/crypto.lib/crypto.bpf create mode 100644 stglibs/crypto.lib/crypto.bpr create mode 100644 stglibs/dotconfpp.lib/Makefile create mode 100644 stglibs/dotconfpp.lib/dotconfpp.cpp create mode 100644 stglibs/dotconfpp.lib/dotconfpp.h create mode 100644 stglibs/dotconfpp.lib/mempool.cpp create mode 100644 stglibs/dotconfpp.lib/mempool.h create mode 100644 stglibs/hostallow.lib/Makefile create mode 100644 stglibs/hostallow.lib/hostallow.cpp create mode 100644 stglibs/hostallow.lib/hostallow.h create mode 100644 stglibs/ia_auth_c.lib/Makefile create mode 100644 stglibs/ia_auth_c.lib/ia_auth_c.bpf create mode 100644 stglibs/ia_auth_c.lib/ia_auth_c.bpr create mode 100644 stglibs/ia_auth_c.lib/ia_auth_c.cpp create mode 100644 stglibs/ia_auth_c.lib/ia_auth_c.h create mode 100644 stglibs/ibpp.lib/Makefile create mode 100644 stglibs/ibpp.lib/_dpb.cpp create mode 100644 stglibs/ibpp.lib/_ibpp.cpp create mode 100644 stglibs/ibpp.lib/_ibpp.h create mode 100644 stglibs/ibpp.lib/_ibs.cpp create mode 100644 stglibs/ibpp.lib/_rb.cpp create mode 100644 stglibs/ibpp.lib/_spb.cpp create mode 100644 stglibs/ibpp.lib/_tpb.cpp create mode 100644 stglibs/ibpp.lib/all_in_one.cpp create mode 100644 stglibs/ibpp.lib/array.cpp create mode 100644 stglibs/ibpp.lib/blob.cpp create mode 100644 stglibs/ibpp.lib/database.cpp create mode 100644 stglibs/ibpp.lib/date.cpp create mode 100644 stglibs/ibpp.lib/dbkey.cpp create mode 100644 stglibs/ibpp.lib/deps create mode 100644 stglibs/ibpp.lib/events.cpp create mode 100644 stglibs/ibpp.lib/exception.cpp create mode 100644 stglibs/ibpp.lib/ibase.h create mode 100644 stglibs/ibpp.lib/iberror.h create mode 100644 stglibs/ibpp.lib/ibpp.h create mode 100755 stglibs/ibpp.lib/libibpp.so create mode 100644 stglibs/ibpp.lib/row.cpp create mode 100644 stglibs/ibpp.lib/service.cpp create mode 100644 stglibs/ibpp.lib/statement.cpp create mode 100644 stglibs/ibpp.lib/time.cpp create mode 100644 stglibs/ibpp.lib/transaction.cpp create mode 100644 stglibs/ibpp.lib/user.cpp create mode 100644 stglibs/pinger.lib/Makefile create mode 100644 stglibs/pinger.lib/pinger.cpp create mode 100644 stglibs/pinger.lib/pinger.h create mode 100644 stglibs/pinger.lib/test.cpp create mode 100644 stglibs/script_executer.lib/Makefile create mode 100644 stglibs/script_executer.lib/script_executer.cpp create mode 100644 stglibs/script_executer.lib/script_executer.h create mode 100644 stglibs/srvconf.lib/Makefile create mode 100644 stglibs/srvconf.lib/netunit.cpp create mode 100644 stglibs/srvconf.lib/netunit.h create mode 100644 stglibs/srvconf.lib/parser.cpp create mode 100644 stglibs/srvconf.lib/servconf.cpp create mode 100644 stglibs/srvconf.lib/servconf.h create mode 100644 stglibs/srvconf.lib/servconf.vpj create mode 100644 stglibs/srvconf.lib/servconf.vpw create mode 100644 stglibs/srvconf.lib/test.cpp create mode 100755 stglibs/srvconf.lib/test.sh create mode 100644 stglibs/stg_locker.lib/Makefile create mode 100644 stglibs/stg_locker.lib/stg_locker.cpp create mode 100644 stglibs/stg_locker.lib/stg_locker.h create mode 100644 stglibs/stg_logger.lib/Makefile create mode 100644 stglibs/stg_logger.lib/stg_logger.cpp create mode 100644 stglibs/stg_logger.lib/stg_logger.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ef735103 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.[ao] +*.so +*.swp +*.diff +*.patch +CVS* diff --git a/include/Makefile b/include/Makefile new file mode 100644 index 00000000..416f82d9 --- /dev/null +++ b/include/Makefile @@ -0,0 +1,14 @@ +############################################################################### +# $Id: Makefile,v 1.2 2005/10/11 12:18:55 nobunaga Exp $ +############################################################################### + +#LIB_NAME = includes + +#INCS = *.h +#include ../Makefile.in + +all: + echo hi + +install: + echo install \ No newline at end of file diff --git a/include/admin_conf.h b/include/admin_conf.h new file mode 100644 index 00000000..88eebdc3 --- /dev/null +++ b/include/admin_conf.h @@ -0,0 +1,77 @@ + /* + $Revision: 1.9 $ + $Date: 2010/09/10 05:02:08 $ + $Author: faust $ + */ + +#ifndef ADMIN_CONF_H +#define ADMIN_CONF_H + +#include + +#include "os_int.h" + +#define ADM_LOGIN_LEN (32) +#define ADM_PASSWD_LEN (32) +//----------------------------------------------------------------------------- +struct PRIV +{ + PRIV() + : userStat(0), + userConf(0), + userCash(0), + userPasswd(0), + userAddDel(0), + adminChg(0), + tariffChg(0) + {}; + PRIV(uint16_t p) + : userStat((p & 0x0003) >> 0x00), + userConf((p & 0x000C) >> 0x02), + userCash((p & 0x0030) >> 0x04), + userPasswd((p & 0x00C0) >> 0x06), + userAddDel((p & 0x0300) >> 0x08), + adminChg((p & 0x0C00) >> 0x0A), + tariffChg((p & 0x3000) >> 0x0C) + {} + + uint16_t ToInt() const; + void FromInt(uint16_t p); + + uint16_t userStat; + uint16_t userConf; + uint16_t userCash; + uint16_t userPasswd; + uint16_t userAddDel; + uint16_t adminChg; + uint16_t tariffChg; +}; +//----------------------------------------------------------------------------- +struct ADMIN_CONF +{ + ADMIN_CONF() + : priv(), + login(), + password("* NO PASSWORD *") + {} + ADMIN_CONF(const ADMIN_CONF & rvalue) + : priv(rvalue.priv), + login(rvalue.login), + password(rvalue.password) + {} + ADMIN_CONF(const PRIV pr, const std::string & l, const std::string & p) + : priv(pr), + login(l), + password(p) + {} + PRIV priv; + std::string login; + std::string password; +}; +//----------------------------------------------------------------------------- + +#include "admin_conf.inc.h" + +#endif + + diff --git a/include/admin_conf.inc.h b/include/admin_conf.inc.h new file mode 100644 index 00000000..44897a08 --- /dev/null +++ b/include/admin_conf.inc.h @@ -0,0 +1,35 @@ + /* + $Revision: 1.1 $ + $Date: 2010/09/10 01:45:24 $ + $Author: faust $ + */ + +#ifndef ADMIN_CONF_INC_H +#define ADMIN_CONF_INC_H + +inline +uint16_t PRIV::ToInt() const +{ +uint16_t p = (userStat << 0) | + (userConf << 2) | + (userCash << 4) | + (userPasswd << 6) | + (userAddDel << 8) | + (adminChg << 10) | + (tariffChg << 12); +return p; +} + +inline +void PRIV::FromInt(uint16_t p) +{ +userStat = (p & 0x0003) >> 0x00; // 1+2 +userConf = (p & 0x000C) >> 0x02; // 4+8 +userCash = (p & 0x0030) >> 0x04; // 10+20 +userPasswd = (p & 0x00C0) >> 0x06; // 40+80 +userAddDel = (p & 0x0300) >> 0x08; // 100+200 +adminChg = (p & 0x0C00) >> 0x0A; // 400+800 +tariffChg = (p & 0x3000) >> 0x0C; // 1000+2000 +} + +#endif diff --git a/include/base_auth.h b/include/base_auth.h new file mode 100644 index 00000000..55152def --- /dev/null +++ b/include/base_auth.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.6 $ + $Date: 2009/03/18 17:24:57 $ + */ + +#ifndef BASE_AUTH_H +#define BASE_AUTH_H + +#include +#include + +#include "base_plugin.h" +#include "stg_message.h" + +using namespace std; + +//----------------------------------------------------------------------------- +class BASE_AUTH : public BASE_PLUGIN +{ +public: + virtual ~BASE_AUTH() {}; + virtual int SendMessage(const STG_MSG & msg, uint32_t ip) const = 0; +}; +//----------------------------------------------------------------------------- +#endif + + diff --git a/include/base_db.h b/include/base_db.h new file mode 100644 index 00000000..8aa05508 --- /dev/null +++ b/include/base_db.h @@ -0,0 +1,75 @@ +#ifndef __BASE_DB_H__ +#define __BASE_DB_H__ + +#include +#include +#include + +class BASE_DB { +public: + + typedef std::map TUPLE; + typedef std::vector TUPLES; + typedef std::vector COLUMNS; + + BASE_DB() {}; + BASE_DB(std::string & dbHost, + std::string & dbDatabase, + std::string & dbUser, + std::string & dbPassword) + : host(dbHost), + database(dbDatabase), + user(dbUser), + password(dbPassword) + {}; + virtual ~BASE_DB() {}; + + void SetHost(const std::string & h) { host = h; }; + void SetDatabase(const std::string & db) { database = db; }; + void SetUser(const std::string & u) { user = u; }; + void SetPassword(const std::string & p) { password = p; }; + + const std::string & GetHost() const { return host; }; + const std::string & GetDatabase() const { return database; }; + const std::string & GetUser() const { return user; }; + const std::string & GetPassword() const { return password; }; + + const std::string & GetErrorMsg() const { return errorMsg; }; + + virtual bool Connect() { return true; }; + virtual bool Disconnect() { return true; }; + virtual bool Query(const std::string & q) { return true; }; + virtual bool Start() { return true; }; + virtual bool Commit() { return true; }; + virtual bool Rollback() { return true; }; + + int GetTuples() const { return tuples; }; + int GetColumns() const { return columns; }; + int GetAffectedRows() const { return affected; }; + + virtual TUPLES GetResult() const { return TUPLES(); }; + virtual TUPLE GetTuple(int n = 0) const { return TUPLE(); }; + const COLUMNS & GetColumnsNames() const { return cols; }; + +protected: + std::string host; + std::string database; + std::string user; + std::string password; + + std::string errorMsg; + + COLUMNS cols; + + int columns; + int tuples; + int affected; +}; + +extern "C" BASE_DB * CreateDriver(); +extern "C" void DestroyDriver(BASE_DB *); + +typedef BASE_DB * (* CreateDriverFn)(); +typedef void (* DestroyDriverFn)(BASE_DB *); + +#endif diff --git a/include/base_plugin.h b/include/base_plugin.h new file mode 100644 index 00000000..16dfa5fa --- /dev/null +++ b/include/base_plugin.h @@ -0,0 +1,71 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.12 $ + $Date: 2010/03/04 11:53:14 $ + $Author: faust $ +*/ + + +#ifndef BASE_PLUGIN_H +#define BASE_PLUGIN_H + +#include +#include "base_settings.h" +#include "noncopyable.h" +#include "os_int.h" + +using namespace std; + +class ADMINS; +class USERS; +class TARIFFS; +class TRAFFCOUNTER; +class SETTINGS; +class BASE_STORE; + +//----------------------------------------------------------------------------- +class BASE_PLUGIN : private NONCOPYABLE +{ +public: + virtual ~BASE_PLUGIN(){}; + virtual void SetUsers(USERS * u) = 0; + virtual void SetTariffs(TARIFFS * t) = 0; + virtual void SetAdmins(ADMINS * a) = 0; + virtual void SetTraffcounter(TRAFFCOUNTER * tc) = 0; + virtual void SetStore(BASE_STORE * st) = 0; + virtual void SetStgSettings(const SETTINGS * s) = 0; + virtual void SetSettings(const MODULE_SETTINGS & s) = 0; + virtual int ParseSettings() = 0; + + virtual int Start() = 0; + virtual int Stop() = 0; + virtual int Reload() = 0; + virtual bool IsRunning() = 0; + virtual const string & GetStrError() const = 0; + virtual const string GetVersion() const = 0; + virtual uint16_t GetStartPosition() const = 0; + virtual uint16_t GetStopPosition() const = 0; +}; +//----------------------------------------------------------------------------- +#endif + + diff --git a/include/base_settings.h b/include/base_settings.h new file mode 100644 index 00000000..1b9366e8 --- /dev/null +++ b/include/base_settings.h @@ -0,0 +1,55 @@ + /* + $Revision: 1.5 $ + $Date: 2010/03/04 11:49:52 $ + $Author: faust $ + */ + +#ifndef BASE_SETTINGS_H +#define BASE_SETTINGS_H + +#include +#include +#include + +using namespace std; + +//----------------------------------------------------------------------------- +struct PARAM_VALUE +{ + PARAM_VALUE() + : param(), + value() + {}; + bool operator==(const PARAM_VALUE & rhs) const + { return !strcasecmp(param.c_str(), rhs.param.c_str()); }; + + bool operator<(const PARAM_VALUE & rhs) const + { return strcasecmp(param.c_str(), rhs.param.c_str()) < 0; }; + + string param; + vector value; +}; +//----------------------------------------------------------------------------- +struct MODULE_SETTINGS +{ + MODULE_SETTINGS() + : moduleName(), + moduleParams() + {}; + MODULE_SETTINGS(const MODULE_SETTINGS & rvalue) + : moduleName(rvalue.moduleName), + moduleParams(rvalue.moduleParams) + {}; + bool operator==(const MODULE_SETTINGS & rhs) const + { return !strcasecmp(moduleName.c_str(), rhs.moduleName.c_str()); }; + + bool operator<(const MODULE_SETTINGS & rhs) const + { return strcasecmp(moduleName.c_str(), rhs.moduleName.c_str()) < 0; }; + +string moduleName; +vector moduleParams; +}; +//----------------------------------------------------------------------------- +#endif //BASE_SETTINGS_H + + diff --git a/include/base_store.h b/include/base_store.h new file mode 100644 index 00000000..e5939623 --- /dev/null +++ b/include/base_store.h @@ -0,0 +1,121 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.16 $ + $Date: 2010/01/19 11:09:48 $ + $Author: faust $ + */ + +#ifndef BASE_STORE_H +#define BASE_STORE_H + +#include +#include +#include + +#include "user_stat.h" +#include "user_conf.h" +#include "corp_conf.h" +#include "service_conf.h" +#include "admin_conf.h" +#include "tariff_conf.h" +#include "base_settings.h" +#include "stg_message.h" + +using namespace std; +//----------------------------------------------------------------------------- +class BASE_STORE +{ +public: + virtual ~BASE_STORE(){}; + virtual int GetUsersList(vector * usersList) const = 0; + virtual int AddUser(const string & login) const = 0; + virtual int DelUser(const string & login) const = 0; + virtual int SaveUserStat(const USER_STAT & stat, const string & login) const = 0; + virtual int SaveUserConf(const USER_CONF & conf, const string & login) const = 0; + virtual int RestoreUserStat(USER_STAT * stat, const string & login) const = 0; + virtual int RestoreUserConf(USER_CONF * conf, const string & login) const = 0; + + virtual int WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message = "") const = 0; + + virtual int WriteUserConnect(const string & login, uint32_t ip) const = 0; + + virtual int WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const = 0; + + virtual int WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const = 0; + + virtual int AddMessage(STG_MSG * msg, const string & login) const = 0; + virtual int EditMessage(const STG_MSG & msg, const string & login) const = 0; + virtual int GetMessage(uint64_t id, STG_MSG * msg, const string & login) const = 0; + virtual int DelMessage(uint64_t id, const string & login) const = 0; + virtual int GetMessageHdrs(vector * hdrsList, const string & login) const = 0; + + + virtual int SaveMonthStat(const USER_STAT & stat, int month, int year, const string & login) const = 0; + + virtual int GetAdminsList(vector * adminsList) const = 0; + virtual int SaveAdmin(const ADMIN_CONF & ac) const = 0; + virtual int RestoreAdmin(ADMIN_CONF * ac, const string & login) const = 0; + virtual int AddAdmin(const string & login) const = 0; + virtual int DelAdmin(const string & login) const = 0; + + virtual int GetTariffsList(vector * tariffsList) const = 0; + virtual int AddTariff(const string & name) const = 0; + virtual int DelTariff(const string & name) const = 0; + virtual int SaveTariff(const TARIFF_DATA & td, const string & tariffName) const = 0; + virtual int RestoreTariff(TARIFF_DATA * td, const string & tariffName) const = 0; + + virtual int GetCorpsList(vector * corpsList) const = 0; + virtual int SaveCorp(const CORP_CONF & cc) const = 0; + virtual int RestoreCorp(CORP_CONF * cc, const string & name) const = 0; + virtual int AddCorp(const string & name) const = 0; + virtual int DelCorp(const string & name) const = 0; + + virtual int GetServicesList(vector * corpsList) const = 0; + virtual int SaveService(const SERVICE_CONF & sc) const = 0; + virtual int RestoreService(SERVICE_CONF * sc, const string & name) const = 0; + virtual int AddService(const string & name) const = 0; + virtual int DelService(const string & name) const = 0; + + virtual void SetSettings(const MODULE_SETTINGS & s) = 0; + virtual int ParseSettings() = 0; + virtual const string & GetStrError() const = 0; + virtual const string & GetVersion() const = 0; +}; +//----------------------------------------------------------------------------- + +#endif //BASE_STORE_H + diff --git a/include/corp_conf.h b/include/corp_conf.h new file mode 100644 index 00000000..88a84f18 --- /dev/null +++ b/include/corp_conf.h @@ -0,0 +1,10 @@ +#ifndef CORP_CONF_H +#define CORP_CONF_H + +struct CORP_CONF +{ +string name; +double cash; +}; + +#endif //CORP_CONF_H diff --git a/include/ia_packets.h b/include/ia_packets.h new file mode 100644 index 00000000..bd8be538 --- /dev/null +++ b/include/ia_packets.h @@ -0,0 +1,307 @@ +#ifndef PACKETH +#define PACKETH + +#include "os_int.h" + +#define CONN_SYN_N 0 +#define CONN_SYN_ACK_N 1 +#define CONN_ACK_N 2 +#define ALIVE_SYN_N 3 +#define ALIVE_ACK_N 4 +#define DISCONN_SYN_N 5 +#define DISCONN_SYN_ACK_N 6 +#define DISCONN_ACK_N 7 +#define FIN_N 8 +#define ERROR_N 9 +#define INFO_N 10 +#define INFO_7_N 11 +#define INFO_8_N 12 +#define UPDATE_N 13 + +#define DIR_NUM (10) + +#define IA_FREEMB_LEN (16) +#define IA_LOGIN_LEN (32) +#define IA_PASSWD_LEN (32) +#define IA_MAX_TYPE_LEN (16) +#define IA_MAX_MSG_LEN (235) +#define IA_MAX_MSG_LEN_8 (1030) +#define IA_DIR_NAME_LEN (16) +#define IA_MAGIC_LEN (6) +#define IA_PROTO_VER_LEN (2) + +#define ST_NOT_INETABLE (0) +#define ST_INETABLE (1) + +#define IA_ID "00100" + +typedef int8_t string16[IA_DIR_NAME_LEN]; +//----------------------------------------------------------------------------- +struct HDR_8 +{ +int8_t magic[IA_MAGIC_LEN]; +int8_t protoVer[IA_PROTO_VER_LEN]; +//uint32_t ip; +//int8_t padding[4]; +}; +//----------------------------------------------------------------------------- +struct CONN_SYN_6 +{ +int8_t magic[IA_MAGIC_LEN]; +int8_t protoVer[IA_PROTO_VER_LEN]; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t login[IA_LOGIN_LEN]; +int8_t padding[2]; +}; +//----------------------------------------------------------------------------- +struct CONN_SYN_8 +{ +HDR_8 hdr; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t login[IA_LOGIN_LEN]; +uint32_t dirs; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct CONN_SYN_ACK_6 +{ +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +int32_t userTimeOut; // Byte-order dependent +int32_t aliveDelay; // Byte-order dependent +string16 dirName[DIR_NUM]; +}; +//----------------------------------------------------------------------------- +struct CONN_SYN_ACK_8 +{ +HDR_8 hdr; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +int32_t userTimeOut; // Byte-order dependent +int32_t aliveDelay; // Byte-order dependent +string16 dirName[DIR_NUM]; +}; +//----------------------------------------------------------------------------- +struct CONN_ACK_6 +{ +int8_t magic[IA_MAGIC_LEN]; +int8_t protoVer[IA_PROTO_VER_LEN]; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct CONN_ACK_8 +{ +HDR_8 hdr; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct ALIVE_SYN_6 +{ +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent + +int64_t mu[DIR_NUM]; // Byte-order dependent +int64_t md[DIR_NUM]; // Byte-order dependent + +int64_t su[DIR_NUM]; // Byte-order dependent +int64_t sd[DIR_NUM]; // Byte-order dependent + +int64_t cash; // Byte-order dependent + +int8_t freeMb[IA_FREEMB_LEN]; +}; +//----------------------------------------------------------------------------- +struct ALIVE_SYN_8 +{ +HDR_8 hdr; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent + +int64_t mu[DIR_NUM]; // Byte-order dependent +int64_t md[DIR_NUM]; // Byte-order dependent + +int64_t su[DIR_NUM]; // Byte-order dependent +int64_t sd[DIR_NUM]; // Byte-order dependent + +int64_t cash; // Äåíüãè óìíîæåííûå íà 1000 - Byte-order dependent +int8_t freeMb[IA_FREEMB_LEN]; + +uint32_t status; // Byte-order dependent +int8_t padding[4]; +}; +//----------------------------------------------------------------------------- +struct ALIVE_ACK_6 +{ +int8_t magic[IA_MAGIC_LEN]; +int8_t protoVer[IA_PROTO_VER_LEN]; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct ALIVE_ACK_8 +{ +HDR_8 hdr; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct DISCONN_SYN_6 +{ +int8_t magic[IA_MAGIC_LEN]; +int8_t protoVer[IA_PROTO_VER_LEN]; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t login[IA_LOGIN_LEN]; +int8_t padding[2]; +}; +//----------------------------------------------------------------------------- +struct DISCONN_SYN_8 +{ +HDR_8 hdr; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t login[IA_LOGIN_LEN]; +int8_t padding[4]; +}; +//----------------------------------------------------------------------------- +struct DISCONN_SYN_ACK_6 +{ +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct DISCONN_SYN_ACK_8 +{ +HDR_8 hdr; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct DISCONN_ACK_6 +{ +int8_t magic[IA_MAGIC_LEN]; +int8_t protoVer[IA_PROTO_VER_LEN]; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct DISCONN_ACK_8 +{ +HDR_8 hdr; +int8_t loginS[IA_LOGIN_LEN]; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +uint32_t rnd; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +struct FIN_6 +{ +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t ok[3]; +int8_t padding[1]; +}; +//----------------------------------------------------------------------------- +struct FIN_8 +{ +HDR_8 hdr; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t ok[3]; +int8_t padding[1]; +}; +//----------------------------------------------------------------------------- +struct ERR +{ +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t text[236]; +}; +//----------------------------------------------------------------------------- +struct ERR_8 +{ +HDR_8 hdr; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t text[236]; +}; +//----------------------------------------------------------------------------- +struct INFO_6 +{ +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t infoType; +int8_t text[IA_MAX_MSG_LEN]; +}; +//----------------------------------------------------------------------------- +struct INFO_7 +{ +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t infoType; +uint32_t sendTime; // Byte-order dependent +int8_t showTime; +int8_t text[IA_MAX_MSG_LEN]; +int8_t padding[5]; +}; +//----------------------------------------------------------------------------- +struct INFO_8 +{ +HDR_8 hdr; +int32_t len; // Byte-order dependent +int8_t type[IA_MAX_TYPE_LEN]; +int8_t infoType; +uint32_t sendTime; // Byte-order dependent +int8_t showTime; +int8_t text[IA_MAX_MSG_LEN_8]; +}; +//----------------------------------------------------------------------------- +struct LOADSTAT +{ +int64_t mu[DIR_NUM]; // Byte-order dependent +int64_t md[DIR_NUM]; // Byte-order dependent + +int64_t su[DIR_NUM]; // Byte-order dependent +int64_t sd[DIR_NUM]; // Byte-order dependent + +int64_t cash; // Äåíüãè óìíîæåííûå íà 1000 - Byte-order dependent +int8_t freeMb[IA_FREEMB_LEN]; +int32_t status; // Byte-order dependent +}; +//----------------------------------------------------------------------------- +#define CONN_SYN_7 CONN_SYN_6 +#define CONN_SYN_ACK_7 CONN_SYN_ACK_6 +#define CONN_ACK_7 CONN_ACK_6 +#define ALIVE_SYN_7 ALIVE_SYN_6 +#define ALIVE_ACK_7 ALIVE_ACK_6 +#define DISCONN_SYN_7 DISCONN_SYN_6 +#define DISCONN_SYN_ACK_7 DISCONN_SYN_ACK_6 +#define DISCONN_ACK_7 DISCONN_ACK_6 +#define FIN_7 FIN_6 + +#endif + + diff --git a/include/ibpp.h b/include/ibpp.h new file mode 100644 index 00000000..7795c7db --- /dev/null +++ b/include/ibpp.h @@ -0,0 +1,929 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: ibpp.h,v 1.3 2007/10/28 11:17:44 nobunaga Exp $ +// Subject : IBPP public header file. This is _the_ only file you include in +// your application files when developing with IBPP. +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +// Contributor(s): +// +// Olivier Mascia, main coding +// Matt Hortman, initial linux port +// Mark Jordan, design contributions +// Maxim Abrashkin, enhancement patches +// Torsten Martinsen, enhancement patches +// Michael Hieke, darwin (OS X) port, enhancement patches +// Val Samko, enhancement patches and debugging +// Mike Nordell, invaluable C++ advices +// Claudio Valderrama, help with not-so-well documented IB/FB features +// Many others, excellent suggestions, bug finding, and support +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// Tabulations should be set every four characters when editing this file. +// +// When compiling a project using IBPP, the following defines should be made +// on the command-line (or in makefiles) according to the OS platform and +// compiler used. +// +// Select the platform: IBPP_WINDOWS | IBPP_LINUX | IBPP_DARWIN +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __IBPP_H__ +#define __IBPP_H__ + +#if !defined(IBPP_WINDOWS) && !defined(IBPP_LINUX) && !defined(IBPP_DARWIN) +#error Please define IBPP_WINDOWS/IBPP_LINUX/IBPP_DARWIN before compiling ! +#endif + +#if !defined(__BCPLUSPLUS__) && !defined(__GNUC__) && !defined(_MSC_VER) && !defined(__DMC__) +#error Your compiler is not recognized. +#endif + +#if defined(IBPP_LINUX) || defined(IBPP_DARWIN) +#define IBPP_UNIX // IBPP_UNIX stands as a common denominator to *NIX flavours +#endif + +// IBPP is written for 32 bits systems or higher. +// The standard type 'int' is assumed to be at least 32 bits. +// And the standard type 'short' is assumed to be exactly 16 bits. +// Everywhere possible, where the exact size of an integer does not matter, +// the standard type 'int' is used. And where an exact integer size is required +// the standard exact precision types definitions of C 99 standard are used. + +#if defined(_MSC_VER) || defined(__DMC__) || defined(__BCPLUSPLUS__) +// C99 §7.18.1.1 Exact-width integer types (only those used by IBPP) +#if defined(_MSC_VER) && (_MSC_VER < 1300) // MSVC 6 should be < 1300 + typedef short int16_t; + typedef int int32_t; + typedef unsigned int uint32_t; +#else + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; +#endif + typedef __int64 int64_t; +#else + #include // C99 (§7.18) integer types definitions +#endif + +#if !defined(_) +#define _(s) s +#endif + +#include +#include +#include + +namespace IBPP +{ + // Typically you use this constant in a call IBPP::CheckVersion as in: + // if (! IBPP::CheckVersion(IBPP::Version)) { throw .... ; } + const uint32_t Version = (2<<24) + (5<<16) + (3<<8) + 0; // Version == 2.5.3.0 + + // Dates range checking + const int MinDate = -693594; // 1 JAN 0001 + const int MaxDate = 2958464; // 31 DEC 9999 + + // Transaction Access Modes + enum TAM {amWrite, amRead}; + + // Transaction Isolation Levels + enum TIL {ilConcurrency, ilReadDirty, ilReadCommitted, ilConsistency}; + + // Transaction Lock Resolution + enum TLR {lrWait, lrNoWait}; + + // Transaction Table Reservation + enum TTR {trSharedWrite, trSharedRead, trProtectedWrite, trProtectedRead}; + + // Prepared Statement Types + enum STT {stUnknown, stUnsupported, + stSelect, stInsert, stUpdate, stDelete, stDDL, stExecProcedure, + stSelectUpdate, stSetGenerator, stSavePoint}; + + // SQL Data Types + enum SDT {sdArray, sdBlob, sdDate, sdTime, sdTimestamp, sdString, + sdSmallint, sdInteger, sdLargeint, sdFloat, sdDouble}; + + // Array Data Types + enum ADT {adDate, adTime, adTimestamp, adString, + adBool, adInt16, adInt32, adInt64, adFloat, adDouble}; + + // Database::Shutdown Modes + enum DSM {dsForce, dsDenyTrans, dsDenyAttach}; + + // Service::StartBackup && Service::StartRestore Flags + enum BRF { + brVerbose = 0x1, + // Backup flags + brIgnoreChecksums = 0x100, brIgnoreLimbo = 0x200, + brMetadataOnly = 0x400, brNoGarbageCollect = 0x800, + brNonTransportable = 0x1000, brConvertExtTables = 0x2000, + // Restore flags + brReplace = 0x10000, brDeactivateIdx = 0x20000, + brNoShadow = 0x40000, brNoValidity = 0x80000, + brPerTableCommit = 0x100000, brUseAllSpace = 0x200000 + }; + + // Service::Repair Flags + enum RPF + { + // Mandatory and mutually exclusives + rpMendRecords = 0x1, rpValidatePages = 0x2, rpValidateFull = 0x4, + // Options + rpReadOnly = 0x100, rpIgnoreChecksums = 0x200, rpKillShadows = 0x400 + }; + + // TransactionFactory Flags + enum TFF {tfIgnoreLimbo = 0x1, tfAutoCommit = 0x2, tfNoAutoUndo = 0x4}; + + /* IBPP never return any error codes. It throws exceptions. + * On database engine reported errors, an IBPP::SQLException is thrown. + * In all other cases, IBPP throws IBPP::LogicException. + * Also note that the runtime and the language might also throw exceptions + * while executing some IBPP methods. A failing new operator will throw + * std::bad_alloc, IBPP does nothing to alter the standard behaviour. + * + * std::exception + * | + * IBPP::Exception + * / \ + * IBPP::LogicException IBPP::SQLException + * | + * IBPP::WrongType + */ + + class Exception : public std::exception + { + public: + virtual const char* Origin() const throw() = 0; + virtual const char* ErrorMessage() const throw() = 0; // Deprecated, use what() + virtual const char* what() const throw() = 0; + virtual ~Exception() throw(); + }; + + class LogicException : public Exception + { + public: + virtual ~LogicException() throw(); + }; + + class SQLException : public Exception + { + public: + virtual int SqlCode() const throw() = 0; + virtual int EngineCode() const throw() = 0; + + virtual ~SQLException() throw(); + }; + + class WrongType : public LogicException + { + public: + virtual ~WrongType() throw(); + }; + + /* Classes Date, Time, Timestamp and DBKey are 'helper' classes. They help + * in retrieving or setting some special SQL types. Dates, times and dbkeys + * are often read and written as strings in SQL scripts. When programming + * with IBPP, we handle those data with these specific classes, which + * enhance their usefullness and free us of format problems (M/D/Y, D/M/Y, + * Y-M-D ?, and so on...). */ + + /* Class Date represent purely a Date (no time part specified). It is + * usefull in interactions with the SQL DATE type of Interbase. You can add + * or substract a number from a Date, that will modify it to represent the + * correct date, X days later or sooner. All the Y2K details taken into + * account. + * The full range goes from integer values IBPP::MinDate to IBPP::MaxDate + * which means from 01 Jan 0001 to 31 Dec 9999. ( Which is inherently + * incorrect as this assumes Gregorian calendar. ) */ + + class Timestamp; // Cross-reference between Timestamp, Date and Time + + class Date + { + protected: + int mDate; // The date : 1 == 1 Jan 1900 + + public: + void Clear() { mDate = MinDate - 1; }; + void Today(); + void SetDate(int year, int month, int day); + void SetDate(int dt); + void GetDate(int& year, int& month, int& day) const; + int GetDate() const { return mDate; } + int Year() const; + int Month() const; + int Day() const; + void Add(int days); + void StartOfMonth(); + void EndOfMonth(); + + Date() { Clear(); }; + Date(int dt) { SetDate(dt); } + Date(int year, int month, int day); + Date(const Date&); // Copy Constructor + Date& operator=(const Timestamp&); // Timestamp Assignment operator + Date& operator=(const Date&); // Date Assignment operator + + bool operator==(const Date& rv) const { return mDate == rv.GetDate(); } + bool operator!=(const Date& rv) const { return mDate != rv.GetDate(); } + bool operator<(const Date& rv) const { return mDate < rv.GetDate(); } + bool operator>(const Date& rv) const { return mDate > rv.GetDate(); } + + virtual ~Date() { }; + }; + + /* Class Time represent purely a Time. It is usefull in interactions + * with the SQL TIME type of Interbase. */ + + class Time + { + protected: + int mTime; // The time, in ten-thousandths of seconds since midnight + + public: + void Clear() { mTime = 0; } + void Now(); + void SetTime(int hour, int minute, int second, int tenthousandths = 0); + void SetTime(int tm); + void GetTime(int& hour, int& minute, int& second) const; + void GetTime(int& hour, int& minute, int& second, int& tenthousandths) const; + int GetTime() const { return mTime; } + int Hours() const; + int Minutes() const; + int Seconds() const; + int SubSeconds() const; // Actually tenthousandths of seconds + Time() { Clear(); } + Time(int tm) { SetTime(tm); } + Time(int hour, int minute, int second, int tenthousandths = 0); + Time(const Time&); // Copy Constructor + Time& operator=(const Timestamp&); // Timestamp Assignment operator + Time& operator=(const Time&); // Time Assignment operator + + bool operator==(const Time& rv) const { return mTime == rv.GetTime(); } + bool operator!=(const Time& rv) const { return mTime != rv.GetTime(); } + bool operator<(const Time& rv) const { return mTime < rv.GetTime(); } + bool operator>(const Time& rv) const { return mTime > rv.GetTime(); } + + virtual ~Time() { }; + }; + + /* Class Timestamp represent a date AND a time. It is usefull in + * interactions with the SQL TIMESTAMP type of Interbase. This class + * inherits from Date and Time and completely inline implements its small + * specific details. */ + + class Timestamp : public Date, public Time + { + public: + void Clear() { Date::Clear(); Time::Clear(); } + void Today() { Date::Today(); Time::Clear(); } + void Now() { Date::Today(); Time::Now(); } + + Timestamp() { Clear(); } + + Timestamp(int y, int m, int d) + { Date::SetDate(y, m, d); Time::Clear(); } + + Timestamp(int y, int mo, int d, int h, int mi, int s, int t = 0) + { Date::SetDate(y, mo, d); Time::SetTime(h, mi, s, t); } + + Timestamp(const Timestamp& rv) + : Date(rv.mDate), Time(rv.mTime) {} // Copy Constructor + + Timestamp(const Date& rv) + { mDate = rv.GetDate(); mTime = 0; } + + Timestamp(const Time& rv) + { mDate = 0; mTime = rv.GetTime(); } + + Timestamp& operator=(const Timestamp& rv) // Timestamp Assignment operator + { mDate = rv.mDate; mTime = rv.mTime; return *this; } + + Timestamp& operator=(const Date& rv) // Date Assignment operator + { mDate = rv.GetDate(); return *this; } + + Timestamp& operator=(const Time& rv) // Time Assignment operator + { mTime = rv.GetTime(); return *this; } + + bool operator==(const Timestamp& rv) const + { return (mDate == rv.GetDate()) && (mTime == rv.GetTime()); } + + bool operator!=(const Timestamp& rv) const + { return (mDate != rv.GetDate()) || (mTime != rv.GetTime()); } + + bool operator<(const Timestamp& rv) const + { return (mDate < rv.GetDate()) || + (mDate == rv.GetDate() && mTime < rv.GetTime()); } + + bool operator>(const Timestamp& rv) const + { return (mDate > rv.GetDate()) || + (mDate == rv.GetDate() && mTime > rv.GetTime()); } + + ~Timestamp() { } + }; + + /* Class DBKey can store a DBKEY, that special value which the hidden + * RDB$DBKEY can give you from a select statement. A DBKey is nothing + * specific to IBPP. It's a feature of the Firebird database engine. See its + * documentation for more information. */ + + class DBKey + { + private: + std::string mDBKey; // Stores the binary DBKey + mutable std::string mString;// String (temporary) representation of it + + public: + void Clear(); + int Size() const { return (int)mDBKey.size(); } + void SetKey(const void*, int size); + void GetKey(void*, int size) const; + const char* AsString() const; + + DBKey& operator=(const DBKey&); // Assignment operator + DBKey(const DBKey&); // Copy Constructor + DBKey() { } + ~DBKey() { } + }; + + /* Class User wraps all the information about a user that the engine can manage. */ + + class User + { + public: + std::string username; + std::string password; + std::string firstname; + std::string middlename; + std::string lastname; + uint32_t userid; // Only relevant on unixes + uint32_t groupid; // Only relevant on unixes + + private: + void copyfrom(const User& r); + + public: + void clear(); + User& operator=(const User& r) { copyfrom(r); return *this; } + User(const User& r) { copyfrom(r); } + User() : userid(0), groupid(0) { } + ~User() { }; + }; + + // Interface Wrapper + template + class Ptr + { + private: + T* mObject; + + public: + void clear() + { + if (mObject != 0) { mObject->Release(); mObject = 0; } + } + + T* intf() const { return mObject; } + T* operator->() const { return mObject; } + + bool operator==(const T* p) const { return mObject == p; } + bool operator==(const Ptr& r) const { return mObject == r.mObject; } + bool operator!=(const T* p) const { return mObject != p; } + bool operator!=(const Ptr& r) const { return mObject != r.mObject; } + + Ptr& operator=(T* p) + { + // AddRef _before_ Release gives correct behaviour on self-assigns + T* tmp = (p == 0 ? 0 : p->AddRef()); // Take care of 0 + if (mObject != 0) mObject->Release(); + mObject = tmp; return *this; + } + + Ptr& operator=(const Ptr& r) + { + // AddRef _before_ Release gives correct behaviour on self-assigns + T* tmp = (r.intf() == 0 ? 0 : r->AddRef());// Take care of 0 + if (mObject != 0) mObject->Release(); + mObject = tmp; return *this; + } + + Ptr(T* p) : mObject(p == 0 ? 0 : p->AddRef()) { } + Ptr(const Ptr& r) : mObject(r.intf() == 0 ? 0 : r->AddRef()) { } + + Ptr() : mObject(0) { } + ~Ptr() { clear(); } + }; + + // --- Interface Classes --- // + + /* Interfaces IBlob, IArray, IService, IDatabase, ITransaction and + * IStatement are at the core of IBPP. Though it is possible to program your + * applications by using theses interfaces directly (as was the case with + * IBPP 1.x), you should refrain from using them and prefer the new IBPP + * Objects Blob, Array, ... (without the I in front). Those new objects are + * typedef'd right after each interface class definition as you can read + * below. If you program using the Blob (instead of the IBlob interface + * itself), you'll never have to care about AddRef/Release and you'll never + * have to care about deleting your objects. */ + + class IBlob; typedef Ptr Blob; + class IArray; typedef Ptr Array; + class IService; typedef Ptr Service; + class IDatabase; typedef Ptr Database; + class ITransaction; typedef Ptr Transaction; + class IStatement; typedef Ptr Statement; + class IEvents; typedef Ptr Events; + class IRow; typedef Ptr Row; + + /* IBlob is the interface to the blob capabilities of IBPP. Blob is the + * object class you actually use in your programming. In Firebird, at the + * row level, a blob is merely a handle to a blob, stored elsewhere in the + * database. Blob allows you to retrieve such a handle and then read from or + * write to the blob, much in the same manner than you would do with a file. */ + + class IBlob + { + public: + virtual void Create() = 0; + virtual void Open() = 0; + virtual void Close() = 0; + virtual void Cancel() = 0; + virtual int Read(void*, int size) = 0; + virtual void Write(const void*, int size) = 0; + virtual void Info(int* Size, int* Largest, int* Segments) = 0; + + virtual void Save(const std::string& data) = 0; + virtual void Load(std::string& data) = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IBlob* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IBlob() { }; + }; + + /* IArray is the interface to the array capabilities of IBPP. Array is the + * object class you actually use in your programming. With an Array object, you + * can create, read and write Interbase Arrays, as a whole or in slices. */ + + class IArray + { + public: + virtual void Describe(const std::string& table, const std::string& column) = 0; + virtual void ReadTo(ADT, void* buffer, int elemcount) = 0; + virtual void WriteFrom(ADT, const void* buffer, int elemcount) = 0; + virtual SDT ElementType() = 0; + virtual int ElementSize() = 0; + virtual int ElementScale() = 0; + virtual int Dimensions() = 0; + virtual void Bounds(int dim, int* low, int* high) = 0; + virtual void SetBounds(int dim, int low, int high) = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IArray* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IArray() { }; + }; + + /* IService is the interface to the service capabilities of IBPP. Service is + * the object class you actually use in your programming. With a Service + * object, you can do some maintenance work of databases and servers + * (backup, restore, create/update users, ...) */ + + class IService + { + public: + virtual void Connect() = 0; + virtual bool Connected() = 0; + virtual void Disconnect() = 0; + + virtual void GetVersion(std::string& version) = 0; + + virtual void AddUser(const User&) = 0; + virtual void GetUser(User&) = 0; + virtual void GetUsers(std::vector&) = 0; + virtual void ModifyUser(const User&) = 0; + virtual void RemoveUser(const std::string& username) = 0; + + virtual void SetPageBuffers(const std::string& dbfile, int buffers) = 0; + virtual void SetSweepInterval(const std::string& dbfile, int sweep) = 0; + virtual void SetSyncWrite(const std::string& dbfile, bool) = 0; + virtual void SetReadOnly(const std::string& dbfile, bool) = 0; + virtual void SetReserveSpace(const std::string& dbfile, bool) = 0; + + virtual void Shutdown(const std::string& dbfile, DSM mode, int sectimeout) = 0; + virtual void Restart(const std::string& dbfile) = 0; + virtual void Sweep(const std::string& dbfile) = 0; + virtual void Repair(const std::string& dbfile, RPF flags) = 0; + + virtual void StartBackup(const std::string& dbfile, + const std::string& bkfile, BRF flags = BRF(0)) = 0; + virtual void StartRestore(const std::string& bkfile, const std::string& dbfile, + int pagesize = 0, BRF flags = BRF(0)) = 0; + + virtual const char* WaitMsg() = 0; // With reporting (does not block) + virtual void Wait() = 0; // Without reporting (does block) + + virtual IService* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IService() { }; + }; + + /* IDatabase is the interface to the database connections in IBPP. Database + * is the object class you actually use in your programming. With a Database + * object, you can create/drop/connect databases. */ + + class EventInterface; // Cross-reference between EventInterface and IDatabase + + class IDatabase + { + public: + virtual const char* ServerName() const = 0; + virtual const char* DatabaseName() const = 0; + virtual const char* Username() const = 0; + virtual const char* UserPassword() const = 0; + virtual const char* RoleName() const = 0; + virtual const char* CharSet() const = 0; + virtual const char* CreateParams() const = 0; + + virtual void Info(int* ODS, int* ODSMinor, int* PageSize, + int* Pages, int* Buffers, int* Sweep, bool* Sync, + bool* Reserve) = 0; + virtual void Statistics(int* Fetches, int* Marks, + int* Reads, int* Writes) = 0; + virtual void Counts(int* Insert, int* Update, int* Delete, + int* ReadIdx, int* ReadSeq) = 0; + virtual void Users(std::vector& users) = 0; + virtual int Dialect() = 0; + + virtual void Create(int dialect) = 0; + virtual void Connect() = 0; + virtual bool Connected() = 0; + virtual void Inactivate() = 0; + virtual void Disconnect() = 0; + virtual void Drop() = 0; + + virtual IDatabase* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IDatabase() { }; + }; + + /* ITransaction is the interface to the transaction connections in IBPP. + * Transaction is the object class you actually use in your programming. A + * Transaction object can be associated with more than one Database, + * allowing for distributed transactions spanning multiple databases, + * possibly located on different servers. IBPP is one among the few + * programming interfaces to Firebird that allows you to support distributed + * transactions. */ + + class ITransaction + { + public: + virtual void AttachDatabase(Database db, TAM am = amWrite, + TIL il = ilConcurrency, TLR lr = lrWait, TFF flags = TFF(0)) = 0; + virtual void DetachDatabase(Database db) = 0; + virtual void AddReservation(Database db, + const std::string& table, TTR tr) = 0; + + virtual void Start() = 0; + virtual bool Started() = 0; + virtual void Commit() = 0; + virtual void Rollback() = 0; + virtual void CommitRetain() = 0; + virtual void RollbackRetain() = 0; + + virtual ITransaction* AddRef() = 0; + virtual void Release() = 0; + + virtual ~ITransaction() { }; + }; + + /* + * Class Row can hold all the values of a row (from a SELECT for instance). + */ + + class IRow + { + public: + virtual void SetNull(int) = 0; + virtual void Set(int, bool) = 0; + virtual void Set(int, const void*, int) = 0; // byte buffers + virtual void Set(int, const char*) = 0; // c-string + virtual void Set(int, const std::string&) = 0; + virtual void Set(int, int16_t) = 0; + virtual void Set(int, int32_t) = 0; + virtual void Set(int, int64_t) = 0; + virtual void Set(int, float) = 0; + virtual void Set(int, double) = 0; + virtual void Set(int, const Timestamp&) = 0; + virtual void Set(int, const Date&) = 0; + virtual void Set(int, const Time&) = 0; + virtual void Set(int, const DBKey&) = 0; + virtual void Set(int, const Blob&) = 0; + virtual void Set(int, const Array&) = 0; + + virtual bool IsNull(int) = 0; + virtual bool Get(int, bool&) = 0; + virtual bool Get(int, void*, int&) = 0; // byte buffers + virtual bool Get(int, std::string&) = 0; + virtual bool Get(int, int16_t&) = 0; + virtual bool Get(int, int32_t&) = 0; + virtual bool Get(int, int64_t&) = 0; + virtual bool Get(int, float&) = 0; + virtual bool Get(int, double&) = 0; + virtual bool Get(int, Timestamp&) = 0; + virtual bool Get(int, Date&) = 0; + virtual bool Get(int, Time&) = 0; + virtual bool Get(int, DBKey&) = 0; + virtual bool Get(int, Blob&) = 0; + virtual bool Get(int, Array&) = 0; + + virtual bool IsNull(const std::string&) = 0; + virtual bool Get(const std::string&, bool&) = 0; + virtual bool Get(const std::string&, void*, int&) = 0; // byte buffers + virtual bool Get(const std::string&, std::string&) = 0; + virtual bool Get(const std::string&, int16_t&) = 0; + virtual bool Get(const std::string&, int32_t&) = 0; + virtual bool Get(const std::string&, int64_t&) = 0; + virtual bool Get(const std::string&, float&) = 0; + virtual bool Get(const std::string&, double&) = 0; + virtual bool Get(const std::string&, Timestamp&) = 0; + virtual bool Get(const std::string&, Date&) = 0; + virtual bool Get(const std::string&, Time&) = 0; + virtual bool Get(const std::string&, DBKey&) = 0; + virtual bool Get(const std::string&, Blob&) = 0; + virtual bool Get(const std::string&, Array&) = 0; + + virtual int ColumnNum(const std::string&) = 0; + virtual const char* ColumnName(int) = 0; + virtual const char* ColumnAlias(int) = 0; + virtual const char* ColumnTable(int) = 0; + virtual SDT ColumnType(int) = 0; + virtual int ColumnSubtype(int) = 0; + virtual int ColumnSize(int) = 0; + virtual int ColumnScale(int) = 0; + virtual int Columns() = 0; + + virtual bool ColumnUpdated(int) = 0; + virtual bool Updated() = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IRow* Clone() = 0; + virtual IRow* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IRow() {}; + }; + + /* IStatement is the interface to the statements execution in IBPP. + * Statement is the object class you actually use in your programming. A + * Statement object is the work horse of IBPP. All your data manipulation + * statements will be done through it. It is also used to access the result + * set of a query (when the statement is such), one row at a time and in + * strict forward direction. */ + + class IStatement + { + public: + virtual void Prepare(const std::string&) = 0; + virtual void Execute() = 0; + virtual void Execute(const std::string&) = 0; + virtual void ExecuteImmediate(const std::string&) = 0; + virtual void CursorExecute(const std::string& cursor) = 0; + virtual void CursorExecute(const std::string& cursor, const std::string&) = 0; + virtual bool Fetch() = 0; + virtual bool Fetch(Row&) = 0; + virtual int AffectedRows() = 0; + virtual void Close() = 0; + virtual std::string& Sql() = 0; + virtual STT Type() = 0; + + virtual void SetNull(int) = 0; + virtual void Set(int, bool) = 0; + virtual void Set(int, const void*, int) = 0; // byte buffers + virtual void Set(int, const char*) = 0; // c-string + virtual void Set(int, const std::string&) = 0; + virtual void Set(int, int16_t value) = 0; + virtual void Set(int, int32_t value) = 0; + virtual void Set(int, int64_t value) = 0; + virtual void Set(int, float value) = 0; + virtual void Set(int, double value) = 0; + virtual void Set(int, const Timestamp& value) = 0; + virtual void Set(int, const Date& value) = 0; + virtual void Set(int, const Time& value) = 0; + virtual void Set(int, const DBKey& value) = 0; + virtual void Set(int, const Blob& value) = 0; + virtual void Set(int, const Array& value) = 0; + + virtual bool IsNull(int) = 0; + virtual bool Get(int, bool&) = 0; + virtual bool Get(int, void*, int&) = 0; // byte buffers + virtual bool Get(int, std::string&) = 0; + virtual bool Get(int, int16_t&) = 0; + virtual bool Get(int, int32_t&) = 0; + virtual bool Get(int, int64_t&) = 0; + virtual bool Get(int, float&) = 0; + virtual bool Get(int, double&) = 0; + virtual bool Get(int, Timestamp& value) = 0; + virtual bool Get(int, Date& value) = 0; + virtual bool Get(int, Time& value) = 0; + virtual bool Get(int, DBKey& value) = 0; + virtual bool Get(int, Blob& value) = 0; + virtual bool Get(int, Array& value) = 0; + + virtual bool IsNull(const std::string&) = 0; + virtual bool Get(const std::string&, bool&) = 0; + virtual bool Get(const std::string&, void*, int&) = 0; // byte buffers + virtual bool Get(const std::string&, std::string&) = 0; + virtual bool Get(const std::string&, int16_t&) = 0; + virtual bool Get(const std::string&, int32_t&) = 0; + virtual bool Get(const std::string&, int64_t&) = 0; + virtual bool Get(const std::string&, float&) = 0; + virtual bool Get(const std::string&, double&) = 0; + virtual bool Get(const std::string&, Timestamp& value) = 0; + virtual bool Get(const std::string&, Date& value) = 0; + virtual bool Get(const std::string&, Time& value) = 0; + virtual bool Get(const std::string&, DBKey& value) = 0; + virtual bool Get(const std::string&, Blob& value) = 0; + virtual bool Get(const std::string&, Array& value) = 0; + + virtual int ColumnNum(const std::string&) = 0; + virtual const char* ColumnName(int) = 0; + virtual const char* ColumnAlias(int) = 0; + virtual const char* ColumnTable(int) = 0; + virtual SDT ColumnType(int) = 0; + virtual int ColumnSubtype(int) = 0; + virtual int ColumnSize(int) = 0; + virtual int ColumnScale(int) = 0; + virtual int Columns() = 0; + + virtual SDT ParameterType(int) = 0; + virtual int ParameterSubtype(int) = 0; + virtual int ParameterSize(int) = 0; + virtual int ParameterScale(int) = 0; + virtual int Parameters() = 0; + + virtual void Plan(std::string&) = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IStatement* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IStatement() { }; + + // DEPRECATED METHODS (WON'T BE AVAILABLE IN VERSIONS 3.x) + virtual bool Get(int, char*) = 0; // DEPRECATED + virtual bool Get(const std::string&, char*) = 0; // DEPRECATED + virtual bool Get(int, bool*) = 0; // DEPRECATED + virtual bool Get(const std::string&, bool*) = 0; // DEPRECATED + virtual bool Get(int, int16_t*) = 0; // DEPRECATED + virtual bool Get(const std::string&, int16_t*) = 0; // DEPRECATED + virtual bool Get(int, int32_t*) = 0; // DEPRECATED + virtual bool Get(const std::string&, int32_t*) = 0; // DEPRECATED + virtual bool Get(int, int64_t*) = 0; // DEPRECATED + virtual bool Get(const std::string&, int64_t*) = 0; // DEPRECATED + virtual bool Get(int, float*) = 0; // DEPRECATED + virtual bool Get(const std::string&, float*) = 0; // DEPRECATED + virtual bool Get(int, double*) = 0; // DEPRECATED + virtual bool Get(const std::string&, double*) = 0; // DEPRECATED + }; + + class IEvents + { + public: + virtual void Add(const std::string&, EventInterface*) = 0; + virtual void Drop(const std::string&) = 0; + virtual void List(std::vector&) = 0; + virtual void Clear() = 0; // Drop all events + virtual void Dispatch() = 0; // Dispatch events (calls handlers) + + virtual Database DatabasePtr() const = 0; + + virtual IEvents* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IEvents() { }; + }; + + /* Class EventInterface is merely a pure interface. + * It is _not_ implemented by IBPP. It is only a base class definition from + * which your own event interface classes have to derive from. + * Please read the reference guide at http://www.ibpp.org for more info. */ + + class EventInterface + { + public: + virtual void ibppEventHandler(Events, const std::string&, int) = 0; + virtual ~EventInterface() { }; + }; + + // --- Factories --- + // These methods are the only way to get one of the above + // Interfaces. They are at the heart of how you program using IBPP. For + // instance, to get access to a database, you'll write code similar to this: + // { + // Database db = DatabaseFactory("server", "databasename", + // "user", "password"); + // db->Connect(); + // ... + // db->Disconnect(); + // } + + Service ServiceFactory(const std::string& ServerName, + const std::string& UserName, const std::string& UserPassword); + + Database DatabaseFactory(const std::string& ServerName, + const std::string& DatabaseName, const std::string& UserName, + const std::string& UserPassword, const std::string& RoleName, + const std::string& CharSet, const std::string& CreateParams); + + inline Database DatabaseFactory(const std::string& ServerName, + const std::string& DatabaseName, const std::string& UserName, + const std::string& UserPassword) + { return DatabaseFactory(ServerName, DatabaseName, UserName, UserPassword, "", "", ""); } + + Transaction TransactionFactory(Database db, TAM am = amWrite, + TIL il = ilConcurrency, TLR lr = lrWait, TFF flags = TFF(0)); + + Statement StatementFactory(Database db, Transaction tr, + const std::string& sql); + + inline Statement StatementFactory(Database db, Transaction tr) + { return StatementFactory(db, tr, ""); } + + Blob BlobFactory(Database db, Transaction tr); + + Array ArrayFactory(Database db, Transaction tr); + + Events EventsFactory(Database db); + + /* IBPP uses a self initialization system. Each time an object that may + * require the usage of the Interbase client C-API library is used, the + * library internal handling details are automatically initialized, if not + * already done. You can kick this initialization at the start of an + * application by calling IBPP::CheckVersion(). This is recommended, because + * IBPP::CheckVersion will assure you that YOUR code has been compiled + * against a compatible version of the library. */ + + bool CheckVersion(uint32_t); + int GDSVersion(); + + /* On Win32 platform, ClientLibSearchPaths() allows to setup + * one or multiple additional paths (separated with a ';') where IBPP + * will look for the client library (before the default implicit search + * locations). This is usefull for applications distributed with a 'private' + * copy of Firebird, when the registry is useless to identify the location + * from where to attempt loading the fbclient.dll / gds32.dll. + * If called, this function must be called *early* by the application, + * before *any* other function or object methods of IBPP. + * Currently, this is a NO-OP on platforms other than Win32. */ + + void ClientLibSearchPaths(const std::string&); + + /* Finally, here are some date and time conversion routines used by IBPP and + * that may be helpful at the application level. They do not depend on + * anything related to Firebird/Interbase. Just a bonus. dtoi and itod + * return false on invalid parameters or out of range conversions. */ + + bool dtoi(int date, int* py, int* pm, int* pd); + bool itod(int* pdate, int year, int month, int day); + void ttoi(int itime, int* phour, int* pminute, int* psecond, int* ptt); + void itot(int* ptime, int hour, int minute, int second = 0, int tenthousandths = 0); + +} + +#endif + +// +// EOF +// diff --git a/include/lp2_blocks.h b/include/lp2_blocks.h new file mode 100644 index 00000000..fddab42b --- /dev/null +++ b/include/lp2_blocks.h @@ -0,0 +1,82 @@ +#define setPBlockStart(qwer) asm("subb $0x20,l_"#qwer"SC1\n"\ +"l_"#qwer"SC1: .byte 0x80\n"\ +" addb $0x20,l_"#qwer"SC1\n"\ +" jmp l_"#qwer"Decryptor\n\t"\ + ".string \"m_TCodeStart\"\n\t"\ + " l_"#qwer"TCodeStart: nop\n\t") + + +#define setPBlockEnd(qwer) asm(" jmp l_"#qwer"Cryptor\n"\ +" .string \"m_TCodeEnd\"\n"\ +" nop\n"\ +" nop\n"\ +" nop\n"\ +" nop\n"\ +" .string \"m_PCodeEP\"\n"\ +"l_"#qwer"Cryptor: movl d_"#qwer"Length,%edx\n"\ +" subl $4,%edx \n"\ +"l_"#qwer"Cryptor_l1: movl $28,%eax\n"\ +"l_"#qwer"Cryptor_l2: movl $d_"#qwer"Password,%ebx\n"\ +" movl (%eax,%ebx),%ecx\n"\ +" movl d_"#qwer"Start_Adr,%ebx\n"\ +" xorl %ecx,(%ebx,%edx)\n"\ +" rorl %cl,(%ebx,%edx)\n"\ +" subl $4,%edx\n"\ +" js l_"#qwer"Cryptor_ex1\n"\ +" subl $4,%eax\n"\ +" js l_"#qwer"Cryptor_l1\n"\ +" jmp l_"#qwer"Cryptor_l2\n"\ +"l_"#qwer"Cryptor_ex1: jmp l_"#qwer"Exit\n"\ +"l_"#qwer"Decryptor: movl d_"#qwer"Length,%edx\n"\ +" subl $4,%edx \n"\ +"l_"#qwer"Decryptor_l1:movl $28,%eax\n"\ +"l_"#qwer"Decryptor_l2:movl $d_"#qwer"Password,%ebx\n"\ +" movl (%eax,%ebx),%ecx\n"\ +" movl d_"#qwer"Start_Adr,%ebx\n"\ +" roll %cl,(%ebx,%edx)\n"\ +" xorl %ecx,(%ebx,%edx)\n"\ +" subl $4,%edx\n"\ +" js l_"#qwer"Decryptor_ex1\n"\ +" subl $4,%eax\n"\ +" js l_"#qwer"Decryptor_l1\n"\ +" jmp l_"#qwer"Decryptor_l2\n"\ +"l_"#qwer"Decryptor_ex1:jmp l_"#qwer"TCodeStart\n"\ +"d_"#qwer"Start_Adr: .string \"m_TCodeStartAdr\"\n"\ +"d_"#qwer"Length: .string \"m_TCodeLength\"\n"\ +"d_"#qwer"Password:.string \"m_TCodePass\"\n"\ +" .string \"_trfgfgfgfdfgfdfgfdfg\"\n"\ +" .string \"m_PCodeRet\"\n"\ +"l_"#qwer"Exit: addb $0x19,l_"#qwer"SC2\n"\ +"l_"#qwer"SC2: .byte 0x48\n"\ +" subb $0x19,l_"#qwer"SC2\n") + +#define DecryptROData \ +asm(\ +" .string \"m_ROEP\"\n"\ +" subb $0x20,l_ro_SC1\n"\ +"l_ro_SC1: .byte 0x80\n"\ +" addb $0x20,l_ro_SC1\n"\ +"l_ro_Decryptor: movl d_ro_Length,%edx\n"\ +" subl $4,%edx \n"\ +"l_ro_Decryptor_l1: movl $28,%eax\n"\ +"l_ro_Decryptor_l2: movl $d_ro_Password,%ebx\n"\ +" movl (%eax,%ebx),%ecx\n"\ +" movl d_ro_Start_Adr,%ebx\n"\ +" roll %cl,(%ebx,%edx)\n"\ +" subl $4,%edx\n"\ +" js l_ro_Exit\n"\ +" subl $4,%eax\n"\ +" js l_ro_Decryptor_l1\n"\ +" jmp l_ro_Decryptor_l2\n"\ +"d_ro_Start_Adr: .string \"m_ROStartAdr\"\n"\ +"d_ro_Length: .string \"m_ROLength\"\n"\ +"d_ro_ExitAdr: .string \"m_ROExitAdr\"\n"\ +"d_ro_Password: .string \"m_ROPass\"\n"\ +" .string \"_trfgfgfgfdfgfdfgfdfg____\"\n"\ +"l_ro_Exit: .byte 0x61\n"\ +" push d_ro_ExitAdr\n"\ +" ret\n") + + + + diff --git a/include/mimetype.h b/include/mimetype.h new file mode 100644 index 00000000..a5f8487a --- /dev/null +++ b/include/mimetype.h @@ -0,0 +1,37 @@ +/* + ***************************************************************************** + * + * File: mimetype.h + * + * Description: TODO: + * + * $Id: mimetype.h,v 1.1.1.1 2005/10/09 11:00:45 nobunaga Exp $ + * + ***************************************************************************** + */ + +#ifndef _MIMETYPE_H +#define _MIMETYPE_H_ + + +struct MIMETYPE + { + char *ext; + char *type; + }; + +const MIMETYPE mTypes[]= +{ + { ".jpg" , "image/jpeg" }, + { ".gif", "image/gif" }, + { ".jpeg", "image/jpeg" }, + { ".htm", "text/html" }, + { ".html", "text/html" }, + { ".txt", "text/plain" }, + { ".css", "text/css" } +}; + +#endif /* _MIMETYPE_H_ */ + +/* EOF */ + diff --git a/include/noncopyable.h b/include/noncopyable.h new file mode 100644 index 00000000..3d4c53b3 --- /dev/null +++ b/include/noncopyable.h @@ -0,0 +1,14 @@ +#ifndef __NONCOPYABLE_H__ +#define __NONCOPYABLE_H__ + +class NONCOPYABLE +{ +protected: + NONCOPYABLE() {} + virtual ~NONCOPYABLE() {} +private: // emphasize the following members are private + NONCOPYABLE(const NONCOPYABLE &); + const NONCOPYABLE & operator=(const NONCOPYABLE &); +}; + +#endif diff --git a/include/notifer.h b/include/notifer.h new file mode 100644 index 00000000..57f9928e --- /dev/null +++ b/include/notifer.h @@ -0,0 +1,29 @@ + /* + $Revision: 1.6 $ + $Date: 2007/12/03 09:00:17 $ + $Author: nobunaga $ + */ + +#ifndef PROPERTY_NOTIFER_H +#define PROPERTY_NOTIFER_H + +//----------------------------------------------------------------------------- +template +class PROPERTY_NOTIFIER_BASE +{ +public: + virtual ~PROPERTY_NOTIFIER_BASE(){}; + virtual void Notify(const varParamType & oldValue, const varParamType & newValue) = 0; +}; +//----------------------------------------------------------------------------- +template +class NOTIFIER_BASE +{ +public: + virtual ~NOTIFIER_BASE(){}; + virtual void Notify(const varParamType & value) = 0; +}; +//----------------------------------------------------------------------------- +#endif //PROPERTY_NOTIFER_H + + diff --git a/include/os_int.h b/include/os_int.h new file mode 100644 index 00000000..cc017b84 --- /dev/null +++ b/include/os_int.h @@ -0,0 +1,61 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.4 $ + $Date: 2008/03/25 17:41:50 $ + */ + + +#ifndef OS_INT_H +#define OS_INT_H + +#ifdef LINUX +#include +#endif + +#ifdef FREE_BSD5 +#include +#endif + +#ifdef FREE_BSD +#include +#endif + +#ifdef WIN32 + +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +typedef int int32_t; +typedef unsigned int uint32_t; + +typedef short int int16_t; +typedef short unsigned int uint16_t; + +typedef char int8_t; +typedef unsigned char uint8_t; + + +#endif + + +#endif + diff --git a/include/rad_packets.h b/include/rad_packets.h new file mode 100644 index 00000000..96cddc4b --- /dev/null +++ b/include/rad_packets.h @@ -0,0 +1,41 @@ +#ifndef RAD_PACKETSH +#define RAD_PACKETSH + +#define RAD_MAGIC_LEN (5) +#define RAD_PROTO_VER_LEN (2) +#define RAD_MAX_PACKET_LEN (1024) +#define RAD_LOGIN_LEN (32) +#define RAD_SERVICE_LEN (16) +#define RAD_PASSWORD_LEN (32) +#define RAD_SESSID_LEN (32) + +// Request +#define RAD_AUTZ_PACKET (0) +#define RAD_AUTH_PACKET (1) +#define RAD_POST_AUTH_PACKET (2) +#define RAD_ACCT_START_PACKET (3) +#define RAD_ACCT_STOP_PACKET (4) +#define RAD_ACCT_UPDATE_PACKET (5) +#define RAD_ACCT_OTHER_PACKET (6) +// Responce +#define RAD_ACCEPT_PACKET (7) +#define RAD_REJECT_PACKET (8) + +#define RAD_ID "00100" + +#include "os_int.h" + +struct RAD_PACKET +{ +uint8_t magic[RAD_MAGIC_LEN]; +uint8_t protoVer[RAD_PROTO_VER_LEN]; +uint8_t packetType; +uint8_t login[RAD_LOGIN_LEN]; +uint32_t ip; +uint8_t service[RAD_SERVICE_LEN]; +uint8_t password[RAD_PASSWORD_LEN]; +uint8_t sessid[RAD_SESSID_LEN]; +uint8_t padding[4]; +}; + +#endif diff --git a/include/raw_ip_packet.h b/include/raw_ip_packet.h new file mode 100644 index 00000000..af62d051 --- /dev/null +++ b/include/raw_ip_packet.h @@ -0,0 +1,214 @@ +#ifndef RAW_IP_PACKET_H +#define RAW_IP_PACKET_H + +#include // for htons +#include // for struct ip + +#include + +#include "stg_const.h" +#include "common.h" + +#define IPv4 (2) + +enum { pcktSize = 68 }; //60(max) ip + 8 udp or tcp (part of tcp or udp header to ports) +//----------------------------------------------------------------------------- +struct RAW_PACKET +{ + RAW_PACKET() + : dataLen(-1) + { + memset(pckt, 0, pcktSize); + } + + RAW_PACKET(const RAW_PACKET & rp) + : dataLen(rp.dataLen) + { + memcpy(pckt, rp.pckt, pcktSize); + } + +uint16_t GetIPVersion() const; +uint8_t GetHeaderLen() const; +uint8_t GetProto() const; +uint32_t GetLen() const; +uint32_t GetSrcIP() const; +uint32_t GetDstIP() const; +uint16_t GetSrcPort() const; +uint16_t GetDstPort() const; + +bool operator==(const RAW_PACKET & rvalue) const; +bool operator!=(const RAW_PACKET & rvalue) const { return !(*this == rvalue); }; +bool operator<(const RAW_PACKET & rvalue) const; + +union + { + uint8_t pckt[pcktSize]; // îÁÞÁÌÏ ÐÁËÅÔÁ ÚÁÈ×ÁÞÅÎÎÏÇÏ ÉÚ ÓÅÔÉ + struct + { + struct ip ipHeader; + // Only for packets without options field + uint16_t sPort; + uint16_t dPort; + } __attribute__ ((packed)); + }; +int32_t dataLen; // äÌÉÎÁ IP ÐÁËÅÔÁ. åÓÌÉ -1, ÔÏ ÉÓÐÏÌØÚÏ×ÁÔØ ÄÌÉÎÕ ÉÚ ÚÁÇÏÌÏ×ËÁ ÓÁÍÏÇÏ ÐÁËÅÔÁ. +}; +//----------------------------------------------------------------------------- +inline uint16_t RAW_PACKET::GetIPVersion() const +{ +return ipHeader.ip_v; +} +//----------------------------------------------------------------------------- +inline uint8_t RAW_PACKET::GetHeaderLen() const +{ +return ipHeader.ip_hl * 4; +} +//----------------------------------------------------------------------------- +inline uint8_t RAW_PACKET::GetProto() const +{ +return ipHeader.ip_p; +} +//----------------------------------------------------------------------------- +inline uint32_t RAW_PACKET::GetLen() const +{ +if (dataLen != -1) + return dataLen; +return ntohs(ipHeader.ip_len); +} +//----------------------------------------------------------------------------- +inline uint32_t RAW_PACKET::GetSrcIP() const +{ +return ipHeader.ip_src.s_addr; +} +//----------------------------------------------------------------------------- +inline uint32_t RAW_PACKET::GetDstIP() const +{ +return ipHeader.ip_dst.s_addr; +} +//----------------------------------------------------------------------------- +inline uint16_t RAW_PACKET::GetSrcPort() const +{ +if (ipHeader.ip_p == 1) // for icmp proto return port 0 + return 0; +return ntohs(*((uint16_t*)(pckt + ipHeader.ip_hl * 4))); +} +//----------------------------------------------------------------------------- +inline uint16_t RAW_PACKET::GetDstPort() const +{ +if (ipHeader.ip_p == 1) // for icmp proto return port 0 + return 0; +return ntohs(*((uint16_t*)(pckt + ipHeader.ip_hl * 4 + 2))); +} +//----------------------------------------------------------------------------- +inline bool RAW_PACKET::operator==(const RAW_PACKET & rvalue) const +{ +if (ipHeader.ip_src.s_addr != rvalue.ipHeader.ip_src.s_addr) + return false; + +if (ipHeader.ip_dst.s_addr != rvalue.ipHeader.ip_dst.s_addr) + return false; + +if (ipHeader.ip_p != 1 && rvalue.ipHeader.ip_p != 1) + { + if (*((uint16_t *)(pckt + ipHeader.ip_hl * 4)) != + *((uint16_t *)(rvalue.pckt + rvalue.ipHeader.ip_hl * 4))) + return false; + + if (*((uint16_t *)(pckt + ipHeader.ip_hl * 4 + 2)) != + *((uint16_t *)(rvalue.pckt + rvalue.ipHeader.ip_hl * 4 + 2))) + return false; + } + +if (ipHeader.ip_p != rvalue.ipHeader.ip_p) + return false; + +return true; +} +/*//----------------------------------------------------------------------------- +inline bool operator==(const RAW_PACKET & lhs, const RAW_PACKET & rhs) +{ +if (lhs.GetSrcIP() != rhs.GetSrcIP()) + return false; + +if (lhs.GetDstIP() != rhs.GetDstIP()) + return false; + +if (lhs.GetSrcPort() != rhs.GetSrcPort()) + return false; + +if (lhs.GetDstPort() != rhs.GetDstPort()) + return false; + +if (lhs.GetProto() != rhs.GetProto()) + return false; + +return true; +}*/ +//----------------------------------------------------------------------------- +inline bool RAW_PACKET::operator<(const RAW_PACKET & rvalue) const +{ +if (ipHeader.ip_src.s_addr < rvalue.ipHeader.ip_src.s_addr) + return true; +if (ipHeader.ip_src.s_addr > rvalue.ipHeader.ip_src.s_addr) + return false; + +if (ipHeader.ip_dst.s_addr < rvalue.ipHeader.ip_dst.s_addr) + return true; +if (ipHeader.ip_dst.s_addr > rvalue.ipHeader.ip_dst.s_addr) + return false; + +if (ipHeader.ip_p != 1 && rvalue.ipHeader.ip_p != 1) + { + if (*((uint16_t *)(pckt + ipHeader.ip_hl * 4)) < + *((uint16_t *)(rvalue.pckt + rvalue.ipHeader.ip_hl * 4))) + return true; + if (*((uint16_t *)(pckt + ipHeader.ip_hl * 4)) > + *((uint16_t *)(rvalue.pckt + rvalue.ipHeader.ip_hl * 4))) + return false; + + if (*((uint16_t *)(pckt + ipHeader.ip_hl * 4 + 2)) < + *((uint16_t *)(rvalue.pckt + rvalue.ipHeader.ip_hl * 4 + 2))) + return true; + if (*((uint16_t *)(pckt + ipHeader.ip_hl * 4 + 2)) > + *((uint16_t *)(rvalue.pckt + rvalue.ipHeader.ip_hl * 4 + 2))) + return false; + } + +if (ipHeader.ip_p < rvalue.ipHeader.ip_p) + return true; + +return false; +} +//----------------------------------------------------------------------------- +/*inline bool operator<(const RAW_PACKET & lhs, const RAW_PACKET & rhs) +{ +if (lhs.GetSrcIP() < rhs.GetSrcIP()) + return true; +if (lhs.GetSrcIP() > rhs.GetSrcIP()) + return false; + +if (lhs.GetDstIP() < rhs.GetDstIP()) + return true; +if (lhs.GetDstIP() > rhs.GetDstIP()) + return false; + +if (lhs.GetSrcPort() < rhs.GetSrcPort()) + return true; +if (lhs.GetSrcPort() > rhs.GetSrcPort()) + return false; + +if (lhs.GetDstPort() < rhs.GetDstPort()) + return true; +if (lhs.GetDstPort() > rhs.GetDstPort()) + return false; + +if (lhs.GetProto() < rhs.GetProto()) + return true; + +return false; +}*/ +//----------------------------------------------------------------------------- + +#endif + + diff --git a/include/resetable.h b/include/resetable.h new file mode 100644 index 00000000..d31bffda --- /dev/null +++ b/include/resetable.h @@ -0,0 +1,98 @@ + /* + $Revision: 1.9 $ + $Date: 2010/03/11 14:42:04 $ + $Author: faust $ + */ + +/* + * Copyright (c) 2001 by Peter Simons . + * All rights reserved. + */ + +#ifndef RESETABLE_VARIABLE_H +#define RESETABLE_VARIABLE_H + +// This is a wrapper class about variables where you want to keep +// track of whether it has been assigened yet or not. + +#include + +template +class RESETABLE +{ + template + friend std::ostream & operator<<(std::ostream & o, RESETABLE v); +public: + typedef varT value_type; + + //------------------------------------------------------------------------- + RESETABLE() + : value(), + is_set(false) + { + } + //------------------------------------------------------------------------- + RESETABLE(const RESETABLE & rvalue) + : value(rvalue.value), + is_set(rvalue.is_set) + { + } + //------------------------------------------------------------------------- + RESETABLE(const value_type& val) + : value(val), + is_set(true) + { + } + //------------------------------------------------------------------------- + RESETABLE & operator=(const RESETABLE & rvalue) + { + value = rvalue.value; + is_set = rvalue.is_set; + return *this; + } + //------------------------------------------------------------------------- + RESETABLE & operator= (const value_type& rhs) + { + value = rhs; + is_set = true; + return *this; + } + //------------------------------------------------------------------------- + const value_type& const_data() const throw() + { + return value; + } + //------------------------------------------------------------------------- + value_type& data() throw() + { + return value; + } + //------------------------------------------------------------------------- + operator const value_type&() const throw() + { + return value; + } + //------------------------------------------------------------------------- + bool res_empty() const throw() + { + return !is_set; + } + //------------------------------------------------------------------------- + void reset() throw() + { + is_set = false; + } + //------------------------------------------------------------------------- +protected: + value_type value; + bool is_set; +}; +//----------------------------------------------------------------------------- +template +std::ostream & operator<<(std::ostream & o, RESETABLE v) +{ + return o << v.value; +} +//------------------------------------------------------------------------- +#endif // RESETABLE_VARIABLE_H + diff --git a/include/rs_packets.h b/include/rs_packets.h new file mode 100644 index 00000000..1a038be7 --- /dev/null +++ b/include/rs_packets.h @@ -0,0 +1,36 @@ +#ifndef RS_PACKETSH +#define RS_PACKETSH + +#define RS_MAGIC_LEN (6) +#define RS_PROTO_VER_LEN (2) +#define RS_MAX_PACKET_LEN (1048) +#define RS_LOGIN_LEN (32) +#define RS_PARAMS_LEN (979) + +#define RS_ALIVE_PACKET (0) +#define RS_CONNECT_PACKET (1) +#define RS_DISCONNECT_PACKET (2) + +#define RS_ID "RSP00" + +#include "os_int.h" + +struct RS_PACKET_HEADER +{ +int8_t magic[RS_MAGIC_LEN]; +int8_t protoVer[RS_PROTO_VER_LEN]; +int8_t packetType; +uint32_t ip; +uint32_t id; +int8_t login[RS_LOGIN_LEN]; +int8_t padding[7]; +} __attribute__((__packed__)); // 48 bytes, 6 blocks + +struct RS_PACKET_TAIL +{ +int8_t magic[RS_MAGIC_LEN]; +int8_t params[RS_PARAMS_LEN]; +int8_t padding[7]; +} __attribute__((__packed__)); // 992 bytes, 124 blocks + +#endif diff --git a/include/service_conf.h b/include/service_conf.h new file mode 100644 index 00000000..6c958c41 --- /dev/null +++ b/include/service_conf.h @@ -0,0 +1,13 @@ +#ifndef SERVICE_CONF_H +#define SERVICE_CONF_H + +struct SERVICE_CONF +{ +string name; +string comment; +double cost; +int payDay; +}; + +#endif //SERVICE_CONF_H + diff --git a/include/stdstring.h b/include/stdstring.h new file mode 100644 index 00000000..dde89e9c --- /dev/null +++ b/include/stdstring.h @@ -0,0 +1,3737 @@ +// ============================================================================= +// FILE: StdString.h +// AUTHOR: Joe O'Leary (with outside help noted in comments) +// REMARKS: +// This header file declares the CStdStr template. This template derives +// the Standard C++ Library basic_string<> template and add to it the +// the following conveniences: +// - The full MFC CString set of functions (including implicit cast) +// - writing to/reading from COM IStream interfaces +// - Functional objects for use in STL algorithms +// +// From this template, we intstantiate two classes: CStdStringA and +// CStdStringW. The name "CStdString" is just a #define of one of these, +// based upone the _UNICODE macro setting +// +// This header also declares our own version of the MFC/ATL UNICODE-MBCS +// conversion macros. Our version looks exactly like the Microsoft's to +// facilitate portability. +// +// NOTE: +// If you you use this in an MFC or ATL build, you should include either +// afx.h or atlbase.h first, as appropriate. +// +// PEOPLE WHO HAVE CONTRIBUTED TO THIS CLASS: +// +// Several people have helped me iron out problems and othewise improve +// this class. OK, this is a long list but in my own defense, this code +// has undergone two major rewrites. Many of the improvements became +// necessary after I rewrote the code as a template. Others helped me +// improve the CString facade. +// +// Anyway, these people are (in chronological order): +// +// - Pete the Plumber (???) +// - Julian Selman +// - Chris (of Melbsys) +// - Dave Plummer +// - John C Sipos +// - Chris Sells +// - Nigel Nunn +// - Fan Xia +// - Matthew Williams +// - Carl Engman +// - Mark Zeren +// - Craig Watson +// - Rich Zuris +// - Karim Ratib +// - Chris Conti +// - Baptiste Lepilleur +// - Greg Pickles +// - Jim Cline +// - Jeff Kohn +// - Todd Heckel +// - Ullrich Pollähne +// - Joe Vitaterna +// - Joe Woodbury +// - Aaron (no last name) +// - Joldakowski (???) +// - Scott Hathaway +// - Eric Nitzche +// - Pablo Presedo +// - Farrokh Nejadlotfi +// - Jason Mills +// - Igor Kholodov +// - Mike Crusader +// - John James +// - Wang Haifeng +// - Tim Dowty +// - Arnt Witteveen +// - Glen Maynard +// - Paul DeMarco +// - Bagira (full name?) +// - Ronny Schulz +// - Jakko Van Hunen +// - Charles G +// +// REVISION HISTORY +// 2003-JUL-10 - Thanks to Charles G for making me realize my 'FmtArg' fixes +// had inadvertently broken the DLL-export code (which is +// normally commented out. I had to move it up higher. Also +// this helped me catch a bug in ssicoll that would prevent +// compilation, otherwise. +// +// 2003-MAR-14 - Thanks to Jakko Van Hunen for pointing out a copy-and-paste +// bug in one of the overloads of FmtArg. +// +// 2003-MAR-10 - Thanks to Ronny Schulz for (twice!) sending me some changes +// to help CStdString build on SGI and for pointing out an +// error in placement of my preprocessor macros for ssfmtmsg. +// +// 2002-NOV-26 - Thanks to Bagira for pointing out that my implementation of +// SpanExcluding was not properly handling the case in which +// the string did NOT contain any of the given characters +// +// 2002-OCT-21 - Many thanks to Paul DeMarco who was invaluable in helping me +// get this code working with Borland's free compiler as well +// as the Dev-C++ compiler (available free at SourceForge). +// +// 2002-SEP-13 - Thanks to Glen Maynard who helped me get rid of some loud +// but harmless warnings that were showing up on g++. Glen +// also pointed out that some pre-declarations of FmtArg<> +// specializations were unnecessary (and no good on G++) +// +// 2002-JUN-26 - Thanks to Arnt Witteveen for pointing out that I was using +// static_cast<> in a place in which I should have been using +// reinterpret_cast<> (the ctor for unsigned char strings). +// That's what happens when I don't unit-test properly! +// Arnt also noticed that CString was silently correcting the +// 'nCount' argument to Left() and Right() where CStdString was +// not (and crashing if it was bad). That is also now fixed! +// +// 2002-FEB-25 - Thanks to Tim Dowty for pointing out (and giving me the fix +// for) a conversion problem with non-ASCII MBCS characters. +// CStdString is now used in my favorite commercial MP3 player! +// +// 2001-DEC-06 - Thanks to Wang Haifeng for spotting a problem in one of the +// assignment operators (for _bstr_t) that would cause compiler +// errors when refcounting protection was turned off. +// +// 2001-NOV-27 - Remove calls to operator!= which involve reverse_iterators +// due to a conflict with the rel_ops operator!=. Thanks to +// John James for pointing this out. +// +// 2001-OCT-29 - Added a minor range checking fix for the Mid function to +// make it as forgiving as CString's version is. Thanks to +// Igor Kholodov for noticing this. +// - Added a specialization of std::swap for CStdString. Thanks +// to Mike Crusader for suggesting this! It's commented out +// because you're not supposed to inject your own code into the +// 'std' namespace. But if you don't care about that, it's +// there if you want it +// - Thanks to Jason Mills for catching a case where CString was +// more forgiving in the Delete() function than I was. +// +// 2001-JUN-06 - I was violating the Standard name lookup rules stated +// in [14.6.2(3)]. None of the compilers I've tried so +// far apparently caught this but HP-UX aCC 3.30 did. The +// fix was to add 'this->' prefixes in many places. +// Thanks to Farrokh Nejadlotfi for this! +// +// 2001-APR-27 - StreamLoad was calculating the number of BYTES in one +// case, not characters. Thanks to Pablo Presedo for this. +// +// 2001-FEB-23 - Replace() had a bug which caused infinite loops if the +// source string was empty. Fixed thanks to Eric Nitzsche. +// +// 2001-FEB-23 - Scott Hathaway was a huge help in providing me with the +// ability to build CStdString on Sun Unix systems. He +// sent me detailed build reports about what works and what +// does not. If CStdString compiles on your Unix box, you +// can thank Scott for it. +// +// 2000-DEC-29 - Joldakowski noticed one overload of Insert failed to do a +// range check as CString's does. Now fixed -- thanks! +// +// 2000-NOV-07 - Aaron pointed out that I was calling static member +// functions of char_traits via a temporary. This was not +// technically wrong, but it was unnecessary and caused +// problems for poor old buggy VC5. Thanks Aaron! +// +// 2000-JUL-11 - Joe Woodbury noted that the CString::Find docs don't match +// what the CString::Find code really ends up doing. I was +// trying to match the docs. Now I match the CString code +// - Joe also caught me truncating strings for GetBuffer() calls +// when the supplied length was less than the current length. +// +// 2000-MAY-25 - Better support for STLPORT's Standard library distribution +// - Got rid of the NSP macro - it interfered with Koenig lookup +// - Thanks to Joe Woodbury for catching a TrimLeft() bug that +// I introduced in January. Empty strings were not getting +// trimmed +// +// 2000-APR-17 - Thanks to Joe Vitaterna for pointing out that ReverseFind +// is supposed to be a const function. +// +// 2000-MAR-07 - Thanks to Ullrich Pollähne for catching a range bug in one +// of the overloads of assign. +// +// 2000-FEB-01 - You can now use CStdString on the Mac with CodeWarrior! +// Thanks to Todd Heckel for helping out with this. +// +// 2000-JAN-23 - Thanks to Jim Cline for pointing out how I could make the +// Trim() function more efficient. +// - Thanks to Jeff Kohn for prompting me to find and fix a typo +// in one of the addition operators that takes _bstr_t. +// - Got rid of the .CPP file - you only need StdString.h now! +// +// 1999-DEC-22 - Thanks to Greg Pickles for helping me identify a problem +// with my implementation of CStdString::FormatV in which +// resulting string might not be properly NULL terminated. +// +// 1999-DEC-06 - Chris Conti pointed yet another basic_string<> assignment +// bug that MS has not fixed. CStdString did nothing to fix +// it either but it does now! The bug was: create a string +// longer than 31 characters, get a pointer to it (via c_str()) +// and then assign that pointer to the original string object. +// The resulting string would be empty. Not with CStdString! +// +// 1999-OCT-06 - BufferSet was erasing the string even when it was merely +// supposed to shrink it. Fixed. Thanks to Chris Conti. +// - Some of the Q172398 fixes were not checking for assignment- +// to-self. Fixed. Thanks to Baptiste Lepilleur. +// +// 1999-AUG-20 - Improved Load() function to be more efficient by using +// SizeOfResource(). Thanks to Rich Zuris for this. +// - Corrected resource ID constructor, again thanks to Rich. +// - Fixed a bug that occurred with UNICODE characters above +// the first 255 ANSI ones. Thanks to Craig Watson. +// - Added missing overloads of TrimLeft() and TrimRight(). +// Thanks to Karim Ratib for pointing them out +// +// 1999-JUL-21 - Made all calls to GetBuf() with no args check length first. +// +// 1999-JUL-10 - Improved MFC/ATL independence of conversion macros +// - Added SS_NO_REFCOUNT macro to allow you to disable any +// reference-counting your basic_string<> impl. may do. +// - Improved ReleaseBuffer() to be as forgiving as CString. +// Thanks for Fan Xia for helping me find this and to +// Matthew Williams for pointing it out directly. +// +// 1999-JUL-06 - Thanks to Nigel Nunn for catching a very sneaky bug in +// ToLower/ToUpper. They should call GetBuf() instead of +// data() in order to ensure the changed string buffer is not +// reference-counted (in those implementations that refcount). +// +// 1999-JUL-01 - Added a true CString facade. Now you can use CStdString as +// a drop-in replacement for CString. If you find this useful, +// you can thank Chris Sells for finally convincing me to give +// in and implement it. +// - Changed operators << and >> (for MFC CArchive) to serialize +// EXACTLY as CString's do. So now you can send a CString out +// to a CArchive and later read it in as a CStdString. I have +// no idea why you would want to do this but you can. +// +// 1999-JUN-21 - Changed the CStdString class into the CStdStr template. +// - Fixed FormatV() to correctly decrement the loop counter. +// This was harmless bug but a bug nevertheless. Thanks to +// Chris (of Melbsys) for pointing it out +// - Changed Format() to try a normal stack-based array before +// using to _alloca(). +// - Updated the text conversion macros to properly use code +// pages and to fit in better in MFC/ATL builds. In other +// words, I copied Microsoft's conversion stuff again. +// - Added equivalents of CString::GetBuffer, GetBufferSetLength +// - new sscpy() replacement of CStdString::CopyString() +// - a Trim() function that combines TrimRight() and TrimLeft(). +// +// 1999-MAR-13 - Corrected the "NotSpace" functional object to use _istpace() +// instead of _isspace() Thanks to Dave Plummer for this. +// +// 1999-FEB-26 - Removed errant line (left over from testing) that #defined +// _MFC_VER. Thanks to John C Sipos for noticing this. +// +// 1999-FEB-03 - Fixed a bug in a rarely-used overload of operator+() that +// caused infinite recursion and stack overflow +// - Added member functions to simplify the process of +// persisting CStdStrings to/from DCOM IStream interfaces +// - Added functional objects (e.g. StdStringLessNoCase) that +// allow CStdStrings to be used as keys STL map objects with +// case-insensitive comparison +// - Added array indexing operators (i.e. operator[]). I +// originally assumed that these were unnecessary and would be +// inherited from basic_string. However, without them, Visual +// C++ complains about ambiguous overloads when you try to use +// them. Thanks to Julian Selman to pointing this out. +// +// 1998-FEB-?? - Added overloads of assign() function to completely account +// for Q172398 bug. Thanks to "Pete the Plumber" for this +// +// 1998-FEB-?? - Initial submission +// +// COPYRIGHT: +// 2002 Joseph M. O'Leary. This code is 100% free. Use it anywhere you +// want. Rewrite it, restructure it, whatever. If you can write software +// that makes money off of it, good for you. I kinda like capitalism. +// Please don't blame me if it causes your $30 billion dollar satellite +// explode in orbit. If you redistribute it in any form, I'd appreciate it +// if you would leave this notice here. +// +// If you find any bugs, please let me know: +// +// jmoleary@earthlink.net +// http://www.joeo.net +// +// The latest version of this code should always be available at the +// following link: +// +// http://www.joeo.net/code/StdString.zip +// ============================================================================= + +// Avoid multiple inclusion the VC++ way, +// Turn off browser references +// Turn off unavoidable compiler warnings + +#if defined(_MSC_VER) && (_MSC_VER > 1100) + #pragma once + #pragma component(browser, off, references, "CStdString") + #pragma warning (disable : 4290) // C++ Exception Specification ignored + #pragma warning (disable : 4127) // Conditional expression is constant + #pragma warning (disable : 4097) // typedef name used as synonym for class name +#endif + +// Borland warnings to turn off +#ifdef __BORLANDC__ + #pragma option push -w-inl +// #pragma warn -inl // Turn off inline function warnings +#endif + +#ifndef STDSTRING_H +#define STDSTRING_H + +// MACRO: SS_UNSIGNED +// ------------------ +// This macro causes the addition of a constructor and assignment operator +// which take unsigned characters. CString has such functions and in order +// to provide maximum CString-compatability, this code needs them as well. +// In practice you will likely never need these functions... + +//#define SS_UNSIGNED + +#ifdef SS_ALLOW_UNSIGNED_CHARS + #define SS_UNSIGNED +#endif + +// MACRO: SS_SAFE_FORMAT +// --------------------- +// This macro provides limited compatability with a questionable CString +// "feature". You can define it in order to avoid a common problem that +// people encounter when switching from CString to CStdString. +// +// To illustrate the problem -- With CString, you can do this: +// +// CString sName("Joe"); +// CString sTmp; +// sTmp.Format("My name is %s", sName); // WORKS! +// +// However if you were to try this with CStdString, your program would +// crash. +// +// CStdString sName("Joe"); +// CStdString sTmp; +// sTmp.Format("My name is %s", sName); // CRASHES! +// +// You must explicitly call c_str() or cast the object to the proper type +// +// sTmp.Format("My name is %s", sName.c_str()); // WORKS! +// sTmp.Format("My name is %s", static_cast(sName));// WORKS! +// sTmp.Format("My name is %s", (PCSTR)sName);// WORKS! +// +// This is because it is illegal to pass anything but a POD type as a +// variadic argument to a variadic function (i.e. as one of the "..." +// arguments). The type const char* is a POD type. The type CStdString +// is not. Of course, neither is the type CString, but CString lets you do +// it anyway due to the way they laid out the class in binary. I have no +// control over this in CStdString since I derive from whatever +// implementation of basic_string is available. +// +// However if you have legacy code (which does this) that you want to take +// out of the MFC world and you don't want to rewrite all your calls to +// Format(), then you can define this flag and it will no longer crash. +// +// Note however that this ONLY works for Format(), not sprintf, fprintf, +// etc. If you pass a CStdString object to one of those functions, your +// program will crash. Not much I can do to get around this, short of +// writing substitutes for those functions as well. + +#define SS_SAFE_FORMAT // use new template style Format() function + + +// MACRO: SS_NO_IMPLICIT_CAST +// -------------------------- +// Some people don't like the implicit cast to const char* (or rather to +// const CT*) that CStdString (and MFC's CString) provide. That was the +// whole reason I created this class in the first place, but hey, whatever +// bakes your cake. Just #define this macro to get rid of the the implicit +// cast. + +//#define SS_NO_IMPLICIT_CAST // gets rid of operator const CT*() + + +// MACRO: SS_NO_REFCOUNT +// --------------------- +// turns off reference counting at the assignment level. Only needed +// for the version of basic_string<> that comes with Visual C++ versions +// 6.0 or earlier, and only then in some heavily multithreaded scenarios. +// Uncomment it if you feel you need it. + +//#define SS_NO_REFCOUNT + +// MACRO: SS_WIN32 +// --------------- +// When this flag is set, we are building code for the Win32 platform and +// may use Win32 specific functions (such as LoadString). This gives us +// a couple of nice extras for the code. +// +// Obviously, Microsoft's is not the only compiler available for Win32 out +// there. So I can't just check to see if _MSC_VER is defined to detect +// if I'm building on Win32. So for now, if you use MS Visual C++ or +// Borland's compiler, I turn this on. Otherwise you may turn it on +// yourself, if you prefer +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(_WIN32) + #define SS_WIN32 +#endif + +// MACRO: SS_ANSI +// -------------- +// When this macro is defined, the code attempts only to use ANSI/ISO +// standard library functions to do it's work. It will NOT attempt to use +// any Win32 of Visual C++ specific functions -- even if they are +// available. You may define this flag yourself to prevent any Win32 +// of VC++ specific functions from being called. + +// If we're not on Win32, we MUST use an ANSI build +#ifndef SS_WIN32 + #if !defined(SS_NO_ANSI) + #define SS_ANSI + #endif +#endif + +// MACRO: SS_ALLOCA +// ---------------- +// Some implementations of the Standard C Library have a non-standard +// function known as alloca(). This functions allows one to allocate a +// variable amount of memory on the stack. It comes in very useful for +// the ASCII/MBCS conversion macros. +// +// Here we attempt to determine automatically if alloca() is available on +// this platform. If so we define SS_ALLOCA to be the name of the alloca +// function. If SS_ALLOCA is undefined later on, then the conversion +// macros will not be compiled. +// +// You may prevent SS_ALLOCA + + + +// Avoid legacy code screw up: if _UNICODE is defined, UNICODE must be as well + +#if defined (_UNICODE) && !defined (UNICODE) + #define UNICODE +#endif +#if defined (UNICODE) && !defined (_UNICODE) + #define _UNICODE +#endif + +// ----------------------------------------------------------------------------- +// MIN and MAX. The Standard C++ template versions go by so many names (at +// at least in the MS implementation) that you never know what's available +// ----------------------------------------------------------------------------- +template +inline const Type& SSMIN(const Type& arg1, const Type& arg2) +{ + return arg2 < arg1 ? arg2 : arg1; +} +template +inline const Type& SSMAX(const Type& arg1, const Type& arg2) +{ + return arg2 > arg1 ? arg2 : arg1; +} + +// If they have not #included W32Base.h (part of my W32 utility library) then +// we need to define some stuff. Otherwise, this is all defined there. + +#if !defined(W32BASE_H) + + // If they want us to use only standard C++ stuff (no Win32 stuff) + + #ifdef SS_ANSI + + // On Win32 we have TCHAR.H so just include it. This is NOT violating + // the spirit of SS_ANSI as we are not calling any Win32 functions here. + + #ifdef SS_WIN32 + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // ... but on non-Win32 platforms, we must #define the types we need. + + #else + + typedef const char* PCSTR; + typedef char* PSTR; + typedef const wchar_t* PCWSTR; + typedef wchar_t* PWSTR; + #ifdef UNICODE + typedef wchar_t TCHAR; + #else + typedef char TCHAR; + #endif + typedef wchar_t OLECHAR; + + #endif // #ifndef _WIN32 + + + // Make sure ASSERT and verify are defined using only ANSI stuff + + #ifndef ASSERT + #include + #define ASSERT(f) assert((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #else // ...else SS_ANSI is NOT defined + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // Make sure ASSERT and verify are defined + + #ifndef ASSERT + #include + #define ASSERT(f) _ASSERTE((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #endif // #ifdef SS_ANSI + + #ifndef UNUSED + #define UNUSED(x) x + #endif + +#endif // #ifndef W32BASE_H + +// Standard headers needed + +#include // basic_string +#include // for_each, etc. +#include // for StdStringLessNoCase, et al +#include // for various facets + +// If this is a recent enough version of VC include comdef.h, so we can write +// member functions to deal with COM types & compiler support classes e.g. _bstr_t + +#if defined (_MSC_VER) && (_MSC_VER >= 1100) + #include + #define SS_INC_COMDEF // signal that we #included MS comdef.h file + #define STDSTRING_INC_COMDEF + #define SS_NOTHROW __declspec(nothrow) +#else + #define SS_NOTHROW +#endif + +#ifndef TRACE + #define TRACE_DEFINED_HERE + #define TRACE +#endif + +// Microsoft defines PCSTR, PCWSTR, etc, but no PCTSTR. I hate to use the +// versions with the "L" in front of them because that's a leftover from Win 16 +// days, even though it evaluates to the same thing. Therefore, Define a PCSTR +// as an LPCTSTR. + +#if !defined(PCTSTR) && !defined(PCTSTR_DEFINED) + typedef const TCHAR* PCTSTR; + #define PCTSTR_DEFINED +#endif + +#if !defined(PCOLESTR) && !defined(PCOLESTR_DEFINED) + typedef const OLECHAR* PCOLESTR; + #define PCOLESTR_DEFINED +#endif + +#if !defined(POLESTR) && !defined(POLESTR_DEFINED) + typedef OLECHAR* POLESTR; + #define POLESTR_DEFINED +#endif + +#if !defined(PCUSTR) && !defined(PCUSTR_DEFINED) + typedef const unsigned char* PCUSTR; + typedef unsigned char* PUSTR; + #define PCUSTR_DEFINED +#endif + + +// SGI compiler 7.3 doesnt know these types - oh and btw, remember to use +// -LANG:std in the CXX Flags +#if defined(__sgi) + typedef unsigned long DWORD; + typedef void * LPCVOID; +#endif + + +// SS_USE_FACET macro and why we need it: +// +// Since I'm a good little Standard C++ programmer, I use locales. Thus, I +// need to make use of the use_facet<> template function here. Unfortunately, +// this need is complicated by the fact the MS' implementation of the Standard +// C++ Library has a non-standard version of use_facet that takes more +// arguments than the standard dictates. Since I'm trying to write CStdString +// to work with any version of the Standard library, this presents a problem. +// +// The upshot of this is that I can't do 'use_facet' directly. The MS' docs +// tell me that I have to use a macro, _USE() instead. Since _USE obviously +// won't be available in other implementations, this means that I have to write +// my OWN macro -- SS_USE_FACET -- that evaluates either to _USE or to the +// standard, use_facet. +// +// If you are having trouble with the SS_USE_FACET macro, in your implementation +// of the Standard C++ Library, you can define your own version of SS_USE_FACET. +#ifndef schMSG + #define schSTR(x) #x + #define schSTR2(x) schSTR(x) + #define schMSG(desc) message(__FILE__ "(" schSTR2(__LINE__) "):" #desc) +#endif + +#ifndef SS_USE_FACET + // STLPort #defines a macro (__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) for + // all MSVC builds, erroneously in my opinion. It causes problems for + // my SS_ANSI builds. In my code, I always comment out that line. You'll + // find it in \stlport\config\stl_msvc.h + #if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 ) + #if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER) + #ifdef SS_ANSI + #pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!) + #endif + #endif + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + #elif defined(_MSC_VER ) + #define SS_USE_FACET(loc, fac) std::_USE(loc, fac) + + // ...and + #elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) + #define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0) + #else + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + #endif +#endif + +// ============================================================================= +// UNICODE/MBCS conversion macros. Made to work just like the MFC/ATL ones. +// ============================================================================= + +#include // Added to Std Library with Amendment #1. + +// First define the conversion helper functions. We define these regardless of +// any preprocessor macro settings since their names won't collide. + +// Not sure if we need all these headers. I believe ANSI says we do. + +#include +#include +#include +#include +#include +#ifndef va_start + #include +#endif + +// StdCodeCvt - made to look like Win32 functions WideCharToMultiByte +// and MultiByteToWideChar but uses locales in SS_ANSI +// builds +//typedef int mbstate_t; +#if defined (SS_ANSI) || !defined (SS_WIN32) + + typedef std::codecvt SSCodeCvt; + + + inline PWSTR StdCodeCvt(PWSTR pW, PCSTR pA, int nChars, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pA); + ASSERT(0 != pW); + pW[0] = '\0'; + PCSTR pBadA = 0; + PWSTR pBadW = 0; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.in(st, + pA, pA + nChars, pBadA, + pW, pW + nChars, pBadW); + ASSERT(SSCodeCvt::ok == res); + return pW; + } + inline PWSTR StdCodeCvt(PWSTR pW, PCUSTR pA, int nChars, + const std::locale& loc=std::locale()) + { + return StdCodeCvt(pW, (PCSTR)pA, nChars, loc); + } + + inline PSTR StdCodeCvt(PSTR pA, PCWSTR pW, int nChars, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pA); + ASSERT(0 != pW); + pA[0] = '\0'; + PSTR pBadA = 0; + PCWSTR pBadW = 0; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.out(st, + pW, pW + nChars, pBadW, + pA, pA + nChars, pBadA); + ASSERT(SSCodeCvt::ok == res); + return pA; + } + inline PUSTR StdCodeCvt(PUSTR pA, PCWSTR pW, int nChars, + const std::locale& loc=std::locale()) + { + return (PUSTR)StdCodeCvt((PSTR)pA, pW, nChars, loc); + } + +#else // ...or are we doing things assuming win32 and Visual C++? + + #include // needed for _alloca + + inline PWSTR StdCodeCvt(PWSTR pW, PCSTR pA, int nChars, UINT acp=CP_ACP) + { + ASSERT(0 != pA); + ASSERT(0 != pW); + pW[0] = '\0'; + MultiByteToWideChar(acp, 0, pA, -1, pW, nChars); + return pW; + } + inline PWSTR StdCodeCvt(PWSTR pW, PCUSTR pA, int nChars, UINT acp=CP_ACP) + { + return StdCodeCvt(pW, (PCSTR)pA, nChars, acp); + } + + inline PSTR StdCodeCvt(PSTR pA, PCWSTR pW, int nChars, UINT acp=CP_ACP) + { + ASSERT(0 != pA); + ASSERT(0 != pW); + pA[0] = '\0'; + WideCharToMultiByte(acp, 0, pW, -1, pA, nChars, 0, 0); + return pA; + } + inline PUSTR StdCodeCvt(PUSTR pA, PCWSTR pW, int nChars, UINT acp=CP_ACP) + { + return (PUSTR)StdCodeCvt((PSTR)pA, pW, nChars, acp); + } + +#endif +// Unicode/MBCS conversion macros are only available on implementations of +// the "C" library that have the non-standard _alloca function. As far as I +// know that's only Microsoft's though I've hear that the function exits +// elsewhere. + +#if defined(SS_ALLOCA) && !defined SS_NO_CONVERSION + + #include // needed for _alloca + + + // Define our conversion macros to look exactly like Microsoft's to + // facilitate using this stuff both with and without MFC/ATL + + #ifdef _CONVERSION_USES_THREAD_LOCALE + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=GetACP(); \ + _acp; PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=GetACP();\ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #else + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=CP_ACP; _acp;\ + PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=CP_ACP; \ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #endif + + #ifdef _CONVERSION_USES_THREAD_LOCALE + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)+1),\ + StdCodeCvt((PWSTR) _alloca(_cvt*2), _pa, _cvt, _acp))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)+1)*2,\ + StdW2AHelper((LPSTR) _alloca(_cvt), _pw, _cvt, _acp))) + #else + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)+1),\ + StdCodeCvt((PWSTR) _alloca(_cvt*2), _pa, _cvt))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)+1)*2,\ + StdCodeCvt((LPSTR) _alloca(_cvt), _pw, _cvt))) + #endif + + #define SSA2CW(pa) ((PCWSTR)SSA2W((pa))) + #define SSW2CA(pw) ((PCSTR)SSW2A((pw))) + + #ifdef UNICODE + #define SST2A SSW2A + #define SSA2T SSA2W + #define SST2CA SSW2CA + #define SSA2CT SSA2CW + inline PWSTR SST2W(PTSTR p) { return p; } + inline PTSTR SSW2T(PWSTR p) { return p; } + inline PCWSTR SST2CW(PCTSTR p) { return p; } + inline PCTSTR SSW2CT(PCWSTR p) { return p; } + #else + #define SST2W SSA2W + #define SSW2T SSW2A + #define SST2CW SSA2CW + #define SSW2CT SSW2CA + inline PSTR SST2A(PTSTR p) { return p; } + inline PTSTR SSA2T(PSTR p) { return p; } + inline PCSTR SST2CA(PCTSTR p) { return p; } + inline PCTSTR SSA2CT(PCSTR p) { return p; } + #endif // #ifdef UNICODE + + #if defined(UNICODE) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #elif defined(OLE2ANSI) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #else + //CharNextW doesn't work on Win95 so we use this + #define SST2COLE(pa) SSA2CW((pa)) + #define SST2OLE(pa) SSA2W((pa)) + #define SSOLE2CT(po) SSW2CA((po)) + #define SSOLE2T(po) SSW2A((po)) + #endif + + #ifdef OLE2ANSI + #define SSW2OLE SSW2A + #define SSOLE2W SSA2W + #define SSW2COLE SSW2CA + #define SSOLE2CW SSA2CW + inline POLESTR SSA2OLE(PSTR p) { return p; } + inline PSTR SSOLE2A(POLESTR p) { return p; } + inline PCOLESTR SSA2COLE(PCSTR p) { return p; } + inline PCSTR SSOLE2CA(PCOLESTR p){ return p; } + #else + #define SSA2OLE SSA2W + #define SSOLE2A SSW2A + #define SSA2COLE SSA2CW + #define SSOLE2CA SSW2CA + inline POLESTR SSW2OLE(PWSTR p) { return p; } + inline PWSTR SSOLE2W(POLESTR p) { return p; } + inline PCOLESTR SSW2COLE(PCWSTR p) { return p; } + inline PCWSTR SSOLE2CW(PCOLESTR p){ return p; } + #endif + + // Above we've defined macros that look like MS' but all have + // an 'SS' prefix. Now we need the real macros. We'll either + // get them from the macros above or from MFC/ATL. + + #if defined (USES_CONVERSION) + + #define _NO_STDCONVERSION // just to be consistent + + #else + + #ifdef _MFC_VER + + #include + #define _NO_STDCONVERSION // just to be consistent + + #else + + #define USES_CONVERSION SSCVT + #define A2CW SSA2CW + #define W2CA SSW2CA + #define T2A SST2A + #define A2T SSA2T + #define T2W SST2W + #define W2T SSW2T + #define T2CA SST2CA + #define A2CT SSA2CT + #define T2CW SST2CW + #define W2CT SSW2CT + #define ocslen sslen + #define ocscpy sscpy + #define T2COLE SST2COLE + #define OLE2CT SSOLE2CT + #define T2OLE SST2COLE + #define OLE2T SSOLE2CT + #define A2OLE SSA2OLE + #define OLE2A SSOLE2A + #define W2OLE SSW2OLE + #define OLE2W SSOLE2W + #define A2COLE SSA2COLE + #define OLE2CA SSOLE2CA + #define W2COLE SSW2COLE + #define OLE2CW SSOLE2CW + + #endif // #ifdef _MFC_VER + #endif // #ifndef USES_CONVERSION +#endif // #ifndef SS_NO_CONVERSION + +// Define ostring - generic name for std::basic_string + +#if !defined(ostring) && !defined(OSTRING_DEFINED) + typedef std::basic_string ostring; + #define OSTRING_DEFINED +#endif + +// StdCodeCvt when there's no conversion to be done +inline PSTR StdCodeCvt(PSTR pDst, PCSTR pSrc, int nChars) +{ + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits::copy(pDst, pSrc, nChars); + if ( nChars > 0 ) + pDst[nChars] = '\0'; + } + + return pDst; +} +inline PSTR StdCodeCvt(PSTR pDst, PCUSTR pSrc, int nChars) +{ + return StdCodeCvt(pDst, (PCSTR)pSrc, nChars); +} +inline PUSTR StdCodeCvt(PUSTR pDst, PCSTR pSrc, int nChars) +{ + return (PUSTR)StdCodeCvt((PSTR)pDst, pSrc, nChars); +} + +inline PWSTR StdCodeCvt(PWSTR pDst, PCWSTR pSrc, int nChars) +{ + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits::copy(pDst, pSrc, nChars); + if ( nChars > 0 ) + pDst[nChars] = '\0'; + } + + return pDst; +} + + +// Define tstring -- generic name for std::basic_string + +#if !defined(tstring) && !defined(TSTRING_DEFINED) + typedef std::basic_string tstring; + #define TSTRING_DEFINED +#endif + +// a very shorthand way of applying the fix for KB problem Q172398 +// (basic_string assignment bug) + +#if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + #define Q172398(x) (x).erase() +#else + #define Q172398(x) +#endif + +// ============================================================================= +// INLINE FUNCTIONS ON WHICH CSTDSTRING RELIES +// +// Usually for generic text mapping, we rely on preprocessor macro definitions +// to map to string functions. However the CStdStr<> template cannot use +// macro-based generic text mappings because its character types do not get +// resolved until template processing which comes AFTER macro processing. In +// other words, UNICODE is of little help to us in the CStdStr template +// +// Therefore, to keep the CStdStr declaration simple, we have these inline +// functions. The template calls them often. Since they are inline (and NOT +// exported when this is built as a DLL), they will probably be resolved away +// to nothing. +// +// Without these functions, the CStdStr<> template would probably have to broken +// out into two, almost identical classes. Either that or it would be a huge, +// convoluted mess, with tons of "if" statements all over the place checking the +// size of template parameter CT. +// +// In several cases, you will see two versions of each function. One version is +// the more portable, standard way of doing things, while the other is the +// non-standard, but often significantly faster Visual C++ way. +// ============================================================================= + +// If they defined SS_NO_REFCOUNT, then we must convert all assignments + +#ifdef SS_NO_REFCOUNT + #define SSREF(x) (x).c_str() +#else + #define SSREF(x) (x) +#endif + +// ----------------------------------------------------------------------------- +// sslen: strlen/wcslen wrappers +// ----------------------------------------------------------------------------- +template inline int sslen(const CT* pT) +{ + return 0 == pT ? 0 : std::basic_string::traits_type::length(pT); +// return 0 == pT ? 0 : std::char_traits::length(pT); +} +inline SS_NOTHROW int sslen(const std::string& s) +{ + return s.length(); +} +inline SS_NOTHROW int sslen(const std::wstring& s) +{ + return s.length(); +} + +// ----------------------------------------------------------------------------- +// sstolower/sstoupper -- convert characters to upper/lower case +// ----------------------------------------------------------------------------- +template +inline CT sstolower(const CT& t, const std::locale& loc = std::locale()) +{ + return std::tolower(t, loc); +} +template +inline CT sstoupper(const CT& t, const std::locale& loc = std::locale()) +{ + return std::toupper(t, loc); +} + +// ----------------------------------------------------------------------------- +// ssasn: assignment functions -- assign "sSrc" to "sDst" +// ----------------------------------------------------------------------------- +typedef std::string::size_type SS_SIZETYPE; // just for shorthand, really +typedef std::string::pointer SS_PTRTYPE; +typedef std::wstring::size_type SW_SIZETYPE; +typedef std::wstring::pointer SW_PTRTYPE; + +inline void ssasn(std::string& sDst, const std::string& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +inline void ssasn(std::string& sDst, PCSTR pA) +{ + // Watch out for NULLs, as always. + + if ( 0 == pA ) + { + sDst.erase(); + } + + // If pA actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pA >= sDst.c_str() && pA <= sDst.c_str() + sDst.size() ) + { + sDst =sDst.substr(static_cast(pA-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pA); + } +} +inline void ssasn(std::string& sDst, const std::wstring& sSrc) +{ + int nLen = sSrc.size(); + sDst.resize(nLen * sizeof(wchar_t) + 1); + StdCodeCvt(const_cast(sDst.data()), sSrc.c_str(), nLen); + sDst.resize(nLen); + //sDst.resize(sslen(sDst.c_str())); +} +inline void ssasn(std::string& sDst, PCWSTR pW) +{ + int nLen = sslen(pW); + sDst.resize(nLen * sizeof(wchar_t) + 1); + StdCodeCvt(const_cast(sDst.data()), pW, nLen); + sDst.resize(nLen); + //sDst.resize(sslen(sDst.c_str())); +} +inline void ssasn(std::string& sDst, const int nNull) +{ + UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(""); +} +inline void ssasn(std::wstring& sDst, const std::wstring& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +inline void ssasn(std::wstring& sDst, PCWSTR pW) +{ + // Watch out for NULLs, as always. + + if ( 0 == pW ) + { + sDst.erase(); + } + + // If pW actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pW >= sDst.c_str() && pW <= sDst.c_str() + sDst.size() ) + { + sDst = sDst.substr(static_cast(pW-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pW); + } +} +#undef StrSizeType +inline void ssasn(std::wstring& sDst, const std::string& sSrc) +{ + int nLen = sSrc.size(); + sDst.resize(nLen+1); + StdCodeCvt(const_cast(sDst.data()), sSrc.c_str(), nLen+1); + sDst.resize(sslen(sDst.c_str())); +} +inline void ssasn(std::wstring& sDst, PCSTR pA) +{ + int nLen = sslen(pA); + sDst.resize(nLen+1); + StdCodeCvt(const_cast(sDst.data()), pA, nLen+1); + sDst.resize(sslen(sDst.c_str())); +} +inline void ssasn(std::wstring& sDst, const int nNull) +{ + UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(L""); +} + + +// ----------------------------------------------------------------------------- +// ssadd: string object concatenation -- add second argument to first +// ----------------------------------------------------------------------------- +inline void ssadd(std::string& sDst, const std::wstring& sSrc) +{ + int nSrcLen = sSrc.size(); + int nDstLen = sDst.size(); + int nEndLen = nSrcLen + nDstLen; + sDst.resize(nEndLen + 1); + StdCodeCvt(const_cast(sDst.data()+nDstLen), sSrc.c_str(), nSrcLen); + sDst.resize(nEndLen); +} +inline void ssadd(std::string& sDst, const std::string& sSrc) +{ + if ( &sDst == &sSrc ) + sDst.reserve(2*sDst.size()); + + sDst.append(sSrc.c_str()); +} +inline void ssadd(std::string& sDst, PCWSTR pW) +{ + int nSrcLen = sslen(pW); + int nDstLen = sDst.size(); + int nEndLen = nSrcLen + nDstLen; + sDst.resize(nEndLen + 1); + StdCodeCvt(const_cast(sDst.data()+nDstLen), pW, nSrcLen+1); + sDst.resize(nEndLen); +} +inline void ssadd(std::string& sDst, PCSTR pA) +{ + if ( pA ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pA >= sDst.c_str() && pA <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pA) ) + sDst.append(std::string(pA)); + else + sDst.append(pA); + } + else + { + sDst.append(pA); + } + } +} +inline void ssadd(std::wstring& sDst, const std::wstring& sSrc) +{ + if ( &sDst == &sSrc ) + sDst.reserve(2*sDst.size()); + + sDst.append(sSrc.c_str()); +} +inline void ssadd(std::wstring& sDst, const std::string& sSrc) +{ + int nSrcLen = sSrc.size(); + int nDstLen = sDst.size(); + int nEndLen = nSrcLen + nDstLen; + sDst.resize(nEndLen+1); + StdCodeCvt(const_cast(sDst.data()+nDstLen), sSrc.c_str(), nSrcLen+1); + sDst.resize(nEndLen); +} +inline void ssadd(std::wstring& sDst, PCSTR pA) +{ + int nSrcLen = sslen(pA); + int nDstLen = sDst.size(); + int nEndLen = nSrcLen + nDstLen; + sDst.resize(nEndLen + 1); + StdCodeCvt(const_cast(sDst.data()+nDstLen), pA, nSrcLen+1); + sDst.resize(nEndLen); +} +inline void ssadd(std::wstring& sDst, PCWSTR pW) +{ + if ( pW ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pW >= sDst.c_str() && pW <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pW) ) + sDst.append(std::wstring(pW)); + else + sDst.append(pW); + } + else + { + sDst.append(pW); + } + } +} + + +// ----------------------------------------------------------------------------- +// ssicmp: comparison (case insensitive ) +// ----------------------------------------------------------------------------- +template +inline int ssicmp(const CT* pA1, const CT* pA2) +{ + std::locale loc; + const std::ctype& ct = SS_USE_FACET(loc, std::ctype); + CT f; + CT l; + + do + { + f = ct.tolower(*(pA1++)); + l = ct.tolower(*(pA2++)); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssupr/sslwr: Uppercase/Lowercase conversion functions +// ----------------------------------------------------------------------------- + +template +inline void sslwr(CT* pT, size_t nLen) +{ + SS_USE_FACET(std::locale(), std::ctype).tolower(pT, pT+nLen); +} +template +inline void ssupr(CT* pT, size_t nLen) +{ + SS_USE_FACET(std::locale(), std::ctype).toupper(pT, pT+nLen); +} + + +// ----------------------------------------------------------------------------- +// vsprintf/vswprintf or _vsnprintf/_vsnwprintf equivalents. In standard +// builds we can't use _vsnprintf/_vsnwsprintf because they're MS extensions. +// ----------------------------------------------------------------------------- +#if defined(SS_ANSI) || !defined(_MSC_VER) + + // Borland's headers put some ANSI "C" functions in the 'std' namespace. + // Promote them to the global namespace so we can use them here. + + #if defined(__BORLANDC__) + using std::vsprintf; + using std::vswprintf; + #endif + inline int ssvsprintf(PSTR pA, size_t /*nCount*/, PCSTR pFmtA, va_list vl) + { + return vsprintf(pA, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + // JMO: Some distributions of the "C" have a version of vswprintf that + // takes 3 arguments (e.g. Microsoft, Borland, GNU). Others have a + // version which takes 4 arguments (an extra "count" argument in the + // second position. The best stab I can take at this so far is that if + // you are NOT running with MS, Borland, or GNU, then I'll assume you + // have the version that takes 4 arguments. + // + // I'm sure that these checks don't catch every platform correctly so if + // you get compiler errors on one of the lines immediately below, it's + // probably because your implemntation takes a different number of + // arguments. You can comment out the offending line (and use the + // alternate version) or you can figure out what compiler flag to check + // and add that preprocessor check in. Regardless, if you get an error + // on these lines, I'd sure like to hear from you about it. + // + // Thanks to Ronny Schulz for the SGI-specific checks here. + +// #if !defined(__MWERKS__) && !defined(__SUNPRO_CC_COMPAT) && !defined(__SUNPRO_CC) + #if !defined(_MSC_VER) \ + && !defined (__BORLANDC__) \ + && !defined(__GNUC__) \ + && !defined(__sgi) + + return vswprintf(pW, nCount, pFmtW, vl); + + // suddenly with the current SGI 7.3 compiler there is no such function as + // vswprintf and the substitute needs explicit casts to compile + + #elif defined(__sgi) + + nCount; + return vsprintf( (char *)pW, (char *)pFmtW, vl); + + #else + + nCount; + return vswprintf(pW, pFmtW, vl); + + #endif + + } +#else + inline int ssnprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return _vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssnprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return _vsnwprintf(pW, nCount, pFmtW, vl); + } +#endif + + + +// ----------------------------------------------------------------------------- +// ssload: Type safe, overloaded ::LoadString wrappers +// There is no equivalent of these in non-Win32-specific builds. However, I'm +// thinking that with the message facet, there might eventually be one +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline int ssload(HMODULE hInst, UINT uId, PSTR pBuf, int nMax) + { + return ::LoadStringA(hInst, uId, pBuf, nMax); + } + inline int ssload(HMODULE hInst, UINT uId, PWSTR pBuf, int nMax) + { + return ::LoadStringW(hInst, uId, pBuf, nMax); + } +#endif + + +// ----------------------------------------------------------------------------- +// sscoll/ssicoll: Collation wrappers +// Note -- with MSVC I have reversed the arguments order here because the +// functions appear to return the opposite of what they should +// ----------------------------------------------------------------------------- +template +inline int sscoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::collate& coll = + SS_USE_FACET(std::locale(), std::collate); + + return coll.compare(sz2, sz2+nLen2, sz1, sz1+nLen1); +} +template +inline int ssicoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::locale loc; + const std::collate& coll = SS_USE_FACET(loc, std::collate); + + // Some implementations seem to have trouble using the collate<> + // facet typedefs so we'll just default to basic_string and hope + // that's what the collate facet uses (which it generally should) + +// std::collate::string_type s1(sz1); +// std::collate::string_type s2(sz2); + const std::basic_string sEmpty; + std::basic_string s1(sz1 ? sz1 : sEmpty.c_str()); + std::basic_string s2(sz2 ? sz2 : sEmpty.c_str()); + + sslwr(const_cast(s1.c_str()), nLen1); + sslwr(const_cast(s2.c_str()), nLen2); + return coll.compare(s2.c_str(), s2.c_str()+nLen2, + s1.c_str(), s1.c_str()+nLen1); +} + + +// ----------------------------------------------------------------------------- +// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade +// Again -- no equivalent of these on non-Win32 builds but their might one day +// be one if the message facet gets implemented +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PWSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } +#else +#endif + + + +// FUNCTION: sscpy. Copies up to 'nMax' characters from pSrc to pDst. +// ----------------------------------------------------------------------------- +// FUNCTION: sscpy +// inline int sscpy(PSTR pDst, PCSTR pSrc, int nMax=-1); +// inline int sscpy(PUSTR pDst, PCSTR pSrc, int nMax=-1) +// inline int sscpy(PSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCSTR pSrc, int nMax=-1); +// +// DESCRIPTION: +// This function is very much (but not exactly) like strcpy. These +// overloads simplify copying one C-style string into another by allowing +// the caller to specify two different types of strings if necessary. +// +// The strings must NOT overlap +// +// "Character" is expressed in terms of the destination string, not +// the source. If no 'nMax' argument is supplied, then the number of +// characters copied will be sslen(pSrc). A NULL terminator will +// also be added so pDst must actually be big enough to hold nMax+1 +// characters. The return value is the number of characters copied, +// not including the NULL terminator. +// +// PARAMETERS: +// pSrc - the string to be copied FROM. May be a char based string, an +// MBCS string (in Win32 builds) or a wide string (wchar_t). +// pSrc - the string to be copied TO. Also may be either MBCS or wide +// nMax - the maximum number of characters to be copied into szDest. Note +// that this is expressed in whatever a "character" means to pDst. +// If pDst is a wchar_t type string than this will be the maximum +// number of wchar_ts that my be copied. The pDst string must be +// large enough to hold least nMaxChars+1 characters. +// If the caller supplies no argument for nMax this is a signal to +// the routine to copy all the characters in pSrc, regardless of +// how long it is. +// +// RETURN VALUE: none +// ----------------------------------------------------------------------------- +template +inline int sscpycvt(CT1* pDst, const CT2* pSrc, int nChars) +{ + StdCodeCvt(pDst, pSrc, nChars); + pDst[SSMAX(nChars, 0)] = '\0'; + return nChars; +} + +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax, int nLen) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, nLen)); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, sslen(pSrc))); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc) +{ + return sscpycvt(pDst, pSrc, sslen(pSrc)); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc, int nMax) +{ + return sscpycvt(pDst, sSrc.c_str(), SSMIN(nMax, (int)sSrc.length())); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc) +{ + return sscpycvt(pDst, sSrc.c_str(), (int)sSrc.length()); +} + +#ifdef SS_INC_COMDEF + template + inline int sscpy(CT1* pDst, const _bstr_t& bs, int nMax) + { + return sscpycvt(pDst, static_cast(bs), + SSMIN(nMax, static_cast(bs.length()))); + } + template + inline int sscpy(CT1* pDst, const _bstr_t& bs) + { + return sscpy(pDst, bs, static_cast(bs.length())); + } +#endif + + +// ----------------------------------------------------------------------------- +// Functional objects for changing case. They also let you pass locales +// ----------------------------------------------------------------------------- + +#ifdef SS_ANSI + template + struct SSToUpper : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstoupper(t, loc); + } + }; + template + struct SSToLower : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstolower(t, loc); + } + }; +#endif + +// This struct is used for TrimRight() and TrimLeft() function implementations. +//template +//struct NotSpace : public std::unary_function +//{ +// const std::locale& loc; +// inline NotSpace(const std::locale& locArg) : loc(locArg) {} +// inline bool operator() (CT t) { return !std::isspace(t, loc); } +//}; +template +struct NotSpace : public std::unary_function +{ + + // DINKUMWARE BUG: + // Note -- using std::isspace in a COM DLL gives us access violations + // because it causes the dynamic addition of a function to be called + // when the library shuts down. Unfortunately the list is maintained + // in DLL memory but the function is in static memory. So the COM DLL + // goes away along with the function that was supposed to be called, + // and then later when the DLL CRT shuts down it unloads the list and + // tries to call the long-gone function. + // This is DinkumWare's implementation problem. Until then, we will + // use good old isspace and iswspace from the CRT unless they + // specify SS_ANSI + + const std::locale loc; + NotSpace(const std::locale& locArg=std::locale()) : loc(locArg) {} + bool operator() (CT t) const { return !std::isspace(t, loc); } +}; + + + + +// Now we can define the template (finally!) +// ============================================================================= +// TEMPLATE: CStdStr +// template class CStdStr : public std::basic_string +// +// REMARKS: +// This template derives from basic_string and adds some MFC CString- +// like functionality +// +// Basically, this is my attempt to make Standard C++ library strings as +// easy to use as the MFC CString class. +// +// Note that although this is a template, it makes the assumption that the +// template argument (CT, the character type) is either char or wchar_t. +// ============================================================================= + +//#define CStdStr _SS // avoid compiler warning 4786 + +// template ARG& FmtArg(ARG& arg) { return arg; } +// PCSTR FmtArg(const std::string& arg) { return arg.c_str(); } +// PCWSTR FmtArg(const std::wstring& arg) { return arg.c_str(); } + +template +struct FmtArg +{ + explicit FmtArg(const ARG& arg) : a_(arg) {} + const ARG& Val() const { return a_; } + const ARG& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template +class CStdStr : public std::basic_string +{ + // Typedefs for shorter names. Using these names also appears to help + // us avoid some ambiguities that otherwise arise on some platforms + + typedef typename std::basic_string MYBASE; // my base class + typedef CStdStr MYTYPE; // myself + typedef typename MYBASE::const_pointer PCMYSTR; // PCSTR or PCWSTR + typedef typename MYBASE::pointer PMYSTR; // PSTR or PWSTR + typedef typename MYBASE::iterator MYITER; // my iterator type + typedef typename MYBASE::const_iterator MYCITER; // you get the idea... + typedef typename MYBASE::reverse_iterator MYRITER; + typedef typename MYBASE::size_type MYSIZE; + typedef typename MYBASE::value_type MYVAL; + typedef typename MYBASE::allocator_type MYALLOC; + +public: + + // shorthand conversion from PCTSTR to string resource ID + #define _TRES(pctstr) (LOWORD((DWORD)(pctstr))) + + // CStdStr inline constructors + CStdStr() + { + } + + CStdStr(const MYTYPE& str) : MYBASE(SSREF(str)) + { + } + + CStdStr(const std::string& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(const std::wstring& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(PCMYSTR pT, MYSIZE n) : MYBASE(pT, n) + { + } + +#ifdef SS_UNSIGNED + CStdStr(PCUSTR pU) + { + *this = reinterpret_cast(pU); + } +#endif + + CStdStr(PCSTR pA) + { + #ifdef SS_ANSI + *this = pA; + #else + if ( 0 != HIWORD(pA) ) + *this = pA; + else if ( 0 != pA && !Load(_TRES(pA)) ) + TRACE(_T("Can't load string %u\n"), _TRES(pA)); + #endif + } + + CStdStr(PCWSTR pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( 0 != HIWORD(pW) ) + *this = pW; + else if ( 0 != pW && !Load(_TRES(pW)) ) + TRACE(_T("Can't load string %u\n"), _TRES(pW)); + #endif + } + + CStdStr(MYCITER first, MYCITER last) + : MYBASE(first, last) + { + } + + CStdStr(MYSIZE nSize, MYVAL ch, const MYALLOC& al=MYALLOC()) + : MYBASE(nSize, ch, al) + { + } + + #ifdef SS_INC_COMDEF + CStdStr(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + this->append(static_cast(bstr), bstr.length()); + } + #endif + + // CStdStr inline assignment operators -- the ssasn function now takes care + // of fixing the MSVC assignment bug (see knowledge base article Q172398). + MYTYPE& operator=(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::string& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::wstring& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(PCSTR pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(PCWSTR pW) + { + ssasn(*this, pW); + return *this; + } + +#ifdef SS_UNSIGNED + MYTYPE& operator=(PCUSTR pU) + { + ssasn(*this, reinterpret_cast(pU)): + return *this; + } +#endif + + MYTYPE& operator=(CT t) + { + Q172398(*this); + this->assign(1, t); + return *this; + } + + #ifdef SS_INC_COMDEF + MYTYPE& operator=(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + { + this->assign(static_cast(bstr), bstr.length()); + return *this; + } + else + { + this->erase(); + return *this; + } + } + #endif + + + // Overloads also needed to fix the MSVC assignment bug (KB: Q172398) + // *** Thanks to Pete The Plumber for catching this one *** + // They also are compiled if you have explicitly turned off refcounting + #if ( defined(_MSC_VER) && ( _MSC_VER < 1200 ) ) || defined(SS_NO_REFCOUNT) + + MYTYPE& assign(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYTYPE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Pollähne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) + { + MYTYPE strTemp(str.c_str()+nStart, nChars); + MYBASE::assign(strTemp); + } + else + { + Q172398(*this); + MYBASE::assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const MYBASE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYBASE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Pollähne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) // watch out for assignment to self + { + MYTYPE strTemp(str.c_str() + nStart, nChars); + MYBASE::assign(strTemp); + } + else + { + Q172398(*this); + MYBASE::assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const CT* pC, MYSIZE nChars) + { + // Q172398 only fix -- erase before assigning, but not if we're + // assigning from our own buffer + + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + if ( !this->empty() && + ( pC < this->data() || pC > this->data() + this->capacity() ) ) + { + this->erase(); + } + #endif + Q172398(*this); + MYBASE::assign(pC, nChars); + return *this; + } + + MYTYPE& assign(MYSIZE nChars, MYVAL val) + { + Q172398(*this); + MYBASE::assign(nChars, val); + return *this; + } + + MYTYPE& assign(const CT* pT) + { + return this->assign(pT, MYBASE::traits_type::length(pT)); + } + + MYTYPE& assign(MYCITER iterFirst, MYCITER iterLast) + { + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + // Q172398 fix. don't call erase() if we're assigning from ourself + if ( iterFirst < this->begin() || + iterFirst > this->begin() + this->size() ) + { + this->erase() + } + #endif + this->replace(this->begin(), this->end(), iterFirst, iterLast); + return *this; + } + #endif + + + // ------------------------------------------------------------------------- + // CStdStr inline concatenation. + // ------------------------------------------------------------------------- + MYTYPE& operator+=(const MYTYPE& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::string& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::wstring& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(PCSTR pA) + { + ssadd(*this, pA); + return *this; + } + + MYTYPE& operator+=(PCWSTR pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(CT t) + { + this->append(1, t); + return *this; + } + #ifdef SS_INC_COMDEF // if we have _bstr_t, define a += for it too. + MYTYPE& operator+=(const _bstr_t& bstr) + { + return this->operator+=(static_cast(bstr)); + } + #endif + + + // ------------------------------------------------------------------------- + // Case changing functions + // ------------------------------------------------------------------------- + + MYTYPE& ToUpper() + { + // Strictly speaking, this would be about the most portable way + + // std::transform(begin(), + // end(), + // begin(), + // std::bind2nd(SSToUpper(), std::locale())); + + // But practically speaking, this works faster + + if ( !empty() ) + ssupr(GetBuf(), this->size()); + + return *this; + } + + + + MYTYPE& ToLower() + { + // Strictly speaking, this would be about the most portable way + + // std::transform(begin(), + // end(), + // begin(), + // std::bind2nd(SSToLower(), std::locale())); + + // But practically speaking, this works faster + + if ( !empty() ) + sslwr(GetBuf(), this->size()); + + return *this; + } + + + + MYTYPE& Normalize() + { + return Trim().ToLower(); + } + + + // ------------------------------------------------------------------------- + // CStdStr -- Direct access to character buffer. In the MS' implementation, + // the at() function that we use here also calls _Freeze() providing us some + // protection from multithreading problems associated with ref-counting. + // In VC 7 and later, of course, the ref-counting stuff is gone. + // ------------------------------------------------------------------------- + + CT* GetBuf(int nMinLen=-1) + { + if ( static_cast(size()) < nMinLen ) + this->resize(static_cast(nMinLen)); + + return this->empty() ? const_cast(this->data()) : &(this->at(0)); + } + + CT* SetBuf(int nLen) + { + nLen = ( nLen > 0 ? nLen : 0 ); + if ( this->capacity() < 1 && nLen == 0 ) + this->resize(1); + + this->resize(static_cast(nLen)); + return const_cast(this->data()); + } + void RelBuf(int nNewLen=-1) + { + this->resize(static_cast(nNewLen > -1 ? nNewLen : + sslen(this->c_str()))); + } + + void BufferRel() { RelBuf(); } // backwards compatability + CT* Buffer() { return GetBuf(); } // backwards compatability + CT* BufferSet(int nLen) { return SetBuf(nLen);}// backwards compatability + + bool Equals(const CT* pT, bool bUseCase=false) const + { // get copy, THEN compare (thread safe) + return bUseCase ? this->compare(pT) == 0 : + ssicmp(MYTYPE(*this).c_str(), pT) == 0; + } + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Load + // REMARKS: + // Loads string from resource specified by nID + // + // PARAMETERS: + // nID - resource Identifier. Purely a Win32 thing in this case + // + // RETURN VALUE: + // true if successful, false otherwise + // ------------------------------------------------------------------------- + +#ifndef SS_ANSI + + bool Load(UINT nId, HMODULE hModule=NULL) + { + bool bLoaded = false; // set to true of we succeed. + + #ifdef _MFC_VER // When in Rome (or MFC land)... + + CString strRes; + bLoaded = FALSE != strRes.LoadString(nId); + if ( bLoaded ) + *this = strRes; + + #else // otherwise make our own hackneyed version of CString's Load + + // Get the resource name and module handle + + if ( NULL == hModule ) + hModule = GetResourceHandle(); + + PCTSTR szName = MAKEINTRESOURCE((nId>>4)+1); // lifted + DWORD dwSize = 0; + + // No sense continuing if we can't find the resource + + HRSRC hrsrc = ::FindResource(hModule, szName, RT_STRING); + + if ( NULL == hrsrc ) + { + TRACE(_T("Cannot find resource %d: 0x%X"), nId, ::GetLastError()); + } + else if ( 0 == (dwSize = ::SizeofResource(hModule, hrsrc) / sizeof(CT))) + { + TRACE(_T("Cant get size of resource %d 0x%X\n"),nId,GetLastError()); + } + else + { + bLoaded = 0 != ssload(hModule, nId, GetBuf(dwSize), dwSize); + ReleaseBuffer(); + } + + #endif // #ifdef _MFC_VER + + if ( !bLoaded ) + TRACE(_T("String not loaded 0x%X\n"), ::GetLastError()); + + return bLoaded; + } + +#endif // #ifdef SS_ANSI + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Format + // void _cdecl Formst(CStdStringA& PCSTR szFormat, ...) + // void _cdecl Format(PCSTR szFormat); + // + // DESCRIPTION: + // This function does sprintf/wsprintf style formatting on CStdStringA + // objects. It looks a lot like MFC's CString::Format. Some people + // might even call this identical. Fortunately, these people are now + // dead... heh heh. + // + // PARAMETERS: + // nId - ID of string resource holding the format string + // szFormat - a PCSTR holding the format specifiers + // argList - a va_list holding the arguments for the format specifiers. + // + // RETURN VALUE: None. + // ------------------------------------------------------------------------- + // formatting (using wsprintf style formatting) + + // If they want a Format() function that safely handles string objects + // without casting + +#ifdef SS_SAFE_FORMAT + + // Question: Joe, you wacky coder you, why do you have so many overloads + // of the Format() function + // Answer: One reason only - CString compatability. In short, by making + // the Format() function a template this way, I can do strong typing + // and allow people to pass CStdString arguments as fillers for + // "%s" format specifiers without crashing their program! The downside + // is that I need to overload on the number of arguments. If you are + // passing more arguments than I have listed below in any of my + // overloads, just add another one. + // + // Yes, yes, this is really ugly. In essence what I am doing here is + // protecting people from a bad (and incorrect) programming practice + // that they should not be doing anyway. I am protecting them from + // themselves. Why am I doing this? Well, if you had any idea the + // number of times I've been emailed by people about this + // "incompatability" in my code, you wouldn't ask. + + void Fmt(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#ifndef SS_ANSI + + void Format(UINT nId) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + this->swap(strFmt); + } + template + void Format(UINT nId, const A1& v) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v).Val()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(),FmtArg(v5).Val(), + FmtArg(v6).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(),FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(),FmtArg(v10).Val(),FmtArg(v11).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(), FmtArg(v13).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(), FmtArg(v13).Val(),FmtArg(v14).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(),FmtArg(v13).Val(),FmtArg(v14).Val(), + FmtArg(v15).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(),FmtArg(v13).Val(),FmtArg(v14).Val(), + FmtArg(v15).Val(), FmtArg(v16).Val()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(),FmtArg(v13).Val(),FmtArg(v14).Val(), + FmtArg(v15).Val(),FmtArg(v16).Val(),FmtArg(v17).Val()); + } + } + +#endif // #ifndef SS_ANSI + + // ...now the other overload of Format: the one that takes a string literal + + void Format(const CT* szFmt) + { + *this = szFmt; + } + template + void Format(const CT* szFmt, A1 v) + { + Fmt(szFmt, FmtArg(v).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(),FmtArg(v10).Val(),FmtArg(v11).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(), FmtArg(v13).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(), FmtArg(v13).Val(),FmtArg(v14).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(),FmtArg(v13).Val(),FmtArg(v14).Val(), + FmtArg(v15).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(),FmtArg(v13).Val(),FmtArg(v14).Val(), + FmtArg(v15).Val(), FmtArg(v16).Val()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + Fmt(szFmt, FmtArg(v1).Val(), FmtArg(v2).Val(), + FmtArg(v3).Val(), FmtArg(v4).Val(), FmtArg(v5).Val(), + FmtArg(v6).Val(), FmtArg(v7).Val(), FmtArg(v8).Val(), + FmtArg(v9).Val(), FmtArg(v10).Val(),FmtArg(v11).Val(), + FmtArg(v12).Val(),FmtArg(v13).Val(),FmtArg(v14).Val(), + FmtArg(v15).Val(),FmtArg(v16).Val(),FmtArg(v17).Val()); + } + +#else // #ifdef SS_SAFE_FORMAT + + +#ifndef SS_ANSI + + void Format(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + va_start(argList, nId); + + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + FormatV(strFmt, argList); + + va_end(argList); + } + +#endif // #ifdef SS_ANSI + + void Format(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#endif // #ifdef SS_SAFE_FORMAT + + void AppendFormat(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + AppendFormatV(szFmt, argList); + va_end(argList); + } + + #define MAX_FMT_TRIES 5 // #of times we try + #define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try + #define BUFSIZE_1ST 256 + #define BUFSIZE_2ND 512 + #define STD_BUF_SIZE 1024 + + // an efficient way to add formatted characters to the string. You may only + // add up to STD_BUF_SIZE characters at a time, though + void AppendFormatV(const CT* szFmt, va_list argList) + { + CT szBuf[STD_BUF_SIZE]; + #if defined(SS_ANSI) || !defined(_MSC_VER) + int nLen = ssvsprintf(szBuf, STD_BUF_SIZE-1, szFmt, argList); + #else + int nLen = ssnprintf(szBuf, STD_BUF_SIZE-1, szFmt, argList); + #endif + if ( 0 < nLen ) + this->append(szBuf, nLen); + } + + // ------------------------------------------------------------------------- + // FUNCTION: FormatV + // void FormatV(PCSTR szFormat, va_list, argList); + // + // DESCRIPTION: + // This function formats the string with sprintf style format-specs. + // It makes a general guess at required buffer size and then tries + // successively larger buffers until it finds one big enough or a + // threshold (MAX_FMT_TRIES) is exceeded. + // + // PARAMETERS: + // szFormat - a PCSTR holding the format of the output + // argList - a Microsoft specific va_list for variable argument lists + // + // RETURN VALUE: + // ------------------------------------------------------------------------- + + void FormatV(const CT* szFormat, va_list argList) + { + #if defined(SS_ANSI) || !defined(_MSC_VER) + int nLen = sslen(szFormat) + STD_BUF_SIZE; + ssvsprintf(GetBuffer(nLen), nLen-1, szFormat, argList); + ReleaseBuffer(); + + #else + + CT* pBuf = NULL; + int nChars = 1; + int nUsed = 0; + size_type nActual = 0; + int nTry = 0; + + do + { + // Grow more than linearly (e.g. 512, 1536, 3072, etc) + + nChars += ((nTry+1) * FMT_BLOCK_SIZE); + pBuf = reinterpret_cast(_alloca(sizeof(CT)*nChars)); + nUsed = ssnprintf(pBuf, nChars-1, szFormat, argList); + + // Ensure proper NULL termination. + + nActual = nUsed == -1 ? nChars-1 : SSMIN(nUsed, nChars-1); + pBuf[nActual+1]= '\0'; + + + } while ( nUsed < 0 && nTry++ < MAX_FMT_TRIES ); + + // assign whatever we managed to format + + this->assign(pBuf, nActual); + + #endif + } + + + // ------------------------------------------------------------------------- + // CString Facade Functions: + // + // The following methods are intended to allow you to use this class as a + // drop-in replacement for CString. + // ------------------------------------------------------------------------- + #ifdef SS_WIN32 + BSTR AllocSysString() const + { + ostring os; + ssasn(os, *this); + return ::SysAllocString(os.c_str()); + } + #endif + + int Collate(PCMYSTR szThat) const + { + return sscoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int CollateNoCase(PCMYSTR szThat) const + { + return ssicoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int Compare(PCMYSTR szThat) const + { + return this->compare(szThat); + } + + int CompareNoCase(PCMYSTR szThat) const + { + return ssicmp(this->c_str(), szThat); + } + + int Delete(int nIdx, int nCount=1) + { + if ( nIdx < 0 ) + nIdx = 0; + + if ( nIdx < GetLength() ) + this->erase(static_cast(nIdx), static_cast(nCount)); + + return GetLength(); + } + + void Empty() + { + this->erase(); + } + + int Find(CT ch) const + { + MYSIZE nIdx = this->find_first_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub) const + { + MYSIZE nIdx = this->find(szSub); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(CT ch, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find_first_of(ch, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find(szSub, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int FindOneOf(PCMYSTR szCharSet) const + { + MYSIZE nIdx = this->find_first_of(szCharSet); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + +#ifndef SS_ANSI + void FormatMessage(PCMYSTR szFormat, ...) throw(std::exception) + { + va_list argList; + va_start(argList, szFormat); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + szFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0 ) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } + + void FormatMessage(UINT nFormatId, ...) throw(std::exception) + { + MYTYPE sFormat; + VERIFY(sFormat.LoadString(nFormatId) != 0); + va_list argList; + va_start(argList, nFormatId); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + sFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } +#endif + + + // ------------------------------------------------------------------------- + // GetXXXX -- Direct access to character buffer + // ------------------------------------------------------------------------- + CT GetAt(int nIdx) const + { + return this->at(static_cast(nIdx)); + } + + CT* GetBuffer(int nMinLen=-1) + { + return GetBuf(nMinLen); + } + + CT* GetBufferSetLength(int nLen) + { + return BufferSet(nLen); + } + + // GetLength() -- MFC docs say this is the # of BYTES but + // in truth it is the number of CHARACTERs (chars or wchar_ts) + int GetLength() const + { + return static_cast(this->length()); + } + + + int Insert(int nIdx, CT ch) + { + if ( static_cast(nIdx) > this->size() -1 ) + this->append(1, ch); + else + this->insert(static_cast(nIdx), 1, ch); + + return GetLength(); + } + int Insert(int nIdx, PCMYSTR sz) + { + if ( nIdx >= this->size() ) + this->append(sz, sslen(sz)); + else + this->insert(static_cast(nIdx), sz); + + return GetLength(); + } + + bool IsEmpty() const + { + return this->empty(); + } + + MYTYPE Left(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(0, static_cast(nCount)); + } + +#ifndef SS_ANSI + bool LoadString(UINT nId) + { + return this->Load(nId); + } +#endif + + void MakeLower() + { + ToLower(); + } + + void MakeReverse() + { + std::reverse(this->begin(), this->end()); + } + + void MakeUpper() + { + ToUpper(); + } + + MYTYPE Mid(int nFirst ) const + { + return Mid(nFirst, size()-nFirst); + } + + MYTYPE Mid(int nFirst, int nCount) const + { + // CString does range checking here. Since we're trying to emulate it, + // we must check too. + + if ( nFirst < 0 ) + nFirst = 0; + if ( nCount < 0 ) + nCount = 0; + + if ( nFirst + nCount > size() ) + nCount = size() - nFirst; + + if ( nFirst > size() ) + return MYTYPE(); + + ASSERT(nFirst >= 0); + ASSERT(nFirst + nCount <= size()); + + return this->substr(static_cast(nFirst), + static_cast(nCount)); + } + + void ReleaseBuffer(int nNewLen=-1) + { + RelBuf(nNewLen); + } + + int Remove(CT ch) + { + MYSIZE nIdx = 0; + int nRemoved = 0; + while ( (nIdx=this->find_first_of(ch)) != MYBASE::npos ) + { + this->erase(nIdx, 1); + nRemoved++; + } + return nRemoved; + } + + int Replace(CT chOld, CT chNew) + { + int nReplaced = 0; + for ( MYITER iter=this->begin(); iter != this->end(); iter++ ) + { + if ( *iter == chOld ) + { + *iter = chNew; + nReplaced++; + } + } + return nReplaced; + } + + int Replace(PCMYSTR szOld, PCMYSTR szNew) + { + int nReplaced = 0; + MYSIZE nIdx = 0; + MYSIZE nOldLen = sslen(szOld); + if ( 0 == nOldLen ) + return 0; + + static const CT ch = CT(0); + MYSIZE nNewLen = sslen(szNew); + PCMYSTR szRealNew = szNew == 0 ? &ch : szNew; + + while ( (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + replace(this->begin()+nIdx, this->begin()+nIdx+nOldLen, szRealNew); + nReplaced++; + nIdx += nNewLen; + } + return nReplaced; + } + + int ReverseFind(CT ch) const + { + MYSIZE nIdx = this->find_last_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + // ReverseFind overload that's not in CString but might be useful + int ReverseFind(PCMYSTR szFind, MYSIZE pos=MYBASE::npos) const + { + MYSIZE nIdx = this->rfind(0 == szFind ? MYTYPE() : szFind, pos); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + MYTYPE Right(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(this->size()-static_cast(nCount)); + } + + void SetAt(int nIndex, CT ch) + { + ASSERT(this->size() > static_cast(nIndex)); + this->at(static_cast(nIndex)) = ch; + } + +#ifndef SS_ANSI + BSTR SetSysString(BSTR* pbstr) const + { + ostring os; + ssasn(os, *this); + if ( !::SysReAllocStringLen(pbstr, os.c_str(), os.length()) ) + throw std::runtime_error("out of memory"); + + ASSERT(*pbstr != 0); + return *pbstr; + } +#endif + + MYTYPE SpanExcluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + + MYTYPE SpanIncluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_not_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + +#if !defined(UNICODE) && !defined(SS_ANSI) + + // CString's OemToAnsi and AnsiToOem functions are available only in + // Unicode builds. However since we're a template we also need a + // runtime check of CT and a reinterpret_cast to account for the fact + // that CStdStringW gets instantiated even in non-Unicode builds. + + void AnsiToOem() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::CharToOem(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + + void OemToAnsi() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::OemToChar(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + +#endif + + + // ------------------------------------------------------------------------- + // Trim and its variants + // ------------------------------------------------------------------------- + MYTYPE& Trim() + { + return TrimLeft().TrimRight(); + } + + MYTYPE& TrimLeft() + { + this->erase(this->begin(), + std::find_if(this->begin(), this->end(), NotSpace())); + + return *this; + } + + MYTYPE& TrimLeft(CT tTrim) + { + this->erase(0, this->find_first_not_of(tTrim)); + return *this; + } + + MYTYPE& TrimLeft(PCMYSTR szTrimChars) + { + this->erase(0, this->find_first_not_of(szTrimChars)); + return *this; + } + + MYTYPE& TrimRight() + { + // NOTE: When comparing reverse_iterators here (MYRITER), I avoid using + // operator!=. This is because namespace rel_ops also has a template + // operator!= which conflicts with the global operator!= already defined + // for reverse_iterator in the header . + // Thanks to John James for alerting me to this. + + MYRITER it = std::find_if(this->rbegin(), this->rend(), NotSpace()); + if ( !(this->rend() == it) ) + this->erase(this->rend() - it); + + this->erase(!(it == this->rend()) ? this->find_last_of(*it) + 1 : 0); + return *this; + } + + MYTYPE& TrimRight(CT tTrim) + { + MYSIZE nIdx = this->find_last_not_of(tTrim); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + MYTYPE& TrimRight(PCMYSTR szTrimChars) + { + MYSIZE nIdx = this->find_last_not_of(szTrimChars); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + void FreeExtra() + { + MYTYPE mt; + this->swap(mt); + if ( !mt.empty() ) + this->assign(mt.c_str(), mt.size()); + } + + // I have intentionally not implemented the following CString + // functions. You cannot make them work without taking advantage + // of implementation specific behavior. However if you absolutely + // MUST have them, uncomment out these lines for "sort-of-like" + // their behavior. You're on your own. + +// CT* LockBuffer() { return GetBuf(); }// won't really lock +// void UnlockBuffer(); { } // why have UnlockBuffer w/o LockBuffer? + + // Array-indexing operators. Required because we defined an implicit cast + // to operator const CT* (Thanks to Julian Selman for pointing this out) + CT& operator[](int nIdx) + { + return MYBASE::operator[](static_cast(nIdx)); + } + + const CT& operator[](int nIdx) const + { + return MYBASE::operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned int nIdx) + { + return MYBASE::operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned int nIdx) const + { + return MYBASE::operator[](static_cast(nIdx)); + } + +#ifndef SS_NO_IMPLICIT_CAST + operator const CT*() const + { + return this->c_str(); + } +#endif + + // IStream related functions. Useful in IPersistStream implementations + +#ifdef SS_INC_COMDEF + + // struct SSSHDR - useful for non Std C++ persistence schemes. + typedef struct SSSHDR + { + BYTE byCtrl; + ULONG nChars; + } SSSHDR; // as in "Standard String Stream Header" + + #define SSSO_UNICODE 0x01 // the string is a wide string + #define SSSO_COMPRESS 0x02 // the string is compressed + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSize + // REMARKS: + // Returns how many bytes it will take to StreamSave() this CStdString + // object to an IStream. + // ------------------------------------------------------------------------- + ULONG StreamSize() const + { + // Control header plus string + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + return (this->size() * sizeof(CT)) + sizeof(SSSHDR); + } + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSave + // REMARKS: + // Saves this CStdString object to a COM IStream. + // ------------------------------------------------------------------------- + HRESULT StreamSave(IStream* pStream) const + { + ASSERT(size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + HRESULT hr = E_FAIL; + ASSERT(pStream != 0); + SSSHDR hdr; + hdr.byCtrl = sizeof(CT) == 2 ? SSSO_UNICODE : 0; + hdr.nChars = this->size(); + + + if ( FAILED(hr=pStream->Write(&hdr, sizeof(SSSHDR), 0)) ) + TRACE(_T("StreamSave: Cannot write control header, ERR=0x%X\n"),hr); + else if ( empty() ) + ; // nothing to write + else if ( FAILED(hr=pStream->Write(this->c_str(), this->size()*sizeof(CT), 0)) ) + TRACE(_T("StreamSave: Cannot write string to stream 0x%X\n"), hr); + + return hr; + } + + + // ------------------------------------------------------------------------- + // FUNCTION: StreamLoad + // REMARKS: + // This method loads the object from an IStream. + // ------------------------------------------------------------------------- + HRESULT StreamLoad(IStream* pStream) + { + ASSERT(pStream != 0); + SSSHDR hdr; + HRESULT hr = E_FAIL; + + if ( FAILED(hr=pStream->Read(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamLoad: Cant read control header, ERR=0x%X\n"), hr); + } + else if ( hdr.nChars > 0 ) + { + ULONG nRead = 0; + PMYSTR pMyBuf = BufferSet(hdr.nChars); + + // If our character size matches the character size of the string + // we're trying to read, then we can read it directly into our + // buffer. Otherwise, we have to read into an intermediate buffer + // and convert. + + if ( (hdr.byCtrl & SSSO_UNICODE) != 0 ) + { + ULONG nBytes = hdr.nChars * sizeof(wchar_t); + if ( sizeof(CT) == sizeof(wchar_t) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PWSTR pBufW = reinterpret_cast(_alloca((nBytes)+1)); + if ( FAILED(hr=pStream->Read(pBufW, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufW, hdr.nChars); + } + } + else + { + ULONG nBytes = hdr.nChars * sizeof(char); + if ( sizeof(CT) == sizeof(char) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PSTR pBufA = reinterpret_cast(_alloca(nBytes)); + if ( FAILED(hr=pStream->Read(pBufA, hdr.nChars, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufA, hdr.nChars); + } + } + } + else + { + this->erase(); + } + return hr; + } +#endif // #ifdef SS_INC_COMDEF + +#ifndef SS_ANSI + + // SetResourceHandle/GetResourceHandle. In MFC builds, these map directly + // to AfxSetResourceHandle and AfxGetResourceHandle. In non-MFC builds they + // point to a single static HINST so that those who call the member + // functions that take resource IDs can provide an alternate HINST of a DLL + // to search. This is not exactly the list of HMODULES that MFC provides + // but it's better than nothing. + +#ifdef _MFC_VER + static void SetResourceHandle(HMODULE hNew) + { + AfxSetResourceHandle(hNew); + } + static HMODULE GetResourceHandle() + { + return AfxGetResourceHandle(); + } +#else + static void SetResourceHandle(HMODULE hNew) + { + SSResourceHandle() = hNew; + } + static HMODULE GetResourceHandle() + { + return SSResourceHandle(); + } +#endif + + + template + MYTYPE operator+(const CStdStr& s2) + { + MYTYPE strRet(SSREF(*this)); + strRet += s2.c_str(); + return strRet; + } + + +#endif +}; + + + +// ----------------------------------------------------------------------------- +// CStdStr friend addition functions defined as inline +// ----------------------------------------------------------------------------- +template +inline +CStdStr operator+(const CStdStr& str1, const CStdStr& str2) +{ + CStdStr strRet(SSREF(str1)); + strRet.append(str2); + return strRet; +} + +template +inline +CStdStr operator+(const CStdStr& str, CT t) +{ + // this particular overload is needed for disabling reference counting + // though it's only an issue from line 1 to line 2 + + CStdStr strRet(SSREF(str)); // 1 + strRet.append(1, t); // 2 + return strRet; +} + +template +inline +CStdStr operator+(const CStdStr& str, PCSTR pA) +{ + return CStdStr(str) + CStdStr(pA); +} + +template +inline +CStdStr operator+(PCSTR pA, const CStdStr& str) +{ + CStdStr strRet(pA); + strRet.append(str); + return strRet; +} + +template +inline +CStdStr operator+(const CStdStr& str, PCWSTR pW) +{ + return CStdStr(SSREF(str)) + CStdStr(pW); +} + +template +inline +CStdStr operator+(PCWSTR pW, const CStdStr& str) +{ + CStdStr strRet(pW); + strRet.append(str); + return strRet; +} + +#ifdef SS_INC_COMDEF + template + inline + CStdStr operator+(const _bstr_t& bstr, const CStdStr& str) + { + return static_cast(bstr) + str; + } + + template + inline + CStdStr operator+(const CStdStr& str, const _bstr_t& bstr) + { + return str + static_cast(bstr); + } +#endif + +// ----------------------------------------------------------------------------- +// HOW TO EXPORT CSTDSTRING FROM A DLL +// +// If you want to export CStdStringA and CStdStringW from a DLL, then all you +// need to +// 1. make sure that all components link to the same DLL version +// of the CRT (not the static one). +// 2. Uncomment the 3 lines of code below +// 3. #define 2 macros per the instructions in MS KnowledgeBase +// article Q168958. The macros are: +// +// MACRO DEFINTION WHEN EXPORTING DEFINITION WHEN IMPORTING +// ----- ------------------------ ------------------------- +// SSDLLEXP (nothing, just #define it) extern +// SSDLLSPEC __declspec(dllexport) __declspec(dllimport) +// +// Note that these macros must be available to ALL clients who want to +// link to the DLL and use the class. If they +// ----------------------------------------------------------------------------- +//#pragma warning(disable:4231) // non-standard extension ("extern template") +// SSDLLEXP template class SSDLLSPEC CStdStr; +// SSDLLEXP template class SSDLLSPEC CStdStr; + +// ============================================================================= +// END OF CStdStr INLINE FUNCTION DEFINITIONS +// ============================================================================= + +// Now typedef our class names based upon this humongous template + +typedef CStdStr CStdStringA; // a better std::string +typedef CStdStr CStdStringW; // a better std::wstring +typedef CStdStr CStdStringO; // almost always CStdStringW + + +// New-style format function is a template + +#ifdef SS_SAFE_FORMAT + +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringA& arg) : a_(arg) {} + PCSTR Val() const { return a_.c_str(); } + const CStdStringA& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringW& arg) : a_(arg) {} + PCWSTR Val() const { return a_.c_str(); } + const CStdStringW& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template<> +struct FmtArg +{ + explicit FmtArg(const std::string& arg) : a_(arg) {} + PCSTR Val() const { return a_.c_str(); } + const std::string& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const std::wstring& arg) : a_(arg) {} + PCWSTR Val() const { return a_.c_str(); } + const std::wstring& a_; +private: + FmtArg& operator=(const FmtArg&) {return *this;} +}; +#endif // #ifdef SS_SAFEFORMAT + +#ifndef SS_ANSI + // SSResourceHandle: our MFC-like resource handle + inline HMODULE& SSResourceHandle() + { + static HMODULE hModuleSS = GetModuleHandle(0); + return hModuleSS; + } +#endif + + + + +// In MFC builds, define some global serialization operators +// Special operators that allow us to serialize CStdStrings to CArchives. +// Note that we use an intermediate CString object in order to ensure that +// we use the exact same format. + +#ifdef _MFC_VER + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA) + { + CString strTemp = strA; + return ar << strTemp; + } + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringW& strW) + { + CString strTemp = strW; + return ar << strTemp; + } + + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringA& strA) + { + CString strTemp; + ar >> strTemp; + strA = strTemp; + return ar; + } + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringW& strW) + { + CString strTemp; + ar >> strTemp; + strW = strTemp; + return ar; + } +#endif // #ifdef _MFC_VER -- (i.e. is this MFC?) + + + +// ----------------------------------------------------------------------------- +// GLOBAL FUNCTION: WUFormat +// CStdStringA WUFormat(UINT nId, ...); +// CStdStringA WUFormat(PCSTR szFormat, ...); +// +// REMARKS: +// This function allows the caller for format and return a CStdStringA +// object with a single line of code. +// ----------------------------------------------------------------------------- +#ifdef SS_ANSI +#else + + inline CStdStringA WUFormatA(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringA strFmt; + CStdStringA strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringA WUFormatA(PCSTR szFormat, ...) + { + va_list argList; + va_start(argList, szFormat); + CStdStringA strOut; + strOut.FormatV(szFormat, argList); + va_end(argList); + return strOut; + } + + inline CStdStringW WUFormatW(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringW strFmt; + CStdStringW strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(PCWSTR szwFormat, ...) + { + va_list argList; + va_start(argList, szwFormat); + CStdStringW strOut; + strOut.FormatV(szwFormat, argList); + va_end(argList); + return strOut; + } +#endif // #ifdef SS_ANSI + +#ifdef SS_WIN32 + // ------------------------------------------------------------------------- + // FUNCTION: WUSysMessage + // CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // + // DESCRIPTION: + // This function simplifies the process of obtaining a string equivalent + // of a system error code returned from GetLastError(). You simply + // supply the value returned by GetLastError() to this function and the + // corresponding system string is returned in the form of a CStdStringA. + // + // PARAMETERS: + // dwError - a DWORD value representing the error code to be translated + // dwLangId - the language id to use. defaults to english. + // + // RETURN VALUE: + // a CStdStringA equivalent of the error code. Currently, this function + // only returns either English of the system default language strings. + // ------------------------------------------------------------------------- + #define SS_DEFLANGID MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT) + inline CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + CHAR szBuf[512]; + + if ( 0 != ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatA("%s (0x%X)", szBuf, dwError); + else + return WUFormatA("Unknown error (0x%X)", dwError); + } + inline CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + WCHAR szBuf[512]; + + if ( 0 != ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatW(L"%s (0x%X)", szBuf, dwError); + else + return WUFormatW(L"Unknown error (0x%X)", dwError); + } +#endif + +// Define TCHAR based friendly names for some of these functions + +#ifdef UNICODE + #define CStdString CStdStringW + #define WUSysMessage WUSysMessageW + #define WUFormat WUFormatW +#else + #define CStdString CStdStringA + #define WUSysMessage WUSysMessageA + #define WUFormat WUFormatA +#endif + +// ...and some shorter names for the space-efficient + +#define WUSysMsg WUSysMessage +#define WUSysMsgA WUSysMessageA +#define WUSysMsgW WUSysMessageW +#define WUFmtA WUFormatA +#define WUFmtW WUFormatW +#define WUFmt WUFormat +#define WULastErrMsg() WUSysMessage(::GetLastError()) +#define WULastErrMsgA() WUSysMessageA(::GetLastError()) +#define WULastErrMsgW() WUSysMessageW(::GetLastError()) + + +// ----------------------------------------------------------------------------- +// FUNCTIONAL COMPARATORS: +// REMARKS: +// These structs are derived from the std::binary_function template. They +// give us functional classes (which may be used in Standard C++ Library +// collections and algorithms) that perform case-insensitive comparisons of +// CStdString objects. This is useful for maps in which the key may be the +// proper string but in the wrong case. +// ----------------------------------------------------------------------------- +#define StdStringLessNoCaseW SSLNCW // avoid VC compiler warning 4786 +#define StdStringEqualsNoCaseW SSENCW +#define StdStringLessNoCaseA SSLNCA +#define StdStringEqualsNoCaseA SSENCA + +#ifdef UNICODE + #define StdStringLessNoCase SSLNCW + #define StdStringEqualsNoCase SSENCW +#else + #define StdStringLessNoCase SSLNCA + #define StdStringEqualsNoCase SSENCA +#endif + +struct StdStringLessNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; +struct StdStringLessNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; + +// If we had to define our own version of TRACE above, get rid of it now + +#ifdef TRACE_DEFINED_HERE + #undef TRACE + #undef TRACE_DEFINED_HERE +#endif + + +// These std::swap specializations come courtesy of Mike Crusader. + +//namespace std +//{ +// inline void swap(CStdStringA& s1, CStdStringA& s2) throw() +// { +// s1.swap(s2); +// } +// template<> +// inline void swap(CStdStringW& s1, CStdStringW& s2) throw() +// { +// s1.swap(s2); +// } +//} + +// Turn back on any Borland warnings we turned off. + +#ifdef __BORLANDC__ + #pragma option pop // Turn back on inline function warnings +// #pragma warn +inl // Turn back on inline function warnings +#endif + +#endif // #ifndef STDSTRING_H \ No newline at end of file diff --git a/include/stg_common.h b/include/stg_common.h new file mode 100644 index 00000000..e49f999e --- /dev/null +++ b/include/stg_common.h @@ -0,0 +1,23 @@ +/* + ***************************************************************************** + * + * File: stg_common.h + * + * Description: çÌÏÂÁÌØÎÏÅ ÄÌÑ ×ÓÅÇÏ ÐÒÏÅËÔÁ STG + * + * $Id: stg_common.h,v 1.1.1.1 2005/10/09 11:00:45 nobunaga Exp $ + * + ***************************************************************************** + */ + +#ifndef _STG_COMMON_H_ +#define _STG_COMMON_H_ + + +#define LOGIN_LEN (32) +#define PASSWD_LEN (32) + +#endif /* _STG_COMMON_H_ */ + +/* EOF */ + diff --git a/include/stg_comp_stat.h b/include/stg_comp_stat.h new file mode 100644 index 00000000..3bab9a7a --- /dev/null +++ b/include/stg_comp_stat.h @@ -0,0 +1,41 @@ +#ifndef STG_COMP_STAT_H +#define STG_COMP_STAT_H + +#ifdef LINUX +#include +#endif + +#ifdef FREE_BSD5 +#include +#endif + +#ifdef FREE_BSD +#include +#endif + +#include "stg_const.h" +//----------------------------------------------------------------------------- +struct DAY_STAT +{ + DAY_STAT() + { + lastUpdate = 0; + memset(upload, 0, sizeof(uint64_t) * DIR_NUM); + memset(download, 0, sizeof(uint64_t) * DIR_NUM); + memset(cash, 0, sizeof(double) * DIR_NUM); + } + + time_t lastUpdate; + uint64_t upload[DIR_NUM]; + uint64_t download[DIR_NUM]; + double cash[DIR_NUM]; +}; +//----------------------------------------------------------------------------- +struct MONTH_STAT +{ + DAY_STAT dayStat[31]; + char notUsed; +}; +//----------------------------------------------------------------------------- + +#endif //STG_COMP_STAT_H diff --git a/include/stg_const.h b/include/stg_const.h new file mode 100644 index 00000000..e882200d --- /dev/null +++ b/include/stg_const.h @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: nobunaga $ + $Revision: 1.10 $ + $Date: 2008/01/11 17:33:50 $ + */ + + +#ifndef STG_CONST_H +#define STG_CONST_H + +#define DIR_NUM (10) +#define SYS_IFACE_LEN (9) +#define IFACE_LEN (255) +#define MAX_IP (5) +#define USERDATA_NUM (10) + +#define LOGIN_LEN (32) +#define PASSWD_LEN (32) +#define ADDR_LEN (255) +#define NOTE_LEN (255) +#define REALNM_LEN (255) +#define GROUP_LEN (255) +#define PHONE_LEN (255) +#define EMAIL_LEN (255) +#define USR_IFACE_LEN (255) +#define USER_DATA_LEN (255) +#define IP_STRING_LEN (255) + +#define ADM_LOGIN_LEN (32) +#define ADM_PASSWD_LEN (32) +#define TARIFF_NAME_LEN (32) +#define SERVER_NAME_LEN (255) + +#define DIR_NAME_LEN (16) + +#define MAX_MSG_LEN (235) +#define MAX_MSG_LEN_8 (1030) + +#define LOGCASH (1) +#define NOLOGCASH (0) + +#define USERNOCASH (0) +#define USERDISCONNECT (1) + +#define LOGEVENT_CONNECT (0) +#define LOGEVENT_DISCONNECT (1) +#define LOGEVENT_NEW_MONTH (2) +#define LOGEVENT_NO_CASH (3) +#define LOGEVENT_CONNECT_NO_CASH (4) +#define LOGEVENT_USER_DOWN (5) +#define LOGEVENT_DELETED (6) + +#define SET_TARIFF_NOW (0) +#define SET_TARIFF_DELAYED (1) +#define SET_TARIFF_RECALC (2) + +#define CASH_SET (0) +#define CASH_ADD (1) + +#define NO_TARIFF_NAME "*_NO_TARIFF_*" +#define NO_CORP_NAME "*_NO_CORP_*" + +#define mega (1024 * 1024) + +#define MONITOR_TIME_DELAY_SEC (60) + +#endif diff --git a/include/stg_int.h b/include/stg_int.h new file mode 100644 index 00000000..30de8f5e --- /dev/null +++ b/include/stg_int.h @@ -0,0 +1,11 @@ +#ifdef LINUX +#include +#endif + +#ifdef FREE_BSD5 +#include +#endif + +#ifdef FREE_BSD +#include +#endif diff --git a/include/stg_message.h b/include/stg_message.h new file mode 100644 index 00000000..156e1731 --- /dev/null +++ b/include/stg_message.h @@ -0,0 +1,54 @@ +#ifndef STG_MESSAGES_H +#define STG_MESSAGES_H + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.3 $ + $Date: 2010/03/04 11:49:52 $ + */ + +#include +#include + +using namespace std; +//----------------------------------------------------------------------------- +struct STG_MSG_HDR +{ +STG_MSG_HDR() + : id(0), + ver(0), + type(0), + lastSendTime(0), + creationTime(0), + showTime(0), + repeat(0), + repeatPeriod(0) +{}; + +uint64_t id; +unsigned ver; +unsigned type; +unsigned lastSendTime; +unsigned creationTime; +unsigned showTime; +int repeat; +unsigned repeatPeriod; +}; +//----------------------------------------------------------------------------- +struct STG_MSG +{ +STG_MSG() + : header(), + text() +{}; + +STG_MSG_HDR header; +string text; +}; +//----------------------------------------------------------------------------- + +#endif + diff --git a/include/tariff_conf.h b/include/tariff_conf.h new file mode 100644 index 00000000..30f19815 --- /dev/null +++ b/include/tariff_conf.h @@ -0,0 +1,225 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.9 $ + $Date: 2010/10/05 20:41:11 $ + $Author: faust $ + */ + +#ifndef TARIFF_CONF_H +#define TARIFF_CONF_H + +#include +#include + +#include "resetable.h" +#include "stg_const.h" + +//----------------------------------------------------------------------------- +enum +{ + TRAFF_UP = 0, + TRAFF_DOWN, + TRAFF_UP_DOWN, + TRAFF_MAX +}; +//----------------------------------------------------------------------------- +struct DIRPRICE_DATA +{ + DIRPRICE_DATA() + : hDay(0), + mDay(0), + hNight(0), + mNight(0), + priceDayA(0), + priceNightA(0), + priceDayB(0), + priceNightB(0), + threshold(0), + singlePrice(0), + noDiscount(0) + {} + int hDay; + int mDay; + int hNight; + int mNight; + double priceDayA; + double priceNightA; + double priceDayB; + double priceNightB; + int threshold; + int singlePrice; // Do not use day/night division + int noDiscount; // Do not use threshold +}; +//----------------------------------------------------------------------------- +struct DIRPRICE_DATA_RES +{ + DIRPRICE_DATA_RES & operator= (const DIRPRICE_DATA & dpd) + { + hDay = dpd.hDay; + mDay = dpd.mDay; + hNight = dpd.hNight; + mNight = dpd.mNight; + priceDayA = dpd.priceDayA; + priceNightA = dpd.priceNightA; + priceDayB = dpd.priceDayB; + priceNightB = dpd.priceNightB; + threshold = dpd.threshold; + singlePrice = dpd.singlePrice; + noDiscount = dpd.noDiscount; + return *this; + }; + + DIRPRICE_DATA GetData() + { + DIRPRICE_DATA dd; + dd.hDay = hDay; + dd.hNight = hNight; + dd.mDay = mDay; + dd.mNight = mNight; + dd.noDiscount = noDiscount; + dd.priceDayA = priceDayA; + dd.priceDayB = priceDayB; + + dd.priceNightA = priceNightA; + dd.priceNightB = priceNightB; + dd.singlePrice = singlePrice; + dd.threshold = threshold; + return dd; + } + + RESETABLE hDay; + RESETABLE mDay; + RESETABLE hNight; + RESETABLE mNight; + RESETABLE priceDayA; + RESETABLE priceNightA; + RESETABLE priceDayB; + RESETABLE priceNightB; + RESETABLE threshold; + RESETABLE singlePrice; + RESETABLE noDiscount; +}; +//----------------------------------------------------------------------------- +struct TARIFF_CONF +{ + double fee; // ÷ÅÌÉÞÉÎÁ ÁÂÏÎÐÌÁÔÙ + double free; // îÁ ËÁËÕÀ ÓÕÍÍÕ ÄÅÎÅÇ ÀÚÅÒ ËÁÞÁÅÔ ÂÅÓÐÌÁÔÎÏ + int traffType; // UP, DOWN, UP+DOWN, MAX + double passiveCost; // óÔÏÉÍÏÓÔØ ÚÁÍÏÒÏÚËÉ + std::string name; + + TARIFF_CONF() + : fee(0), + free(0), + traffType(TRAFF_UP_DOWN), // UP-DOWN + passiveCost(0), + name() + {}; + + TARIFF_CONF(const std::string & n) + : fee(0), + free(0), + traffType(TRAFF_UP_DOWN), // UP-DOWN + passiveCost(0), + name(n) + {}; +}; +//----------------------------------------------------------------------------- +struct TARIFF_CONF_RES +{ + TARIFF_CONF_RES & operator=(const TARIFF_CONF & tc) + { + fee = tc.fee; + free = tc.free; + traffType = tc.traffType; + passiveCost = tc.passiveCost; + name = tc.name; + return *this; + }; + + TARIFF_CONF GetData() + { + TARIFF_CONF tc; + tc.fee = fee; + tc.free = free; + tc.name = name; + tc.passiveCost = passiveCost; + tc.traffType = traffType; + return tc; + } + + RESETABLE fee; + RESETABLE free; + RESETABLE traffType; + RESETABLE passiveCost; + RESETABLE name; +}; +//----------------------------------------------------------------------------- +struct TARIFF_DATA +{ + TARIFF_CONF tariffConf; + std::vector dirPrice; + + TARIFF_DATA() + : tariffConf(), + dirPrice(DIR_NUM) + {} + + TARIFF_DATA(const std::string & name) + : tariffConf(name), + dirPrice(DIR_NUM) + {} + + TARIFF_DATA(const TARIFF_DATA & td) + : tariffConf(td.tariffConf), + dirPrice(td.dirPrice) + {} + + TARIFF_DATA & operator=(const TARIFF_DATA & td) + { + tariffConf = td.tariffConf; + dirPrice = td.dirPrice; + return *this; + }; +}; +//----------------------------------------------------------------------------- +struct TARIFF_DATA_RES +{ + TARIFF_CONF_RES tariffConf; + std::vector dirPrice; + + TARIFF_DATA_RES() + : tariffConf(), + dirPrice(DIR_NUM) + {} + + TARIFF_DATA GetData() + { + TARIFF_DATA td; + td.tariffConf = tariffConf.GetData(); + for (int i = 0; i < DIR_NUM; i++) + td.dirPrice[i] = dirPrice[i].GetData(); + return td; + } +}; +//----------------------------------------------------------------------------- +#endif diff --git a/include/user_conf.h b/include/user_conf.h new file mode 100644 index 00000000..9a6efb23 --- /dev/null +++ b/include/user_conf.h @@ -0,0 +1,158 @@ + /* + $Revision: 1.12 $ + $Date: 2010/03/11 14:42:05 $ + $Author: faust $ + */ + +#ifndef USER_CONF_H +#define USER_CONF_H + +#include +#include +#include "stg_const.h" +#include "user_ips.h" +#include "resetable.h" +#include "os_int.h" + +//----------------------------------------------------------------------------- +struct USER_CONF +{ + USER_CONF() + : password(), + passive(0), + disabled(0), + disabledDetailStat(0), + alwaysOnline(0), + tariffName(), + address(), + phone(), + email(), + note(), + realName(), + corp(), + service(), + group(), + credit(0), + nextTariff(), + userdata(USERDATA_NUM), + creditExpire(0), + ips() + {}; + + std::string password; + int passive; + int disabled; + int disabledDetailStat; + int alwaysOnline; + std::string tariffName; + std::string address; + std::string phone; + std::string email; + std::string note; + std::string realName; + std::string corp; + std::vector service; + std::string group; + double credit; + std::string nextTariff; + std::vector userdata; + time_t creditExpire; + USER_IPS ips; +}; +//----------------------------------------------------------------------------- +struct USER_CONF_RES +{ + USER_CONF_RES() + : password(), + passive(), + disabled(), + disabledDetailStat(), + alwaysOnline(), + tariffName(), + address(), + phone(), + email(), + note(), + realName(), + group(), + credit(), + nextTariff(), + userdata(USERDATA_NUM, RESETABLE()), + creditExpire(), + ips() + { + }; + + USER_CONF_RES & operator=(const USER_CONF & uc) + { + userdata.resize(USERDATA_NUM); + password = uc.password; + passive = uc.passive; + disabled = uc.disabled; + disabledDetailStat = uc.disabledDetailStat; + alwaysOnline = uc.alwaysOnline; + tariffName = uc.tariffName; + address = uc.address; + phone = uc.phone; + email = uc.email; + note = uc.note; + realName = uc.realName; + group = uc.group; + credit = uc.credit; + nextTariff = uc.nextTariff; + for (int i = 0; i < USERDATA_NUM; i++) + { + userdata[i] = uc.userdata[i]; + } + creditExpire = uc.creditExpire; + ips = uc.ips; + return *this; + }; + operator USER_CONF() const + { + USER_CONF uc; + uc.password = password; + uc.passive = passive; + uc.disabled = disabled; + uc.disabledDetailStat = disabledDetailStat; + uc.alwaysOnline = alwaysOnline; + uc.tariffName = tariffName; + uc.address = address; + uc.phone = phone; + uc.email = email; + uc.note = note; + uc.realName = realName; + uc.group = group; + uc.credit = credit; + uc.nextTariff = nextTariff; + for (int i = 0; i < USERDATA_NUM; i++) + { + uc.userdata[i] = userdata[i]; + } + uc.creditExpire = creditExpire; + uc.ips = ips; + return uc; + } + //------------------------------------------------------------------------- + + RESETABLE password; + RESETABLE passive; + RESETABLE disabled; + RESETABLE disabledDetailStat; + RESETABLE alwaysOnline; + RESETABLE tariffName; + RESETABLE address; + RESETABLE phone; + RESETABLE email; + RESETABLE note; + RESETABLE realName; + RESETABLE group; + RESETABLE credit; + RESETABLE nextTariff; + std::vector > userdata; + RESETABLE creditExpire; + RESETABLE ips; +}; +//----------------------------------------------------------------------------- +#endif + diff --git a/include/user_ips.h b/include/user_ips.h new file mode 100644 index 00000000..d3e640c1 --- /dev/null +++ b/include/user_ips.h @@ -0,0 +1,280 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.22 $ + $Date: 2010/03/04 11:49:53 $ + $Author: faust $ + */ + +#ifndef USER_IPS_H +#define USER_IPS_H + +#include +//#include +#include +#include +#include +#include +///////////////////////// +#include +#include +#include + +#include "common.h" +#include "os_int.h" + +using namespace std; + +//------------------------------------------------------------------------- +struct IP_MASK +{ +IP_MASK() : ip(0), mask(0) {} +IP_MASK(const IP_MASK & ipm) : ip(ipm.ip), mask(ipm.mask) {} +uint32_t ip; +uint32_t mask; +}; +//------------------------------------------------------------------------- +class USER_IPS +{ + friend std::ostream & operator<< (ostream & o, const USER_IPS & i); + //friend stringstream & operator<< (stringstream & s, const USER_IPS & i); + friend const USER_IPS StrToIPS(const string & ipsStr) throw(string); + +public: + USER_IPS(); + USER_IPS(const USER_IPS &); + USER_IPS & operator=(const USER_IPS &); + const IP_MASK & operator[](int idx) const; + std::string GetIpStr() const; + bool IsIPInIPS(uint32_t ip) const; + bool OnlyOneIP() const; + int Count() const; + void Add(const IP_MASK &im); + void Erase(); + +private: + uint32_t CalcMask(unsigned int msk) const; + std::vector ips; +}; +//------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +inline +USER_IPS::USER_IPS() + : ips() +{} +//----------------------------------------------------------------------------- +inline +USER_IPS::USER_IPS(const USER_IPS & i) + : ips(i.ips) +{} +//----------------------------------------------------------------------------- +inline +USER_IPS & USER_IPS::operator=(const USER_IPS & i) +{ +ips = i.ips; +return *this; +} +//----------------------------------------------------------------------------- +inline +const IP_MASK & USER_IPS::operator[](int idx) const +{ +return ips[idx]; +} +//----------------------------------------------------------------------------- +inline +std::string USER_IPS::GetIpStr() const +{ +if (ips.empty()) + { + return ""; + } + +if (ips[0].ip == 0) + { + return "*"; + } + +std::vector::const_iterator it(ips.begin()); +std::stringstream s; +s << inet_ntostring(it->ip); +++it; +for (; it != ips.end(); ++it) + { + s << "," << inet_ntostring(it->ip); + } +return s.str(); +} +//----------------------------------------------------------------------------- +inline +int USER_IPS::Count() const +{ +return ips.size(); +} +//----------------------------------------------------------------------------- +inline +bool USER_IPS::IsIPInIPS(uint32_t ip) const +{ +if (ips.empty()) + { + return false; + } + +if (ips.front().ip == 0) + return true; + +for (std::vector::const_iterator it(ips.begin()); it != ips.end(); ++it) + { + uint32_t mask(CalcMask(it->mask)); + if ((ip & mask) == (it->ip & mask)) + return true; + } +return false; +} +//----------------------------------------------------------------------------- +inline +bool USER_IPS::OnlyOneIP() const +{ +if (ips.size() == 1 && ips.front().mask == 32) + return true; + +return false; +} +//----------------------------------------------------------------------------- +inline +uint32_t USER_IPS::CalcMask(unsigned int msk) const +{ +if (msk > 32) + return 0; +return htonl(0xFFffFFff << (32 - msk)); +} +//----------------------------------------------------------------------------- +inline +void USER_IPS::Add(const IP_MASK &im) +{ +ips.push_back(im); +} +//----------------------------------------------------------------------------- +inline +void USER_IPS::Erase() +{ +ips.erase(ips.begin(), ips.end()); +} +//----------------------------------------------------------------------------- +inline +std::ostream & operator<<(std::ostream & o, const USER_IPS & i) +{ +return o << i.GetIpStr(); +} +//----------------------------------------------------------------------------- +/*inline +stringstream & operator<<(std::stringstream & s, const USER_IPS & i) +{ +s << i.GetIpStr(); +return s; +}*/ +//----------------------------------------------------------------------------- +inline +const USER_IPS StrToIPS(const std::string & ipsStr) throw(std::string) +{ +USER_IPS ips; +char * paddr; +IP_MASK im; +std::vector ipMask; +std::string err; +if (ipsStr.empty()) + { + err = "Incorrect IP address."; + throw(err); + } + +if (ipsStr[0] == '*' && ipsStr.size() == 1) + { + im.ip = 0; + im.mask = 0; + ips.ips.push_back(im); + return ips; + } + +char * str = new char[ipsStr.size() + 1]; +strcpy(str, ipsStr.c_str()); +char * pstr = str; +while ((paddr = strtok(pstr, ","))) + { + pstr = NULL; + ipMask.push_back(paddr); + } + +delete[] str; + +for (unsigned int i = 0; i < ipMask.size(); i++) + { + char str[128]; + char * strIp; + char * strMask; + strcpy(str, ipMask[i].c_str()); + strIp = strtok(str, "/"); + if (strIp == NULL) + { + err = "Incorrect IP address " + ipsStr; + throw(err); + } + strMask = strtok(NULL, "/"); + + im.ip = inet_addr(strIp); + if (im.ip == INADDR_NONE) + { + err = "Incorrect IP address: " + std::string(strIp); + throw(err); + } + + im.mask = 32; + if (strMask != NULL) + { + int m = 0; + if (str2x(strMask, m) != 0) + { + err = "Incorrect mask: " + std::string(strMask); + throw(err); + } + im.mask = m; + + if (im.mask > 32) + { + err = "Incorrect mask: " + std::string(strMask); + throw(err); + } + + if ((im.ip & ips.CalcMask(im.mask)) != im.ip) + { + err = "Address does'n match mask: " + std::string(strIp) + "/" + std::string(strMask); + throw(err); + } + } + ips.ips.push_back(im); + } + +return ips; +} +//------------------------------------------------------------------------- +#endif //USER_IPS_H + + diff --git a/include/user_stat.h b/include/user_stat.h new file mode 100644 index 00000000..eeb73df6 --- /dev/null +++ b/include/user_stat.h @@ -0,0 +1,173 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.15 $ + $Date: 2010/03/11 14:42:05 $ + $Author: faust $ + */ + +#ifndef USER_STAT_H +#define USER_STAT_H + +#include + +#include "os_int.h" +#include "resetable.h" +#include "user_traff.h" +//----------------------------------------------------------------------------- +struct IP_DIR_PAIR +{ + #ifdef TRAFF_STAT_WITH_PORTS + IP_DIR_PAIR(uint32_t _ip, + int _dir, + uint16_t _port) + : ip(_ip), + dir(_dir), + port(_port) + {} + #else + IP_DIR_PAIR(uint32_t _ip, + int _dir) + : ip(_ip), + dir(_dir) + {} + #endif + //------------------------ + bool operator<(const IP_DIR_PAIR & idp) const + { + if (ip < idp.ip) + return true; + + if (ip > idp.ip) + return false; + + #ifdef TRAFF_STAT_WITH_PORTS + if (port < idp.port) + return true; + + if (port > idp.port) + return false; + #endif + + if (dir < idp.dir) + return true; + + return false; + } + //------------------------ + uint32_t ip; + int dir; + #ifdef TRAFF_STAT_WITH_PORTS + uint16_t port; + #endif +}; +//----------------------------------------------------------------------------- +struct STAT_NODE +{ + STAT_NODE(uint64_t _up, + uint64_t _down, + double _cash) + : up(_up), + down(_down), + cash(_cash) + {} + uint64_t up; + uint64_t down; + double cash; +}; +//----------------------------------------------------------------------------- +struct USER_STAT +{ + //USER_STAT & operator= (const USER_STAT_RES & usr); + USER_STAT() + : up(), + down(), + cash(0), + freeMb(0), + lastCashAdd(0), + lastCashAddTime(0), + passiveTime(0), + lastActivityTime(0) + {}; + + DIR_TRAFF up; + DIR_TRAFF down; + double cash; + double freeMb; + double lastCashAdd; + time_t lastCashAddTime; + time_t passiveTime; + time_t lastActivityTime; +}; +//----------------------------------------------------------------------------- +struct USER_STAT_RES +{ + USER_STAT_RES() + : cash(), + freeMb(), + lastCashAdd(), + lastCashAddTime(), + passiveTime(), + lastActivityTime(), + up(), + down() + {} + + USER_STAT_RES & operator= (const USER_STAT & us) + { + cash = us.cash; + freeMb = us.freeMb; + lastCashAdd = us.lastCashAdd; + lastCashAddTime = us.lastCashAddTime; + passiveTime = us.passiveTime; + lastActivityTime = us.lastActivityTime; + up = us.up; + down = us.down; + return * this; + }; + operator USER_STAT() + { + USER_STAT us; + us.cash = cash; + us.freeMb = freeMb; + us.lastCashAdd = lastCashAdd; + us.lastCashAddTime = lastCashAddTime; + us.passiveTime = passiveTime; + us.lastActivityTime = lastActivityTime; + us.up = up; + us.down = down; + return us; + }; + + RESETABLE cash; + RESETABLE freeMb; + RESETABLE lastCashAdd; + RESETABLE lastCashAddTime; + RESETABLE passiveTime; + RESETABLE lastActivityTime; + RESETABLE up; + RESETABLE down; +}; +//----------------------------------------------------------------------------- +#endif + + + diff --git a/include/user_traff.h b/include/user_traff.h new file mode 100644 index 00000000..fdefee0f --- /dev/null +++ b/include/user_traff.h @@ -0,0 +1,111 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.7 $ + $Date: 2010/10/07 19:48:52 $ + $Author: faust $ + */ + +#ifndef USER_TRAFF_H +#define USER_TRAFF_H + +#include +#include + +#include "stg_const.h" +#include "os_int.h" + +enum TRAFF_DIRECTION {TRAFF_UPLOAD, TRAFF_DOWNLOAD}; + +class DIR_TRAFF +{ + friend std::ostream & operator<< (std::ostream & o, const DIR_TRAFF & traff); + +public: + //------------------------------------------------------------------------- + DIR_TRAFF(); + DIR_TRAFF(const DIR_TRAFF & ts); + DIR_TRAFF & operator=(const DIR_TRAFF & ts); + ~DIR_TRAFF(); + uint64_t operator[](int idx) const; + uint64_t & operator[](int idx); + DIR_TRAFF operator+(const DIR_TRAFF & ts); + +private: + std::vector traff; +}; +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +inline DIR_TRAFF::DIR_TRAFF() + : traff(DIR_NUM, 0) +{ +} +//----------------------------------------------------------------------------- +inline DIR_TRAFF::DIR_TRAFF(const DIR_TRAFF & ts) + : traff(ts.traff) +{ +} +//----------------------------------------------------------------------------- +inline DIR_TRAFF::~DIR_TRAFF() +{ +} +//----------------------------------------------------------------------------- +inline DIR_TRAFF & DIR_TRAFF::operator=(const DIR_TRAFF & ts) +{ +traff = ts.traff; +return *this; +}; +//----------------------------------------------------------------------------- +inline uint64_t & DIR_TRAFF::operator[](int idx) +{ +return traff[idx]; +}; +//----------------------------------------------------------------------------- +inline uint64_t DIR_TRAFF::operator[](int idx) const +{ +return traff[idx]; +}; +//----------------------------------------------------------------------------- +inline DIR_TRAFF DIR_TRAFF::operator+(const DIR_TRAFF & ts) +{ +for (int i = 0; i < DIR_NUM; i++) + { + traff[i] = traff[i] + ts.traff[i]; + } +return *this; +}; +//----------------------------------------------------------------------------- +inline std::ostream & operator<<(std::ostream & o, const DIR_TRAFF & traff) +{ +bool first = true; +for (size_t i = 0; i < DIR_NUM; ++i) + { + if (first) + first = false; + else + o << ","; + o << traff[i]; + } +return o; +} +//----------------------------------------------------------------------------- +#endif diff --git a/include/utime.h b/include/utime.h new file mode 100644 index 00000000..4342edc6 --- /dev/null +++ b/include/utime.h @@ -0,0 +1,179 @@ +#ifndef UTIME_H +#define UTIME_H + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 22.12.2007 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.6 $ + $Date: 2009/08/05 11:40:30 $ + $Author: faust $ + */ + +#include +#include + +#ifdef FREE_BSD +typedef long suseconds_t; +#endif + +struct UTIME: public timeval +{ + UTIME() + { + tv_sec = 0; + tv_usec = 0; + } + + UTIME(time_t t) + { + tv_sec = t; + tv_usec = 0; + } + + UTIME(long long a, long long b) + { + tv_sec = a; + tv_usec = b; + } + + bool operator<(const UTIME & rhs) const + { + if (tv_sec < rhs.tv_sec) + return true; + else if (tv_sec > rhs.tv_sec) + return false; + else if (tv_usec < rhs.tv_usec) + return true; + return false; + } + + bool operator<=(const UTIME & rhs) const + { + if (tv_sec < rhs.tv_sec) + return true; + else if (tv_sec > rhs.tv_sec) + return false; + else if (tv_usec < rhs.tv_usec) + return true; + else if (tv_usec > rhs.tv_usec) + return false; + return true; + } + + bool operator>(const UTIME & rhs) const + { + if (tv_sec > rhs.tv_sec) + return true; + else if (tv_sec < rhs.tv_sec) + return false; + else if (tv_usec > rhs.tv_usec) + return true; + return false; + } + + bool operator>=(const UTIME & rhs) const + { + if (tv_sec > rhs.tv_sec) + return true; + else if (tv_sec < rhs.tv_sec) + return false; + else if (tv_usec > rhs.tv_usec) + return true; + else if (tv_usec < rhs.tv_usec) + return false; + return true; + } + + bool operator==(const UTIME & rhs) const + { + //cout << tv_sec << "." << tv_usec << " " << rhs.tv_sec << "." << rhs.tv_usec << endl; + //cout << (tv_sec == rhs.tv_sec) << " " << (tv_usec == rhs.tv_usec) << endl; + return (tv_sec == rhs.tv_sec) && (tv_usec == rhs.tv_usec); + } + + UTIME operator+(const UTIME & rhs) + { + // TODO optimize + long long a, b; + /*a = tv_sec * 1000000 + tv_usec; + b = rhs.tv_sec * 1000000 + rhs.tv_usec; + return UTIME((a + b) / 1000000, (a + b) % 1000000);*/ + a = tv_sec + rhs.tv_sec; + b = tv_usec + rhs.tv_usec; + if (b > 1000000) + { + ++a; + b -= 1000000; + } + return UTIME(a, b); + } + + UTIME operator-(const UTIME & rhs) + { + // TODO optimize + long long a, b; + /*a = tv_sec * 1000000 + tv_usec; + b = rhs.tv_sec * 1000000 + rhs.tv_usec; + return UTIME((a - b) / 1000000, (a - b) % 1000000);*/ + a = tv_sec - rhs.tv_sec; + b = tv_usec - rhs.tv_usec; + if (a >= 0) + { + if (b >= 0) + { + return UTIME(a, b); + } + else + { + return UTIME(--a, b + 1000000); + } + } + else + { + if (b >= 0) + { + return UTIME(++a, 1000000 - b); + } + else + { + return UTIME(a, b); + } + } + } + + time_t GetSec() const + { + return tv_sec; + } + + suseconds_t GetUSec() const + { + return tv_usec; + } +}; + + +#endif //UTIME_H + diff --git a/include/version.h b/include/version.h new file mode 100644 index 00000000..08085470 --- /dev/null +++ b/include/version.h @@ -0,0 +1,26 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontiv + */ + + /* + $Revision: 1.5 $ + $Date: 2009/08/05 09:24:01 $ + */ + +#define SERVER_VERSION "2.406" diff --git a/include/vpn_stg_packets.h b/include/vpn_stg_packets.h new file mode 100644 index 00000000..e1f0b0b8 --- /dev/null +++ b/include/vpn_stg_packets.h @@ -0,0 +1,58 @@ +#ifndef vpn_stg_packets_h +#define vpn_stg_packets_h + +#define VS_MAGIC "VS01" + +enum +{ +GET_LOGIN, +GET_LOGIN_ANS, +VS_ALIVE, +VS_ALIVE_ANS, +VS_DISCONNECT +} + + +struct VS_GET_LOGIN +{ +char magic[4]; +STG_PACKET_TYPES type; +char login[32]; +//char password[32]; +}; + +struct VS_CHECK_LOGIN_ANS +{ +char magic[4]; +STG_PACKET_TYPES type; +char login[32]; +char password[32]; +uint32_t ip; +}; + +struct VS_ALIVE +{ +char magic[4]; +STG_PACKET_TYPES type; +char login[32]; +uint32_t ip; +}; + +struct VS_ALIVE_ANS +{ +char magic[4]; +STG_PACKET_TYPES type; +char login[32]; +uint32_t ip; +}; + +struct VS_DISCONNECT +{ +char magic[4]; +STG_PACKET_TYPES type; +char login[32]; +uint32_t ip; +}; + + +#endif diff --git a/projects/convertor/Makefile b/projects/convertor/Makefile new file mode 100644 index 00000000..c07f698e --- /dev/null +++ b/projects/convertor/Makefile @@ -0,0 +1,74 @@ +############################################################################### +# $Id: Makefile,v 1.12 2009/03/03 15:49:34 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +PROG = convertor + +SRCS = ./main.cpp \ + ./settings.cpp + +STGLIBS = -lstg_logger \ + -lstg_common \ + -ldotconfpp \ + -lstg_crypto + +LIBS += $(LIB_THREAD) + +ifeq ($(OS),linux) +LIBS += -ldl +else +LIBS += -lc +endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +CXXFLAGS += -Wall +LDFLAGS += -Wl,-E -L$(DIR_LIB) -Wl,-rpath,$(DIR_LIB) + +vpath %.so $(DIR_LIB) + +.PHONY: all clean distclean libs plugins install uninstall +all: libs plugins $(PROG) ../../Makefile.conf + +libs: + $(MAKE) -C $(DIR_LIBSRC) + +plugins: libs + $(MAKE) -C $(DIR_PLUGINS) + +$(PROG): $(OBJS) $(STGLIBS) + $(CC) $^ $(LDFLAGS) $(LIBS) -o $(PROG) + +clean: + rm -f deps $(PROG) *.o tags *.*~ .OS + rm -f .OS + rm -f .store + rm -f .db.sql + rm -f core* + $(MAKE) -C $(DIR_LIBSRC) clean + $(MAKE) -C $(DIR_PLUGINS) clean + +distclean: clean + rm -f ../../Makefile.conf + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf + $(MAKE) -C $(DIR_LIBSRC) includes + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) -MM $$file` Makefile" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/convertor/build b/projects/convertor/build new file mode 100755 index 00000000..491cf81c --- /dev/null +++ b/projects/convertor/build @@ -0,0 +1,259 @@ +#!/bin/sh + +# $Revision: 1.20 $ +# $Author: faust $ +# $Date: 2010/04/14 08:58:43 $ +###################################################### + +OS=unknown +sys=`uname -s` +release=`uname -r | cut -b1` +BUILD_DIR=`pwd` +CONFFILE="../../Makefile.conf" +PREFIX="/" +BIN_MODE=0755 +DATA_MODE=0644 +OWNER=root +VAR_DIR="./inst/var/stargazer" +DEFS="-DDEBUG" +MAKEOPTS="-j1" +CXXFLAGS="$CXXFLAGS -g3 -W -Wall -I/usr/local/include" +LDFLAGS="$LDFLAGS -L/usr/local/lib" + +if [ "$sys" = "Linux" ] +then + OS=linux + release="" + MAKE="make" +fi + +if [ "$sys" = "FreeBSD" ] +then + case $release in + 4) OS=bsd;; + 5) OS=bsd5;; + 6) OS=bsd5;; + 7) OS=bsd7;; + 8) OS=bsd7;; + *) OS=unknown;; + esac + MAKE="gmake" +fi + +if [ "$OS" = "unknown" ] +then + echo "#############################################################################" + echo "# Sorry, but convertor currently supported by Linux, FreeBSD 4.x, 5.x, 6.x #" + echo "#############################################################################" + exit 1 +fi + +echo "#############################################################################" +echo " Building convertor for $sys $release" +echo "#############################################################################" + +STG_LIBS="stg_logger.lib + stg_locker.lib + crypto.lib + common.lib + conffiles.lib + dotconfpp.lib" + +PLUGINS="store/files" + +if [ "$OS" = "linux" ] +then + DEFS="$DEFS -DLINUX" + SHELL="/bin/bash" + LIB_THREAD=-lpthread +else + if [ "$OS" = "bsd" ] + then + DEFS="$DEFS -DFREE_BSD" + LIB_THREAD=-lc_r + else + DEFS="$DEFS -DFREE_BSD5" + if [ "$OS" = "bsd7" ] + then + LIB_THREAD=-lpthread + else + LIB_THREAD=-lc_r + fi + fi + SHELL="/usr/local/bin/bash" +fi + +echo -n "Checking endianess... " +echo "int main() { int probe = 0x00000001; return *(char *)&probe; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS -L/usr/lib/mysql -L/usr/local/lib/mysql build_check.c -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + echo "FAIL!" + echo "Endianess checking failed" + exit; +else + ./fake + if [ $? = 1 ] + then + ARCH=le + CXXFLAGS="$CXXFLAGS -DARCH_LE" + echo "Little Endian" + else + ARCH=be + CXXFLAGS="$CXXFLAGS -DARCH_BE" + echo "Big Endian" + fi +fi +rm -f fake + +echo -n "Checking for -lfbclient... " +gcc $CXXFLAGS $LDFLAGS build_check.c -lfbclient $LIB_THREAD -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + CHECK_FBCLIENT=no + echo "no" +else + CHECK_FBCLIENT=yes + echo "yes" +fi +rm -f fake + +echo -n "Checking for mysql_config... " +MYSQL_VERSION=`mysql_config --version 2> /dev/null` +if [ $? != 0 ] +then + echo "no"; + echo -n "Checking for -lmysqlclient... " + gcc $CXXFLAGS $LDFLAGS build_check.c -lmysqlclient_r $LIB_THREAD -o fake > /dev/null 2> /dev/null + if [ $? != 0 ] + then + CHECK_MYSQLCLIENT=no + echo "no" + else + CHECK_MYSQLCLIENT=yes + echo "yes" + fi + rm -f fake +else + echo "yes" + echo -n "Checking for mysql_config --cflags... " + MYSQL_CFLAGS=`mysql_config --cflags 2> /dev/null` + if [ $? != 0 ] + then + CHECK_MYSQLCLIENT=no + echo "no" + else + #CXXFLAGS="$CXXFLAGS $MYSQL_CFLAGS" + echo "[$MYSQL_CFLAGS]" + echo -n "Checking for mysql_config --libs_r... " + MYSQL_LDFLAGS=`mysql_config --libs_r 2> /dev/null` + if [ $? != 0 ] + then + CHECK_MYSQLCLIENT=no + echo "no" + else + CHECK_MYSQLCLIENT=yes + #LDFLAGS="$LDFLAGS $MYSQL_LDFLAGS" + echo "[$MYSQL_LDFLAGS]" + fi + fi +fi + +echo -n "Checking for pg_config... " +PG_VERSION=`pg_config --version 2> /dev/null` +if [ $? != 0 ] +then + echo "no"; + echo -n "Checking for -lpq... " + gcc $CXXFLAGS $LDFLAGS build_check.c -lpq $LIB_THREAD -o fake > /dev/null 2> /dev/null + if [ $? != 0 ] + then + CHECK_PQ=no + echo "no" + else + CHECK_PQ=yes + echo "yes" + fi + rm -f fake +else + echo "yes"; + echo -n "Checking for pg_config --includedir... " + PG_CFLAGS=`pg_config --includedir 2> /dev/null` + if [ $? != 0 ] + then + CHECK_PQ=no + echo "no" + else + echo "[$PG_CFLAGS]" + echo -n "Checking for pg_config --libdir... " + PG_LDFLAGS=`pg_config --libdir 2> /dev/null` + if [ $? != 0 ] + then + CHECK_PQ=no + echo "no" + else + CHECK_PQ=yes + echo "[$PG_LDFLAGS]" + fi + fi +fi + +rm -f build_check.c + +if [ "$CHECK_FBCLIENT" = "yes" ] +then + STG_LIBS="$STG_LIBS + ibpp.lib" + PLUGINS="$PLUGINS + store/firebird" +fi + +if [ "$CHECK_PQ" = "yes" ] +then + PLUGINS="$PLUGINS + store/postgresql" +fi + +if [ "$CHECK_MYSQLCLIENT" = "yes" ] +then + PLUGINS="$PLUGINS + store/mysql" +fi + +echo "OS=$OS" > $CONFFILE +echo "STG_TIME=yes" >> $CONFFILE +echo "DIR_BUILD=$BUILD_DIR" >> $CONFFILE +echo "DIR_LIB=\$(DIR_BUILD)/../../lib" >> $CONFFILE +echo "DIR_LIBSRC=\$(DIR_BUILD)/../../stglibs" >> $CONFFILE +echo "DIR_INCLUDE=\$(DIR_BUILD)/../../include" >> $CONFFILE +echo "DIR_MOD=\$(DIR_BUILD)/../stargazer/modules" >> $CONFFILE +echo "DIR_PLUGINS=\$(DIR_BUILD)/../stargazer/plugins" >> $CONFFILE +echo "ARCH=$ARCH" >> $CONFFILE +echo "CHECK_FBCLIENT=$CHECK_FBCLIENT" >> $CONFFILE +echo "DEFS=$DEFS" >> $CONFFILE +echo -n "STG_LIBS=" >> $CONFFILE +for lib in $STG_LIBS +do + echo -n "$lib " >> $CONFFILE +done +echo "" >> $CONFFILE +echo -n "PLUGINS=" >> $CONFFILE +for plugin in $PLUGINS +do + echo -n "$plugin " >> $CONFFILE +done +echo "" >> $CONFFILE +echo "SHELL=$SHELL" >> $CONFFILE +echo "CXXFLAGS=$CXXFLAGS" >> $CONFFILE +echo "LDFLAGS=$LDFLAGS" >> $CONFFILE +echo "LIB_THREAD=$LIB_THREAD" >> $CONFFILE +echo "PREFIX=$PREFIX" >> $CONFFILE +echo "BIN_MODE=$BIN_MODE" >> $CONFFILE +echo "DATA_MODE=$DATA_MODE" >> $CONFFILE +echo "OWNER=$OWNER" >> $CONFFILE +echo "VAR_DIR=$VAR_DIR" >> $CONFFILE + +mkdir -p ../stargazer/modules + +$MAKE $MAKEOPTS + diff --git a/projects/convertor/convertor.conf b/projects/convertor/convertor.conf new file mode 100644 index 00000000..d2405adf --- /dev/null +++ b/projects/convertor/convertor.conf @@ -0,0 +1,66 @@ +################################################################################ +# æÁÊÌ ÎÁÓÔÒÏÅË ËÏÎ×ÅÒÔÏÒÁ stargazer # +################################################################################ + +# ðÕÔØ Ë ÄÉÒÅËÔÏÒÉÉ, × ËÏÔÏÒÏÊ ÎÁÈÏÄÑÔÓÑ ÍÏÄÕÌÉ ÓÅÒ×ÅÒÁ +ModulesPath = ../stargazer/modules + +################################################################################ +# Store module +# îÁÓÔÒÏÊËÉ ÐÌÁÇÉÎÁ ÒÁÂÏÔÁÀÝÅÇÏ Ó âä ÓÅÒ×ÅÒÁ + +# ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ +# ô.Å. ÐÏÌÎÏÅ ÉÍÑ ÍÏÄÕÌÑ mod_store_files.so + + + # òÁÂÏÞÁÑ ÄÉÒÅËÔÏÒÉÑ ÓÅÒ×ÅÒÁ, ÔÕÔ ÓÏÄÅÒÖÁÔÓÑ ÄÁÎÎÙÅ Ï ÔÁÒÉÆÁÈ, ÐÏÌØÚÏ×ÁÔÅÌÑÈ, + # ÁÄÍÉÎÉÓÔÒÁÔÏÒÁÈ É Ô.Ä. + WorkDir = /var/stargazer + + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÆÁÊÌÙ ÓÔÁÔÉÓÔÉËÉ (stat) ÐÏÌØÚÏ×ÁÔÅÌÑ + ConfOwner = root + ConfGroup = wheel + ConfMode = 600 + + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÆÁÊÌÙ ËÏÎÆÉÇÕÒÁÃÉÉ (conf) ÐÏÌØÚÏ×ÁÔÅÌÑ + StatOwner = root + StatGroup = wheel + StatMode = 640 + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÌÏÇ-ÆÁÊÌÙ (log) ÐÏÌØÚÏ×ÁÔÅÌÑ + UserLogOwner = root + UserLogGroup = wheel + UserLogMode = 640 + + + +# +# server = localhost +# database = /var/stargazer/stargazer.fdb +# user = stg +# password = 123456 +# + + + server = localhost + database = stargazer + user = stg + password = 123456 + + +# +# # éÍÑ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# dbuser = stg +# +# # ðÁÒÏÌØ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# rootdbpass = 123456 +# +# # éÍÑ âä ÎÁ ÓÅÒ×ÅÒÅ +# dbname = stg +# +# # áÄÒÅÓ ÓÅÒ×ÅÒÁ âä +# dbhost = localhost +# + diff --git a/projects/convertor/main.cpp b/projects/convertor/main.cpp new file mode 100644 index 00000000..58ff7fbd --- /dev/null +++ b/projects/convertor/main.cpp @@ -0,0 +1,444 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.11 $ + $Date: 2010/03/25 12:32:30 $ + $Author: faust $ + */ + +#include + +#include +#include +#include +#include +#include + +#include "common.h" +#include "base_store.h" +#include "settings.h" +#include "conffiles.h" + +#include "user_stat.h" +#include "user_conf.h" +#include "corp_conf.h" +#include "service_conf.h" +#include "admin_conf.h" +#include "tariff_conf.h" +#include "base_settings.h" +#include "stg_message.h" + +using namespace std; + +volatile time_t stgTime = time(NULL); + +int main(int argc, char **argv) +{ +printfd(__FILE__, "Start\n"); + +BASE_STORE * fromStore = NULL; +BASE_STORE * toStore = NULL; + +SETTINGS * settings = NULL; + +string modulePath; + +MODULE_SETTINGS fromStoreSettings; +MODULE_SETTINGS toStoreSettings; + +ADMIN_CONF ac; +USER_CONF uc; +USER_STAT us; +STG_MSG msg; +TARIFF_DATA td; +CORP_CONF cc; +SERVICE_CONF sc; +vector hdrs; +vector::iterator mit; + +void * src_lh; +void * dst_lh; + +if (argc == 2) + settings = new SETTINGS(argv[1]); +else + settings = new SETTINGS(); + +if (settings->ReadSettings()) +{ + printfd(__FILE__, "Error reading settings\n"); + delete settings; + return -1; +} + +fromStoreSettings = settings->GetSourceStoreModuleSettings(); +toStoreSettings = settings->GetDestStoreModuleSettings(); +modulePath = settings->GetModulesPath(); + +string sourcePlugin(modulePath + "/mod_" + fromStoreSettings.moduleName + ".so"); +string destPlugin(modulePath + "/mod_" + toStoreSettings.moduleName + ".so"); + +src_lh = dlopen(sourcePlugin.c_str(), RTLD_NOW); +if (!src_lh) + { + printfd(__FILE__, "Source storage plugin loading failed: %s\n", dlerror()); + delete settings; + return -1; + } + +dst_lh = dlopen(destPlugin.c_str(), RTLD_NOW); +if (!dst_lh) + { + printfd(__FILE__, "Destination storage plugin loading failed: %s\n", dlerror()); + delete settings; + return -1; + } + +BASE_STORE * (*GetSourceStore)(); +BASE_STORE * (*GetDestStore)(); +GetSourceStore = (BASE_STORE * (*)())dlsym(src_lh, "GetStore"); +if (!GetSourceStore) + { + printfd(__FILE__, "Source storage plugin loading failed. GetStore not found: %s\n", dlerror()); + delete settings; + return -1; + } +GetDestStore = (BASE_STORE * (*)())dlsym(dst_lh, "GetStore"); +if (!GetDestStore) + { + printfd(__FILE__, "Storage plugin (firebird) loading failed. GetStore not found: %s\n", dlerror()); + delete settings; + return -1; + } + +fromStore = GetSourceStore(); +toStore = GetDestStore(); + +vector entities; +vector ready; +vector::const_iterator it; +fromStore->SetSettings(fromStoreSettings); +fromStore->ParseSettings(); +toStore->SetSettings(toStoreSettings); +toStore->ParseSettings(); + +printfd(__FILE__, "Importing admins:\n"); +entities.erase(entities.begin(), entities.end()); +ready.erase(ready.begin(), ready.end()); +if (fromStore->GetAdminsList(&entities)) + { + printfd(__FILE__, "Error getting admins list: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +if (toStore->GetAdminsList(&ready)) + { + printfd(__FILE__, "Error getting admins list: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +for (it = entities.begin(); it != entities.end(); ++it) + { + printfd(__FILE__, "\t - %s\n", it->c_str()); + if (find(ready.begin(), ready.end(), *it) == ready.end()) + if (toStore->AddAdmin(*it)) + { + printfd(__FILE__, "Error adding admin: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (fromStore->RestoreAdmin(&ac, *it)) + { + printfd(__FILE__, "Error getting admin's confi: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + ac.login = *it; + if (toStore->SaveAdmin(ac)) + { + printfd(__FILE__, "Error saving admin's conf: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + } + +printfd(__FILE__, "Importing tariffs:\n"); +entities.erase(entities.begin(), entities.end()); +ready.erase(ready.begin(), ready.end()); +if (fromStore->GetTariffsList(&entities)) + { + printfd(__FILE__, "Error getting tariffs list: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +if (toStore->GetTariffsList(&ready)) + { + printfd(__FILE__, "Error getting tariffs list: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +for (it = entities.begin(); it != entities.end(); ++it) + { + printfd(__FILE__, "\t - %s\n", it->c_str()); + if (find(ready.begin(), ready.end(), *it) == ready.end()) + if (toStore->AddTariff(*it)) + { + printfd(__FILE__, "Error adding tariff: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (fromStore->RestoreTariff(&td, *it)) + { + printfd(__FILE__, "Error getting tariff's data: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (toStore->SaveTariff(td, *it)) + { + printfd(__FILE__, "Error saving tariff's data: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + } + +printfd(__FILE__, "Importing services:\n"); +entities.erase(entities.begin(), entities.end()); +ready.erase(ready.begin(), ready.end()); +if (fromStore->GetServicesList(&entities)) + { + printfd(__FILE__, "Error getting service list: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +if (toStore->GetServicesList(&ready)) + { + printfd(__FILE__, "Error getting service list: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +for (it = entities.begin(); it != entities.end(); ++it) + { + printfd(__FILE__, "\t - %s\n", it->c_str()); + if (find(ready.begin(), ready.end(), *it) == ready.end()) + if (toStore->AddService(*it)) + { + printfd(__FILE__, "Error adding service: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (fromStore->RestoreService(&sc, *it)) + { + printfd(__FILE__, "Error getting service's data: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (toStore->SaveService(sc)) + { + printfd(__FILE__, "Error saving service's data: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + } + +printfd(__FILE__, "Importing corporations:\n"); +entities.erase(entities.begin(), entities.end()); +ready.erase(ready.begin(), ready.end()); +if (fromStore->GetCorpsList(&entities)) + { + printfd(__FILE__, "Error getting corporations list: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +if (toStore->GetCorpsList(&ready)) + { + printfd(__FILE__, "Error getting corporations list: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +for (it = entities.begin(); it != entities.end(); ++it) + { + printfd(__FILE__, "\t - %s\n", it->c_str()); + if (find(ready.begin(), ready.end(), *it) == ready.end()) + if (toStore->AddCorp(*it)) + { + printfd(__FILE__, "Error adding corporation: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (fromStore->RestoreCorp(&cc, *it)) + { + printfd(__FILE__, "Error getting corporation's data: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (toStore->SaveCorp(cc)) + { + printfd(__FILE__, "Error saving corporation's data: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + } + +printfd(__FILE__, "Importing users:\n"); +entities.erase(entities.begin(), entities.end()); +ready.erase(ready.begin(), ready.end()); +if (fromStore->GetUsersList(&entities)) + { + printfd(__FILE__, "Error getting users list: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +if (toStore->GetUsersList(&ready)) + { + printfd(__FILE__, "Error getting users list: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } +sort(ready.begin(), ready.end()); +for (it = entities.begin(); it != entities.end(); ++it) + { + printfd(__FILE__, "\t - %s\n", it->c_str()); + if (!binary_search(ready.begin(), ready.end(), *it)) { + if (toStore->AddUser(*it)) + { + printfd(__FILE__, "Error adding user: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + } else { + printfd(__FILE__, "\t\t(adding passed)\n"); + } + if (fromStore->RestoreUserConf(&uc, *it)) + { + printfd(__FILE__, "Error getting user's conf: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (fromStore->RestoreUserStat(&us, *it)) + { + printfd(__FILE__, "Error getting user's stat: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (toStore->SaveUserConf(uc, *it)) + { + printfd(__FILE__, "Error saving user's conf: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + if (toStore->SaveUserStat(us, *it)) + { + printfd(__FILE__, "Error saving user's stat: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + hdrs.erase(hdrs.begin(), hdrs.end()); + if (fromStore->GetMessageHdrs(&hdrs, *it)) + { + printfd(__FILE__, "Error getting user's messages: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + for (mit = hdrs.begin(); mit != hdrs.end(); ++mit) + { + if (fromStore->GetMessage(mit->id, &msg, *it)) + { + printfd(__FILE__, "Error getting message for a user: %s\n", fromStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + printfd(__FILE__, "\t\t * %s\n", msg.text.c_str()); + if (toStore->AddMessage(&msg, *it)) + { + printfd(__FILE__, "Error adding message to a user: %s\n", toStore->GetStrError().c_str()); + dlclose(src_lh); + dlclose(dst_lh); + delete settings; + return -1; + } + } + + } + +dlclose(src_lh); +dlclose(dst_lh); +printfd(__FILE__, "Done\n"); +delete settings; +return 0; +} diff --git a/projects/convertor/settings.cpp b/projects/convertor/settings.cpp new file mode 100644 index 00000000..33f4457e --- /dev/null +++ b/projects/convertor/settings.cpp @@ -0,0 +1,267 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* +$Revision: 1.6 $ +$Date: 2009/06/22 16:26:54 $ +*/ + +#include +#include +#include +#include +#include + +using namespace std; + +#include "settings.h" +#include "common.h" + +//----------------------------------------------------------------------------- +SETTINGS::SETTINGS(const char * cf) +{ +confFile = string(cf); +} +//----------------------------------------------------------------------------- +SETTINGS::~SETTINGS() +{ + +} +//----------------------------------------------------------------------------- +/* +int SETTINGS::ParseYesNo(const string & value, bool * val) +{ +if (0 == strcasecmp(value.c_str(), "yes")) + { + *val = true; + return 0; + } +if (0 == strcasecmp(value.c_str(), "no")) + { + *val = false; + return 0; + } + +strError = "Incorrect value \'" + value + "\'."; +return -1; +} +//----------------------------------------------------------------------------- +int SETTINGS::ParseInt(const string & value, int * val) +{ +char *res; +*val = strtol(value.c_str(), &res, 10); +if (*res != 0) + { + strError = "Cannot convert \'" + value + "\' to integer."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int SETTINGS::ParseIntInRange(const string & value, int min, int max, int * val) +{ +if (ParseInt(value, val) != 0) + return -1; + +if (*val < min || *val > max) + { + strError = "Value \'" + value + "\' out of range."; + return -1; + } + +return 0; +} +*/ +//----------------------------------------------------------------------------- +int SETTINGS::ParseModuleSettings(const DOTCONFDocumentNode * node, vector * params) +{ +/*if (!node) + return 0;*/ +const DOTCONFDocumentNode * childNode; +PARAM_VALUE pv; +const char * value; + +pv.param = node->getName(); + +if (node->getValue(1)) + { + strError = "Unexpected value \'" + string(node->getValue(1)) + "\'."; + return -1; + } + +value = node->getValue(0); + +if (!value) + { + strError = "Module name expected."; + return -1; + } + +childNode = node->getChildNode(); +while (childNode) + { + pv.param = childNode->getName(); + int i = 0; + while ((value = childNode->getValue(i)) != NULL) + { + //printfd(__FILE__, "--> param=\'%s\' value=\'%s\'\n", childNode->getName(), value); + pv.value.push_back(value); + i++; + } + params->push_back(pv); + pv.value.clear(); + childNode = childNode->getNextNode(); + } + +/*for (unsigned i = 0; i < params->size(); i++) + { + printfd(__FILE__, "param \'%s\'\n", (*params)[i].param.c_str()); + for (unsigned j = 0; j < (*params)[i].value.size(); j++) + { + printfd(__FILE__, "value \'%s\'\n", (*params)[i].value[j].c_str()); + } + }*/ + +return 0; +} +//----------------------------------------------------------------------------- +string SETTINGS::GetStrError() const +{ +return strError; +} +//----------------------------------------------------------------------------- +int SETTINGS::ReadSettings() +{ +const char * requiredOptions[] = { + "ModulesPath", + "SourceStoreModule", + "DestStoreModule", + NULL + }; +int sourceStoreModulesCount = 0; +int destStoreModulesCount = 0; + +DOTCONFDocument conf(DOTCONFDocument::CASEINSENSITIVE); +conf.setRequiredOptionNames(requiredOptions); + +//printfd(__FILE__, "Conffile: %s\n", confFile.c_str()); + +if(conf.setContent(confFile.c_str()) != 0) + { + strError = "Cannot read file " + confFile + "."; + return -1; + } + +const DOTCONFDocumentNode * node = conf.getFirstNode(); + +while (node) + { + if (strcasecmp(node->getName(), "ModulesPath") == 0) + { + modulesPath = node->getValue(0); + //printfd(__FILE__, "ModulesPath: %s\n", logFile.c_str()); + } + + if (strcasecmp(node->getName(), "SourceStoreModule") == 0) + { + // íÙ ×ÎÕÔÒÉ ÓÅËÃÉÉ StoreModule + //printfd(__FILE__, "StoreModule\n"); + + if (node->getValue(1)) + { + // StoreModule ÄÏÌÖÅÎ ÉÍÅÔØ 1 ÁÔÒÉÂÕÔ + strError = "Unexpected \'" + string(node->getValue(1)) + "\'."; + return -1; + } + + if (sourceStoreModulesCount) + { + // äÏÌÖÅÎ ÂÙÔØ ÔÏÌØËÏ ÏÄÉÎ ÍÏÄÕÌØ StoreModule! + strError = "Should be only one source StoreModule."; + return -1; + } + sourceStoreModulesCount++; + + //storeModuleSettings.clear(); //TODO To make constructor + //printfd(__FILE__, "StoreModule %s\n", node->getValue()); + sourceStoreModuleSettings.moduleName = node->getValue(0); + ParseModuleSettings(node, &sourceStoreModuleSettings.moduleParams); + } + + if (strcasecmp(node->getName(), "DestStoreModule") == 0) + { + // íÙ ×ÎÕÔÒÉ ÓÅËÃÉÉ StoreModule + //printfd(__FILE__, "StoreModule\n"); + + if (node->getValue(1)) + { + // StoreModule ÄÏÌÖÅÎ ÉÍÅÔØ 1 ÁÔÒÉÂÕÔ + strError = "Unexpected \'" + string(node->getValue(1)) + "\'."; + return -1; + } + + if (destStoreModulesCount) + { + // äÏÌÖÅÎ ÂÙÔØ ÔÏÌØËÏ ÏÄÉÎ ÍÏÄÕÌØ StoreModule! + strError = "Should be only one dest StoreModule."; + return -1; + } + destStoreModulesCount++; + + //storeModuleSettings.clear(); //TODO To make constructor + //printfd(__FILE__, "StoreModule %s\n", node->getValue()); + destStoreModuleSettings.moduleName = node->getValue(0); + ParseModuleSettings(node, &destStoreModuleSettings.moduleParams); + } + + node = node->getNextNode(); + } + +//sort(modulesSettings.begin(), modulesSettings.end()); +//modulesSettings.erase(unique(modulesSettings.begin(), modulesSettings.end()), modulesSettings.end()); + +return 0; +} +//----------------------------------------------------------------------------- +int SETTINGS::Reload () +{ +return ReadSettings(); +} +//----------------------------------------------------------------------------- +const MODULE_SETTINGS & SETTINGS::GetSourceStoreModuleSettings() const +{ +return sourceStoreModuleSettings; +} +//----------------------------------------------------------------------------- +const MODULE_SETTINGS & SETTINGS::GetDestStoreModuleSettings() const +{ +return destStoreModuleSettings; +} +//----------------------------------------------------------------------------- +const string & SETTINGS::GetModulesPath() const +{ +return modulesPath; +} +//----------------------------------------------------------------------------- + diff --git a/projects/convertor/settings.h b/projects/convertor/settings.h new file mode 100644 index 00000000..129e7f90 --- /dev/null +++ b/projects/convertor/settings.h @@ -0,0 +1,78 @@ + /* + $Revision: 1.6 $ + $Date: 2009/06/22 16:26:54 $ + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + +#ifndef settingsh_h +#define settingsh_h 1 + +#include +#include +#include + +#include "common.h" +#include "base_settings.h" +#include "stg_logger.h" + +using namespace std; + +//----------------------------------------------------------------------------- +class SETTINGS +{ +public: + SETTINGS(const char * cf = "./convertor.conf"); + ~SETTINGS(); + int Reload(); + int ReadSettings(); + + string GetStrError() const; + + const string & GetConfDir() const; + + const string & GetModulesPath() const; + const MODULE_SETTINGS & GetSourceStoreModuleSettings() const; + const MODULE_SETTINGS & GetDestStoreModuleSettings() const; + +private: + + //int ParseInt(const string & value, int * val); + //int ParseIntInRange(const string & value, int min, int max, int * val); + //int ParseYesNo(const string & value, bool * val); + + int ParseModuleSettings(const DOTCONFDocumentNode * dirNameNode, vector * params); + + string strError; + //////////settings + string modulesPath; + string confFile; + + MODULE_SETTINGS sourceStoreModuleSettings; + MODULE_SETTINGS destStoreModuleSettings; +}; +//----------------------------------------------------------------------------- +#endif + diff --git a/projects/make_tarball/get_from_cvs b/projects/make_tarball/get_from_cvs new file mode 100755 index 00000000..362f4029 --- /dev/null +++ b/projects/make_tarball/get_from_cvs @@ -0,0 +1,58 @@ +#!/usr/bin/expect + +# login_cvs host password + +set user [lindex $argv 0] +set pass [lindex $argv 1] +set host [lindex $argv 2] +set cvsroot [lindex $argv 3] +set module [lindex $argv 4] +set dir [lindex $argv 5] + +set timeout 30 + +send_user "checkout module $module"; + +spawn ssh $user@$host +expect { + "(yes/no)? " { + send "yes\r" + send_user "Key accepted"; + } + "assword:" { + close + send_user "Key accepting dont needed"; + } + } + +spawn cvs -d :ext:$user@$host:$cvsroot co -N -d $dir $module +expect { + "assword:" { + send "$pass\r" + expect { + "assword:" { + send_user "

cvs checkout $module failed. Incorrect password

"; + exit 1 + } + "aborted" { + send_user "

cvs checkout $module failed.

"; + exit 1 + } + expect eof { + if {[lindex [wait] 3]} { + send_user "

cvs checkout $module failed.

" + exit 1 + } + } + } + } + + "aborted" { + send_user "

cvs checkout $module failed.

"; + exit 1 + } + } + + + + diff --git a/projects/make_tarball/mt.sh b/projects/make_tarball/mt.sh new file mode 100755 index 00000000..0ad2e6b1 --- /dev/null +++ b/projects/make_tarball/mt.sh @@ -0,0 +1,59 @@ +#!/bin/bash + + +cvs_host=stgteam.dp.ua +cvs_user= +cvs_pass= + +#arc_name=stg-2.4-`date "+%Y.%m.%d-%H.%M.%S"`.tgz +#arc_name=stg-2.4-`date "+%Y.%m.%d-%H.%M.%S"`.tgz +src_dir=stg-2.4-`date "+%Y.%m.%d-%H.%M.%S"` +arc_name=$src_dir.tar.gz + +#mkdir $src_dir + +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stgincludes include $src_dir + +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/common.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/ibpp.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/common_settings.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/conffiles.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/crypto.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/stg_logger.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/stg_locker.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/hostallow.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/pinger.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/dotconfpp.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/ia_auth_c.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/script_executer.lib $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/srvconf.lib $src_dir + +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/Makefile $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stglibs stglibs/Makefile.in $src_dir +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stargazer convertor $src_dir/projects +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stargazer stargazer $src_dir/projects + +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/sgauth sgauth $src_dir/projects +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/sgconf sgconf $src_dir/projects +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/rscriptd rscriptd $src_dir/projects +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stargazer rlm_stg $src_dir/projects + +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stgplugins plugins/store/firebird $src_dir/projects/stargazer +./get_from_cvs $cvs_user $cvs_pass $cvs_host /cvsroot/stg3plugins max_mods/mysql $src_dir/projects/stargazer/plugins/store +mv $src_dir/projects/stargazer/plugins/store/max_mods/mysql $src_dir/projects/stargazer/plugins/store/ +rm -rf $src_dir/projects/stargazer/plugins/store/max_mods +rm -rf $src_dir/projects/stargazer/plugins/other/userstat +rm -rf $src_dir/projects/stargazer/plugins/authorization/stress +rm -rf $src_dir/projects/stargazer/plugins/store/db +rm -rf $src_dir/projects/stargazer/plugins/configuration/rpcconfig + +rm -f $src_dir/include/lp2_blocks.h +rm -f $src_dir/include/stdstring.h + +mkdir -p $src_dir/lib + +rm -fr $(find $src_dir/ -name CVS -type d) + +rm -fr $src_dir/projects/stargazer/inst/var/stargazer/users/CVS + +tar -czf $arc_name $src_dir diff --git a/projects/rlm_stg/Makefile b/projects/rlm_stg/Makefile new file mode 100644 index 00000000..deaf437e --- /dev/null +++ b/projects/rlm_stg/Makefile @@ -0,0 +1,81 @@ +############################################################################### +# $Id: Makefile,v 1.5 2009/03/03 15:49:34 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +LIB_NAME = rlm_stg + +PROG = $(LIB_NAME).so + +SRCS = ./rlm_stg.cpp \ + ./stg_client.cpp + +STGLIBS = -lstg_common \ + -lstg_crypto + +LIBS += $(LIB_THREAD) + +ifeq ($(OS),linux) +LIBS += -ldl +else +LIBS += -lintl \ + -lc +endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +CXXFLAGS += -Wall -fPIC -I./ +LDFLAGS += -shared -L$(DIR_LIB) -Wl,-rpath,$(PREFIX)/usr/lib/stg + +vpath %.so $(DIR_LIB) + +.PHONY: all clean distclean libs install uninstall install-bin uninstall-bin +all: libs $(PROG) ../../Makefile.conf + +libs: + $(MAKE) -C $(DIR_LIBSRC) + +$(PROG): $(OBJS) $(STGLIBS) + $(CC) $^ $(LDFLAGS) -o $(PROG) $(LIBS) + +clean: + rm -f deps $(PROG) *.o tags *.*~ .OS + rm -f .OS + rm -f core* + $(MAKE) -C $(DIR_LIBSRC) clean + +distclean: clean + rm -f ../../Makefile.conf + +install: install-bin + +install-bin: + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/lib + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/lib/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) install + +uninstall: uninstall-bin + +uninstall-bin: + rm -f $(PREFIX)/usr/sbin/$(PROG) + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf + $(MAKE) -C $(DIR_LIBSRC) includes + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) -MM $$file` Makefile" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/rlm_stg/build b/projects/rlm_stg/build new file mode 100755 index 00000000..e1102fc4 --- /dev/null +++ b/projects/rlm_stg/build @@ -0,0 +1,135 @@ +#!/bin/sh + +# $Author: faust $ +# $Revision: 1.13 $ +# $Date: 2010/04/14 08:58:44 $ +###################################################### + +OS=unknown +sys=`uname -s` +release=`uname -r | cut -b1` +BUILD_DIR=`pwd` +CONFFILE="../../Makefile.conf" +PREFIX="/" +BIN_MODE=0755 +OWNER=root + +if [ -z $1 ] +then + MAKEOPTS="-j1" +else + if [ "$1" = "debug" ] + then + DEFS="-DDEBUG" + MAKEOPTS="-j1" + CXXFLAGS="$CXXFLAGS -g3 -W -Wall" + else + MAKEOPTS="-j1" + fi +fi + +CXXFLAGS="$CXXFLAGS -I/usr/local/include" +LDFLAGS="$LDFLAGS -L/usr/local/lib" + +if [ "$sys" = "Linux" ] +then + OS=linux + release="" + MAKE="make" +fi + +if [ "$sys" = "FreeBSD" ] +then + case $release in + 4) OS=bsd;; + 5) OS=bsd5;; + 6) OS=bsd5;; + 7) OS=bsd7;; + 8) OS=bsd7;; + *) OS=unknown;; + esac + MAKE="gmake" +fi + +if [ "$OS" = "unknown" ] +then + echo "#############################################################################" + echo "# Sorry, but rlm_stg currently supported by Linux, FreeBSD 4.x, 5.x, 6.x #" + echo "#############################################################################" + exit 1 +fi + +echo "#############################################################################" +echo " Building rlm_stg for $sys $release" +echo "#############################################################################" + +STG_LIBS="crypto.lib common.lib" + +if [ "$OS" = "linux" ] +then + DEFS="$DEFS -DLINUX" + LIB_THREAD=-lpthread + SHELL="/bin/bash" +else + if [ "$OS" = "bsd" ] + then + DEFS="$DEFS -DFREE_BSD" + else + DEFS="$DEFS -DFREE_BSD5" + if [ "$OS" = "bsd7" ] + then + LIB_THREAD=-lpthread + else + LIB_THREAD=-lc_r + fi + fi + SHELL="/usr/local/bin/bash" +fi + +echo -n "Checking endianess... " +echo "int main() { int probe = 0x00000001; return *(char *)&probe; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS build_check.c -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + echo "FAIL!" + echo "Endianess checking failed" + exit; +else + ./fake + if [ $? = 1 ] + then + ARCH=le + CXXFLAGS="$CXXFLAGS -DARCH_LE" + echo "Little Endian" + else + ARCH=be + CXXFLAGS="$CXXFLAGS -DARCH_BE" + echo "Big Endian" + fi +fi +rm -f fake + +echo "OS=$OS" > $CONFFILE +echo "STG_TIME=yes" >> $CONFFILE +echo "DIR_BUILD=$BUILD_DIR" >> $CONFFILE +echo "DIR_LIB=\$(DIR_BUILD)/../../lib" >> $CONFFILE +echo "DIR_LIBSRC=\$(DIR_BUILD)/../../stglibs" >> $CONFFILE +echo "DIR_INCLUDE=\$(DIR_BUILD)/../../include" >> $CONFFILE +echo "ARCH=$ARCH" >> $CONFFILE +echo "DEFS=$DEFS" >> $CONFFILE +echo -n "STG_LIBS=" >> $CONFFILE +for lib in $STG_LIBS +do + echo -n "$lib " >> $CONFFILE +done +echo "" >> $CONFFILE +echo "CXXFLAGS=$CXXFLAGS" >> $CONFFILE +echo "LDFLAGS=$LDFLAGS" >> $CONFFILE +echo "LIB_THREAD=$LIB_THREAD" >> $CONFFILE +echo "PREFIX=$PREFIX" >> $CONFFILE +echo "BIN_MODE=$BIN_MODE" >> $CONFFILE +echo "DATA_MODE=$DATA_MODE" >> $CONFFILE +echo "OWNER=$OWNER" >> $CONFFILE +echo "SHELL=$SHELL" >> $CONFFILE +$MAKE $MAKEOPTS + diff --git a/projects/rlm_stg/build_check.c b/projects/rlm_stg/build_check.c new file mode 100644 index 00000000..a5a7341e --- /dev/null +++ b/projects/rlm_stg/build_check.c @@ -0,0 +1 @@ +int main() { int probe = 0x00000001; return *(char *)&probe; } diff --git a/projects/rlm_stg/conf.h b/projects/rlm_stg/conf.h new file mode 100644 index 00000000..e96eb715 --- /dev/null +++ b/projects/rlm_stg/conf.h @@ -0,0 +1,38 @@ +/* Default Database File Names */ + +#define RADIUS_DIR RADDBDIR +#define RADACCT_DIR RADIR +#define RADLOG_DIR LOGDIR + +#define RADIUS_DICTIONARY "dictionary" +#define RADIUS_CLIENTS "clients" +#define RADIUS_NASLIST "naslist" +#define RADIUS_REALMS "realms" + +#define RADUTMP LOGDIR "/radutmp" +#define SRADUTMP LOGDIR "/sradutmp" +#define RADWTMP LOGDIR "/radwtmp" +#define SRADWTMP LOGDIR "/sradwtmp" + +/* Hack for funky ascend ports on MAX 4048 (and probably others) + The "NAS-Port-Id" value is "xyyzz" where "x" = 1 for digital, 2 for analog; + "yy" = line number (1 for first PRI/T1/E1, 2 for second, so on); + "zz" = channel number (on the PRI or Channelized T1/E1). + This should work with normal terminal servers, unless you have a TS with + more than 9999 ports ;^). + The "ASCEND_CHANNELS_PER_LINE" is the number of channels for each line into + the unit. For my US/PRI that's 23. A US/T1 would be 24, and a + European E1 would be 30 (I think ... never had one ;^). + This will NOT change the "NAS-Port-Id" reported in the detail log. This + is simply to fix the dynamic IP assignments a la Cistron. + You can change the default of 23 with an argument to ./configure. + WARNING: This hack works for me, but I only have one PRI!!! I've not + tested it on 2 or more (or with models other than the Max 4048) + Use at your own risk! + -- dgreer@austintx.com +*/ +#ifdef ASCEND_PORT_HACK +# ifndef ASCEND_CHANNELS_PER_LINE +# define ASCEND_CHANNELS_PER_LINE 23 +# endif +#endif diff --git a/projects/rlm_stg/conffile.h b/projects/rlm_stg/conffile.h new file mode 100644 index 00000000..e940e115 --- /dev/null +++ b/projects/rlm_stg/conffile.h @@ -0,0 +1,126 @@ +#ifndef _CONFFILE_H +#define _CONFFILE_H + +/* + * conffile.h Defines for the conffile parsing routines. + * + * Version: $Id: conffile.h,v 1.1 2010/08/14 04:13:52 faust Exp $ + * + */ + +#include +RCSIDH(conffile_h, "$Id: conffile.h,v 1.1 2010/08/14 04:13:52 faust Exp $") + +#include +#include + +/* + * Export the minimum amount of information about these structs + */ +typedef struct conf_item CONF_ITEM; +typedef struct conf_pair CONF_PAIR; +typedef struct conf_part CONF_SECTION; +typedef struct conf_data CONF_DATA; + +/* + * Instead of putting the information into a configuration structure, + * the configuration file routines MAY just parse it directly into + * user-supplied variables. + */ +#define PW_TYPE_STRING_PTR 100 +#define PW_TYPE_BOOLEAN 101 +#define PW_TYPE_SUBSECTION 102 +#define PW_TYPE_FILENAME 103 + +typedef struct CONF_PARSER { + const char *name; + int type; /* PW_TYPE_STRING, etc. */ + size_t offset; /* relative pointer within "base" */ + void *data; /* absolute pointer if base is NULL */ + const char *dflt; /* default as it would appear in radiusd.conf */ +} CONF_PARSER; + +/* This preprocessor trick will be useful in initializing CONF_PARSER struct */ +#define XStringify(x) #x +#define Stringify(x) XStringify(x) + +void cf_pair_free(CONF_PAIR **cp); +int cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, + const char *value); +void cf_section_free(CONF_SECTION **cp); +int cf_item_parse(CONF_SECTION *cs, const char *name, + int type, void *data, const char *dflt); +int cf_section_parse(CONF_SECTION *, void *base, + const CONF_PARSER *variables); +void cf_section_parse_free(CONF_SECTION *cs, void *base); +const CONF_PARSER *cf_section_parse_table(CONF_SECTION *cs); +CONF_SECTION *cf_file_read(const char *file); +int cf_file_include(const char *file, CONF_SECTION *cs); + +CONF_PAIR *cf_pair_find(const CONF_SECTION *, const char *name); +CONF_PAIR *cf_pair_find_next(const CONF_SECTION *, CONF_PAIR *, const char *name); +CONF_SECTION *cf_section_find(const char *name); +CONF_SECTION *cf_section_sub_find(const CONF_SECTION *, const char *name); +CONF_SECTION *cf_section_sub_find_name2(const CONF_SECTION *, const char *name1, const char *name2); +const char *cf_section_value_find(const CONF_SECTION *, const char *attr); +CONF_SECTION *cf_top_section(CONF_SECTION *cs); + +void *cf_data_find(CONF_SECTION *, const char *); +int cf_data_add(CONF_SECTION *, const char *, void *, void (*)(void *)); + +const char *cf_pair_attr(CONF_PAIR *pair); +const char *cf_pair_value(CONF_PAIR *pair); +VALUE_PAIR *cf_pairtovp(CONF_PAIR *pair); +const char *cf_section_name1(const CONF_SECTION *); +const char *cf_section_name2(const CONF_SECTION *); +int dump_config(CONF_SECTION *cs); +CONF_SECTION *cf_subsection_find_next(CONF_SECTION *section, + CONF_SECTION *subsection, + const char *name1); +CONF_SECTION *cf_section_find_next(CONF_SECTION *section, + CONF_SECTION *subsection, + const char *name1); +int cf_section_lineno(CONF_SECTION *section); +int cf_pair_lineno(CONF_PAIR *pair); +const char *cf_pair_filename(CONF_PAIR *pair); +const char *cf_section_filename(CONF_SECTION *section); +CONF_ITEM *cf_item_find_next(CONF_SECTION *section, CONF_ITEM *item); +int cf_item_is_section(CONF_ITEM *item); +int cf_item_is_pair(CONF_ITEM *item); +CONF_PAIR *cf_itemtopair(CONF_ITEM *item); +CONF_SECTION *cf_itemtosection(CONF_ITEM *item); +CONF_ITEM *cf_pairtoitem(CONF_PAIR *cp); +CONF_ITEM *cf_sectiontoitem(CONF_SECTION *cs); +int cf_section_template(CONF_SECTION *cs, CONF_SECTION *_template); +void cf_log_err(CONF_ITEM *ci, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif +; +void cf_log_info(CONF_SECTION *cs, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif +; +void cf_log_module(CONF_SECTION *cs, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif +; +CONF_ITEM *cf_reference_item(const CONF_SECTION *parentcs, + CONF_SECTION *outercs, + const char *ptr); +extern int cf_log_config; +extern int cf_log_modules; + +extern int cf_pair2xml(FILE *fp, CONF_PAIR *cp); +extern int cf_section2xml(FILE *fp, CONF_SECTION *cs); +extern int cf_pair2file(FILE *fp, CONF_PAIR *cp); +extern int cf_section2file(FILE *fp, CONF_SECTION *cs); + +/* + * Big magic. + */ +int cf_section_migrate(CONF_SECTION *dst, CONF_SECTION *src); + +#endif /* _CONFFILE_H */ diff --git a/projects/rlm_stg/event.h b/projects/rlm_stg/event.h new file mode 100644 index 00000000..704f13cc --- /dev/null +++ b/projects/rlm_stg/event.h @@ -0,0 +1,57 @@ +#ifndef FR_EVENT_H +#define FR_EVENT_H + +/* + * event.h Simple event queue + * + * Version: $Id: event.h,v 1.1 2010/08/14 04:13:52 faust Exp $ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2007 The FreeRADIUS server project + * Copyright 2007 Alan DeKok + */ + +#include +RCSIDH(event_h, "$Id: event.h,v 1.1 2010/08/14 04:13:52 faust Exp $") + +typedef struct fr_event_list_t fr_event_list_t; +typedef struct fr_event_t fr_event_t; + +typedef void (*fr_event_callback_t)(void *); +typedef void (*fr_event_status_t)(struct timeval *); +typedef void (*fr_event_fd_handler_t)(fr_event_list_t *el, int sock, void *ctx); + +fr_event_list_t *fr_event_list_create(fr_event_status_t status); +void fr_event_list_free(fr_event_list_t *el); + +int fr_event_list_num_elements(fr_event_list_t *el); + +int fr_event_insert(fr_event_list_t *el, + fr_event_callback_t callback, + void *ctx, struct timeval *when, fr_event_t **ev_p); +int fr_event_delete(fr_event_list_t *el, fr_event_t **ev_p); + +int fr_event_run(fr_event_list_t *el, struct timeval *when); + +int fr_event_now(fr_event_list_t *el, struct timeval *when); + +int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + fr_event_fd_handler_t handler, void *ctx); +int fr_event_fd_delete(fr_event_list_t *el, int type, int fd); +int fr_event_loop(fr_event_list_t *el); +void fr_event_loop_exit(fr_event_list_t *el, int code); + +#endif /* FR_HASH_H */ diff --git a/projects/rlm_stg/libradius.h b/projects/rlm_stg/libradius.h new file mode 100644 index 00000000..b184a75a --- /dev/null +++ b/projects/rlm_stg/libradius.h @@ -0,0 +1,470 @@ +#ifndef LIBRADIUS_H +#define LIBRADIUS_H + +/* + * libradius.h Structures and prototypes + * for the radius library. + * + * Version: $Id: libradius.h,v 1.1 2010/08/14 04:13:52 faust Exp $ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008 The FreeRADIUS server project + */ + +#include +RCSIDH(libradius_h, "$Id: libradius.h,v 1.1 2010/08/14 04:13:52 faust Exp $") + +#include + +#include +#include +#include +#include "os_int.h" + +#include +#include + +#include +#include +#include + +#ifdef SIZEOF_UNSIGNED_INT +#if SIZEOF_UNSIGNED_INT != 4 +#error FATAL: sizeof(unsigned int) != 4 +#endif +#endif + +/* + * Include for modules. + */ +#include +#include + +#define EAP_START 2 + +#define AUTH_VECTOR_LEN 16 +#define CHAP_VALUE_LENGTH 16 +#define MAX_STRING_LEN 254 /* RFC2138: string 0-253 octets */ + +# define VENDOR(x) ((x >> 16) & 0xffff) + +#ifdef _LIBRADIUS +# define AUTH_HDR_LEN 20 +# define VENDORPEC_USR 429 +#define VENDORPEC_LUCENT 4846 +#define VENDORPEC_STARENT 8164 +# define DEBUG if (fr_debug_flag && fr_log_fp) fr_printf_log +# define debug_pair(vp) do { if (fr_debug_flag && fr_log_fp) { \ + fputc('\t', fr_log_fp); \ + vp_print(fr_log_fp, vp); \ + fputc('\n', fr_log_fp); \ + } \ + } while(0) +# define TAG_VALID(x) ((x) > 0 && (x) < 0x20) +# define TAG_VALID_ZERO(x) ((x) < 0x20) +# define TAG_ANY -128 /* minimum signed char */ +#endif + +#if defined(__GNUC__) +# define PRINTF_LIKE(n) __attribute__ ((format(printf, n, n+1))) +# define NEVER_RETURNS __attribute__ ((noreturn)) +# define UNUSED __attribute__ ((unused)) +# define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ +#else +# define PRINTF_LIKE(n) /* ignore */ +# define NEVER_RETURNS /* ignore */ +# define UNUSED /* ignore */ +# define BLANK_FORMAT "" +#endif + +typedef struct attr_flags { + unsigned int addport : 1; /* add NAS-Port to IP address */ + unsigned int has_tag : 1; /* tagged attribute */ + unsigned int do_xlat : 1; /* strvalue is dynamic */ + unsigned int unknown_attr : 1; /* not in dictionary */ + unsigned int array : 1; /* pack multiples into 1 attr */ + unsigned int has_value : 1; /* has a value */ + unsigned int has_value_alias : 1; /* has a value alias */ + unsigned int has_tlv : 1; /* has sub attributes */ + unsigned int is_tlv : 1; /* is a sub attribute */ + unsigned int encoded : 1; /* has been put into packet */ + + int8_t tag; /* tag for tunneled attributes */ + uint8_t encrypt; /* encryption method */ +} ATTR_FLAGS; + +/* + * Values of the encryption flags. + */ +#define FLAG_ENCRYPT_NONE (0) +#define FLAG_ENCRYPT_USER_PASSWORD (1) +#define FLAG_ENCRYPT_TUNNEL_PASSWORD (2) +#define FLAG_ENCRYPT_ASCEND_SECRET (3) + +typedef struct dict_attr { + unsigned int attr; + int type; + int vendor; + ATTR_FLAGS flags; + char name[1]; +} DICT_ATTR; + +typedef struct dict_value { + unsigned int attr; + int value; + char name[1]; +} DICT_VALUE; + +typedef struct dict_vendor { + int vendorpec; + int type; /* length of type data */ + int length; /* length of length data */ + int flags; + char name[1]; +} DICT_VENDOR; + +typedef union value_pair_data { + char strvalue[MAX_STRING_LEN]; + uint8_t octets[MAX_STRING_LEN]; + struct in_addr ipaddr; + struct in6_addr ipv6addr; + uint32_t date; + uint32_t integer; + int32_t sinteger; + uint8_t filter[32]; + uint8_t ifid[8]; /* struct? */ + uint8_t ipv6prefix[18]; /* struct? */ + uint8_t ether[6]; + uint8_t *tlv; +} VALUE_PAIR_DATA; + +typedef struct value_pair { + const char *name; + int attribute; + int vendor; + int type; + size_t length; /* of data */ + FR_TOKEN _operator; + ATTR_FLAGS flags; + struct value_pair *next; + uint32_t lvalue; + VALUE_PAIR_DATA data; +} VALUE_PAIR; +#define vp_strvalue data.strvalue +#define vp_octets data.octets +#define vp_ipv6addr data.ipv6addr +#define vp_ifid data.ifid +#define vp_ipv6prefix data.ipv6prefix +#define vp_filter data.filter +#define vp_ether data.ether +#define vp_signed data.sinteger +#define vp_tlv data.tlv + +#if 0 +#define vp_ipaddr data.ipaddr.s_addr +#define vp_date data.date +#define vp_integer data.integer +#else +/* + * These are left as lvalue until we audit the source for code + * that prints to vp_strvalue for integer/ipaddr/date types. + */ +#define vp_ipaddr lvalue +#define vp_date lvalue +#define vp_integer lvalue +#endif + + +typedef struct fr_ipaddr_t { + int af; /* address family */ + union { + struct in_addr ip4addr; + struct in6_addr ip6addr; /* maybe defined in missing.h */ + } ipaddr; +} fr_ipaddr_t; + +/* + * vector: Request authenticator from access-request packet + * Put in there by rad_decode, and must be put in the + * response RADIUS_PACKET as well before calling rad_send + * + * verified: Filled in by rad_decode for accounting-request packets + * + * data,data_len: Used between rad_recv and rad_decode. + */ +typedef struct radius_packet { + int sockfd; + fr_ipaddr_t src_ipaddr; + fr_ipaddr_t dst_ipaddr; + uint16_t src_port; + uint16_t dst_port; + int id; + unsigned int code; + uint32_t hash; + uint8_t vector[AUTH_VECTOR_LEN]; + time_t timestamp; + uint8_t *data; + int data_len; + VALUE_PAIR *vps; + ssize_t offset; +} RADIUS_PACKET; + +/* + * Printing functions. + */ +int fr_utf8_char(const uint8_t *str); +void fr_print_string(const char *in, size_t inlen, + char *out, size_t outlen); +int vp_prints_value(char *out, size_t outlen, + VALUE_PAIR *vp, int delimitst); +const char *vp_print_name(char *buffer, size_t bufsize, int attr); +int vp_prints(char *out, size_t outlen, VALUE_PAIR *vp); +void vp_print(FILE *, VALUE_PAIR *); +void vp_printlist(FILE *, VALUE_PAIR *); +#define fprint_attr_val vp_print + +/* + * Dictionary functions. + */ +int dict_addvendor(const char *name, int value); +int dict_addattr(const char *name, int vendor, int type, int value, ATTR_FLAGS flags); +int dict_addvalue(const char *namestr, const char *attrstr, int value); +int dict_init(const char *dir, const char *fn); +void dict_free(void); +DICT_ATTR *dict_attrbyvalue(unsigned int attr); +DICT_ATTR *dict_attrbyname(const char *attr); +DICT_VALUE *dict_valbyattr(unsigned int attr, int val); +DICT_VALUE *dict_valbyname(unsigned int attr, const char *val); +int dict_vendorbyname(const char *name); +DICT_VENDOR *dict_vendorbyvalue(int vendor); + +#if 1 /* FIXME: compat */ +#define dict_attrget dict_attrbyvalue +#define dict_attrfind dict_attrbyname +#define dict_valfind dict_valbyname +/*#define dict_valget dict_valbyattr almost but not quite*/ +#endif + +/* get around diffrent ctime_r styles */ +#ifdef CTIMERSTYLE +#if CTIMERSTYLE == SOLARISSTYLE +#define CTIME_R(a,b,c) ctime_r(a,b,c) +#else +#define CTIME_R(a,b,c) ctime_r(a,b) +#endif +#else +#define CTIME_R(a,b,c) ctime_r(a,b) +#endif + +/* md5.c */ + +void fr_md5_calc(uint8_t *, const uint8_t *, unsigned int); + +/* hmac.c */ + +void fr_hmac_md5(const uint8_t *text, int text_len, + const uint8_t *key, int key_len, + unsigned char *digest); + +/* hmacsha1.c */ + +void fr_hmac_sha1(const uint8_t *text, int text_len, + const uint8_t *key, int key_len, + uint8_t *digest); + +/* radius.c */ +int rad_send(RADIUS_PACKET *, const RADIUS_PACKET *, const char *secret); +int rad_packet_ok(RADIUS_PACKET *packet, int flags); +RADIUS_PACKET *rad_recv(int fd, int flags); +ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, int *src_port, + int *code); +void rad_recv_discard(int sockfd); +int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, + const char *secret); +int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, const char *secret); +int rad_encode(RADIUS_PACKET *packet, const RADIUS_PACKET *original, + const char *secret); +int rad_sign(RADIUS_PACKET *packet, const RADIUS_PACKET *original, + const char *secret); + +RADIUS_PACKET *rad_alloc(int newvector); +RADIUS_PACKET *rad_alloc_reply(RADIUS_PACKET *); +void rad_free(RADIUS_PACKET **); +int rad_pwencode(char *encpw, size_t *len, const char *secret, + const uint8_t *vector); +int rad_pwdecode(char *encpw, size_t len, const char *secret, + const uint8_t *vector); +int rad_tunnel_pwencode(char *encpw, size_t *len, const char *secret, + const uint8_t *vector); +int rad_tunnel_pwdecode(uint8_t *encpw, size_t *len, + const char *secret, const uint8_t *vector); +int rad_chap_encode(RADIUS_PACKET *packet, uint8_t *output, + int id, VALUE_PAIR *password); +VALUE_PAIR *rad_attr2vp(const RADIUS_PACKET *packet, const RADIUS_PACKET *original, + const char *secret, int attribute, int length, + const uint8_t *data); +int rad_vp2attr(const RADIUS_PACKET *packet, + const RADIUS_PACKET *original, const char *secret, + const VALUE_PAIR *vp, uint8_t *ptr); + +/* valuepair.c */ +VALUE_PAIR *pairalloc(DICT_ATTR *da); +VALUE_PAIR *paircreate(int attr, int type); +void pairfree(VALUE_PAIR **); +void pairbasicfree(VALUE_PAIR *pair); +VALUE_PAIR *pairfind(VALUE_PAIR *, int); +void pairdelete(VALUE_PAIR **, int); +void pairadd(VALUE_PAIR **, VALUE_PAIR *); +void pairreplace(VALUE_PAIR **first, VALUE_PAIR *add); +int paircmp(VALUE_PAIR *check, VALUE_PAIR *data); +VALUE_PAIR *paircopyvp(const VALUE_PAIR *vp); +VALUE_PAIR *paircopy(VALUE_PAIR *vp); +VALUE_PAIR *paircopy2(VALUE_PAIR *vp, int attr); +void pairmove(VALUE_PAIR **to, VALUE_PAIR **from); +void pairmove2(VALUE_PAIR **to, VALUE_PAIR **from, int attr); +VALUE_PAIR *pairparsevalue(VALUE_PAIR *vp, const char *value); +VALUE_PAIR *pairmake(const char *attribute, const char *value, int _operator); +VALUE_PAIR *pairread(const char **ptr, FR_TOKEN *eol); +FR_TOKEN userparse(const char *buffer, VALUE_PAIR **first_pair); +VALUE_PAIR *readvp2(FILE *fp, int *pfiledone, const char *errprefix); + +/* + * Error functions. + */ +#ifdef _LIBRADIUS +void fr_strerror_printf(const char *, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; +#endif +void fr_perror(const char *, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; +extern const char *fr_strerror(void); +extern int fr_dns_lookups; /* 0 = no dns lookups */ +extern int fr_debug_flag; /* 0 = no debugging information */ +extern int fr_max_attributes; /* per incoming packet */ +#define FR_MAX_PACKET_CODE (52) +extern const char *fr_packet_codes[FR_MAX_PACKET_CODE]; +extern FILE *fr_log_fp; +void fr_printf_log(const char *, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; + +/* + * Several handy miscellaneous functions. + */ +const char * ip_ntoa(char *, uint32_t); +char *ifid_ntoa(char *buffer, size_t size, uint8_t *ifid); +uint8_t *ifid_aton(const char *ifid_str, uint8_t *ifid); +int rad_lockfd(int fd, int lock_len); +int rad_lockfd_nonblock(int fd, int lock_len); +int rad_unlockfd(int fd, int lock_len); +void fr_bin2hex(const uint8_t *bin, char *hex, size_t len); +size_t fr_hex2bin(const char *hex, uint8_t *bin, size_t len); +#ifndef HAVE_CLOSEFROM +int closefrom(int fd); +#endif +int fr_ipaddr_cmp(const fr_ipaddr_t *a, const fr_ipaddr_t *b); + +int ip_hton(const char *src, int af, fr_ipaddr_t *dst); +const char *ip_ntoh(const fr_ipaddr_t *src, char *dst, size_t cnt); +int fr_ipaddr2sockaddr(const fr_ipaddr_t *ipaddr, int port, + struct sockaddr_storage *sa, socklen_t *salen); +int fr_sockaddr2ipaddr(const struct sockaddr_storage *sa, socklen_t salen, + fr_ipaddr_t *ipaddr, int * port); + + +#ifdef ASCEND_BINARY +/* filters.c */ +int ascend_parse_filter(VALUE_PAIR *pair); +void print_abinary(VALUE_PAIR *vp, char *buffer, size_t len); +#endif /*ASCEND_BINARY*/ + +/* random numbers in isaac.c */ +/* context of random number generator */ +typedef struct fr_randctx { + uint32_t randcnt; + uint32_t randrsl[256]; + uint32_t randmem[256]; + uint32_t randa; + uint32_t randb; + uint32_t randc; +} fr_randctx; + +void fr_isaac(fr_randctx *ctx); +void fr_randinit(fr_randctx *ctx, int flag); +uint32_t fr_rand(void); /* like rand(), but better. */ +void fr_rand_seed(const void *, size_t ); /* seed the random pool */ + + +/* crypt wrapper from crypt.c */ +int fr_crypt_check(const char *key, const char *salt); + +/* rbtree.c */ +typedef struct rbtree_t rbtree_t; +typedef struct rbnode_t rbnode_t; + +rbtree_t *rbtree_create(int (*Compare)(const void *, const void *), + void (*freeNode)(void *), + int replace_flag); +void rbtree_free(rbtree_t *tree); +int rbtree_insert(rbtree_t *tree, void *Data); +rbnode_t *rbtree_insertnode(rbtree_t *tree, void *Data); +void rbtree_delete(rbtree_t *tree, rbnode_t *Z); +int rbtree_deletebydata(rbtree_t *tree, const void *data); +rbnode_t *rbtree_find(rbtree_t *tree, const void *Data); +void *rbtree_finddata(rbtree_t *tree, const void *Data); +int rbtree_num_elements(rbtree_t *tree); +void *rbtree_min(rbtree_t *tree); +void *rbtree_node2data(rbtree_t *tree, rbnode_t *node); + +/* callback order for walking */ +typedef enum { PreOrder, InOrder, PostOrder } RBTREE_ORDER; + +/* + * The callback should be declared as: + * int callback(void *context, void *data) + * + * The "context" is some user-defined context. + * The "data" is the pointer to the user data in the node, + * NOT the node itself. + * + * It should return 0 if all is OK, and !0 for any error. + * The walking will stop on any error. + */ +int rbtree_walk(rbtree_t *tree, RBTREE_ORDER order, int (*callback)(void *, void *), void *context); + +/* + * FIFOs + */ +typedef struct fr_fifo_t fr_fifo_t; +typedef void (*fr_fifo_free_t)(void *); +fr_fifo_t *fr_fifo_create(int max_entries, fr_fifo_free_t freeNode); +void fr_fifo_free(fr_fifo_t *fi); +int fr_fifo_push(fr_fifo_t *fi, void *data); +void *fr_fifo_pop(fr_fifo_t *fi); +void *fr_fifo_peek(fr_fifo_t *fi); +int fr_fifo_num_elements(fr_fifo_t *fi); + +#include + +#endif /*LIBRADIUS_H*/ diff --git a/projects/rlm_stg/modules.h b/projects/rlm_stg/modules.h new file mode 100644 index 00000000..e241ca80 --- /dev/null +++ b/projects/rlm_stg/modules.h @@ -0,0 +1,91 @@ +/* + * module.h Interface to the RADIUS module system. + * + * Version: $Id: modules.h,v 1.1 2010/08/14 04:13:52 faust Exp $ + * + */ + +#ifndef RADIUS_MODULES_H +#define RADIUS_MODULES_H + +#include +RCSIDH(modules_h, "$Id: modules.h,v 1.1 2010/08/14 04:13:52 faust Exp $") + +#include "conffile.h" + +typedef int (*packetmethod)(void *instance, REQUEST *request); + +enum { + RLM_COMPONENT_AUTH = 0, + RLM_COMPONENT_AUTZ, /* 1 */ + RLM_COMPONENT_PREACCT, /* 2 */ + RLM_COMPONENT_ACCT, /* 3 */ + RLM_COMPONENT_SESS, /* 4 */ + RLM_COMPONENT_PRE_PROXY, /* 5 */ + RLM_COMPONENT_POST_PROXY, /* 6 */ + RLM_COMPONENT_POST_AUTH, /* 7 */ +#ifdef WITH_COA + RLM_COMPONENT_RECV_COA, /* 8 */ + RLM_COMPONENT_SEND_COA, /* 9 */ +#endif + RLM_COMPONENT_COUNT /* 8 / 10: How many components are there */ +}; + +#define RLM_TYPE_THREAD_SAFE (0 << 0) +#define RLM_TYPE_THREAD_UNSAFE (1 << 0) +#define RLM_TYPE_CHECK_CONFIG_SAFE (1 << 1) +#define RLM_TYPE_HUP_SAFE (1 << 2) + +#define RLM_MODULE_MAGIC_NUMBER ((uint32_t) (0xf4ee4ad2)) +#define RLM_MODULE_INIT RLM_MODULE_MAGIC_NUMBER + +typedef struct module_t { + uint32_t magic; /* may later be opaque struct */ + const char *name; + int type; + int (*instantiate)(CONF_SECTION *mod_cs, void **instance); + int (*detach)(void *instance); + packetmethod methods[RLM_COMPONENT_COUNT]; +} module_t; + +enum { + RLM_MODULE_REJECT, /* immediately reject the request */ + RLM_MODULE_FAIL, /* module failed, don't reply */ + RLM_MODULE_OK, /* the module is OK, continue */ + RLM_MODULE_HANDLED, /* the module handled the request, so stop. */ + RLM_MODULE_INVALID, /* the module considers the request invalid. */ + RLM_MODULE_USERLOCK, /* reject the request (user is locked out) */ + RLM_MODULE_NOTFOUND, /* user not found */ + RLM_MODULE_NOOP, /* module succeeded without doing anything */ + RLM_MODULE_UPDATED, /* OK (pairs modified) */ + RLM_MODULE_NUMCODES /* How many return codes there are */ +}; + +int setup_modules(int, CONF_SECTION *); +int detach_modules(void); +int module_hup(CONF_SECTION *modules); +int module_authorize(int type, REQUEST *request); +int module_authenticate(int type, REQUEST *request); +int module_preacct(REQUEST *request); +int module_accounting(int type, REQUEST *request); +int module_checksimul(int type, REQUEST *request, int maxsimul); +int module_pre_proxy(int type, REQUEST *request); +int module_post_proxy(int type, REQUEST *request); +int module_post_auth(int type, REQUEST *request); +#ifdef WITH_COA +int module_recv_coa(int type, REQUEST *request); +int module_send_coa(int type, REQUEST *request); +#define MODULE_NULL_COA_FUNCS ,NULL,NULL +#else +#define MODULE_NULL_COA_FUNCS +#endif +int indexed_modcall(int comp, int idx, REQUEST *request); + +/* + * For now, these are strongly tied together. + */ +int virtual_servers_load(CONF_SECTION *config); +void virtual_servers_free(time_t when); + + +#endif /* RADIUS_MODULES_H */ diff --git a/projects/rlm_stg/radiusd.h b/projects/rlm_stg/radiusd.h new file mode 100644 index 00000000..2b93ee3a --- /dev/null +++ b/projects/rlm_stg/radiusd.h @@ -0,0 +1,633 @@ +#ifndef RADIUSD_H +#define RADIUSD_H +/* + * radiusd.h Structures, prototypes and global variables + * for the FreeRADIUS server. + * + * Version: $Id: radiusd.h,v 1.1 2010/08/14 04:13:52 faust Exp $ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 1999,2000,2002,2003,2004,2005,2006,2007,2008 The FreeRADIUS server project + * + */ + +#include +RCSIDH(radiusd_h, "$Id: radiusd.h,v 1.1 2010/08/14 04:13:52 faust Exp $") + +#include "libradius.h" +#include +#include "conf.h" +#include "conffile.h" +#include "event.h" + +typedef struct auth_req REQUEST; + +#ifdef HAVE_PTHREAD_H +#include +#endif + +#ifndef NDEBUG +#define REQUEST_MAGIC (0xdeadbeef) +#endif + +/* + * New defines for minimizing the size of the server, to strip + * out functionality. In order to ensure that people don't have + * to re-run "configure", after "cvs update", we play some + * special games with the defines. i.e. any top-level "configure" + * option should set both WITH_FOO and WITHOUT_FOO. After a few + * weeks, the WITHOUT_FOO can be deleted from the configure script. + */ +#ifndef WITHOUT_PROXY +#define WITH_PROXY (1) +#endif + +#ifndef WITHOUT_DETAIL +#define WITH_DETAIL (1) +#endif + +#ifndef WITHOUT_SESSION_MGMT +#define WITH_SESSION_MGMT (1) +#endif + +#ifndef WITHOUT_UNLANG +#define WITH_UNLANG (1) +#endif + +#ifndef WITHOUT_ACCOUNTING +#define WITH_ACCOUNTING (1) +#else +#ifdef WITH_SESSION_MGMT +#error WITH_SESSION_MGMT is defined, but WITH_ACCOUNTING is not. Session management requires accounting. +#endif +#ifdef WITH_DETAIL +#error WITH_DETAIL is defined, but WITH_ACCOUNTING is not. Detail file reading requires accounting. +#endif +#endif + +#ifndef WITHOUT_DYNAMIC_CLIENTS +#define WITH_DYNAMIC_CLIENTS (1) +#endif + +#ifndef WITHOUT_STATS +#define WITH_STATS +#endif + +#ifndef WITHOUT_COMMAND_SOCKET +#ifdef HAVE_SYS_UN_H +#define WITH_COMMAND_SOCKET (1) +#endif +#endif + +#ifndef WITHOUT_COA +#define WITH_COA (1) +#ifndef WITH_PROXY +#error WITH_COA requires WITH_PROXY +#endif +#endif + +#include "stats.h" +#include "realms.h" + + +/* + * See util.c + */ +typedef struct request_data_t request_data_t; + +typedef struct radclient { + fr_ipaddr_t ipaddr; + int prefix; + char *longname; + char *secret; + char *shortname; + int message_authenticator; + char *nastype; + char *login; + char *password; + char *server; + int number; /* internal use only */ + const CONF_SECTION *cs; +#ifdef WITH_STATS + fr_stats_t *auth; +#ifdef WITH_ACCOUNTING + fr_stats_t *acct; +#endif +#endif + +#ifdef WITH_DYNAMIC_CLIENTS + int lifetime; + int dynamic; /* was dynamically defined */ + time_t created; + time_t last_new_client; + char *client_server; +#endif +} RADCLIENT; + +/* + * Types of listeners. + * + * Ordered by priority! + */ +typedef enum RAD_LISTEN_TYPE { + RAD_LISTEN_NONE = 0, +#ifdef WITH_PROXY + RAD_LISTEN_PROXY, +#endif + RAD_LISTEN_AUTH, +#ifdef WITH_ACCOUNTING + RAD_LISTEN_ACCT, +#endif +#ifdef WITH_DETAIL + RAD_LISTEN_DETAIL, +#endif +#ifdef WITH_VMPS + RAD_LISTEN_VQP, +#endif +#ifdef WITH_DHCP + RAD_LISTEN_DHCP, +#endif +#ifdef WITH_COMMAND_SOCKET + RAD_LISTEN_COMMAND, +#endif +#ifdef WITH_COA + RAD_LISTEN_COA, +#endif + RAD_LISTEN_MAX +} RAD_LISTEN_TYPE; + + +/* + * For listening on multiple IP's and ports. + */ +typedef struct rad_listen_t rad_listen_t; +typedef void (*radlog_func_t)(int, int, REQUEST *, const char *, ...); + +#define REQUEST_DATA_REGEX (0xadbeef00) +#define REQUEST_MAX_REGEX (8) + +struct auth_req { +#ifndef NDEBUG + uint32_t magic; /* for debugging only */ +#endif + RADIUS_PACKET *packet; +#ifdef WITH_PROXY + RADIUS_PACKET *proxy; +#endif + RADIUS_PACKET *reply; +#ifdef WITH_PROXY + RADIUS_PACKET *proxy_reply; +#endif + VALUE_PAIR *config_items; + VALUE_PAIR *username; + VALUE_PAIR *password; + + struct main_config_t *root; + + request_data_t *data; + RADCLIENT *client; +#ifdef HAVE_PTHREAD_H + pthread_t child_pid; +#endif + time_t timestamp; + int number; /* internal server number */ + + rad_listen_t *listener; +#ifdef WITH_PROXY + rad_listen_t *proxy_listener; +#endif + + + int simul_max; /* see modcall.c && xlat.c */ +#ifdef WITH_SESSION_MGMT + int simul_count; + int simul_mpp; /* WEIRD: 1 is false, 2 is true */ +#endif + + int options; /* miscellanous options */ + const char *module; /* for debugging unresponsive children */ + const char *component; /* ditto */ + + struct timeval received; + struct timeval when; /* to wake up */ + int delay; + + int master_state; + int child_state; + RAD_LISTEN_TYPE priority; + + fr_event_t *ev; + struct timeval next_when; + fr_event_callback_t next_callback; + + int in_request_hash; + + const char *server; + REQUEST *parent; + radlog_func_t radlog; /* logging function, if set */ +#ifdef WITH_COA + REQUEST *coa; + int num_coa_requests; +#endif +}; /* REQUEST typedef */ + +#define RAD_REQUEST_OPTION_NONE (0) +#define RAD_REQUEST_OPTION_DEBUG (1) +#define RAD_REQUEST_OPTION_DEBUG2 (2) +#define RAD_REQUEST_OPTION_DEBUG3 (3) +#define RAD_REQUEST_OPTION_DEBUG4 (4) + +#define REQUEST_ACTIVE (1) +#define REQUEST_STOP_PROCESSING (2) +#define REQUEST_COUNTED (3) + +#define REQUEST_QUEUED (1) +#define REQUEST_RUNNING (2) +#define REQUEST_PROXIED (3) +#define REQUEST_REJECT_DELAY (4) +#define REQUEST_CLEANUP_DELAY (5) +#define REQUEST_DONE (6) + +/* + * Function handler for requests. + */ +typedef int (*RAD_REQUEST_FUNP)(REQUEST *); + +typedef struct radclient_list RADCLIENT_LIST; + +typedef struct pair_list { + const char *name; + VALUE_PAIR *check; + VALUE_PAIR *reply; + int lineno; + int order; + struct pair_list *next; + struct pair_list *lastdefault; +} PAIR_LIST; + + +typedef int (*rad_listen_recv_t)(rad_listen_t *, RAD_REQUEST_FUNP *, REQUEST **); +typedef int (*rad_listen_send_t)(rad_listen_t *, REQUEST *); +typedef int (*rad_listen_print_t)(rad_listen_t *, char *, size_t); +typedef int (*rad_listen_encode_t)(rad_listen_t *, REQUEST *); +typedef int (*rad_listen_decode_t)(rad_listen_t *, REQUEST *); + +struct rad_listen_t { + struct rad_listen_t *next; /* should be rbtree stuff */ + + /* + * For normal sockets. + */ + RAD_LISTEN_TYPE type; + int fd; + const char *server; + int status; + + rad_listen_recv_t recv; + rad_listen_send_t send; + rad_listen_encode_t encode; + rad_listen_decode_t decode; + rad_listen_print_t print; + + void *data; + +#ifdef WITH_STATS + fr_stats_t stats; +#endif +}; + +#define RAD_LISTEN_STATUS_INIT (0) +#define RAD_LISTEN_STATUS_KNOWN (1) +#define RAD_LISTEN_STATUS_CLOSED (2) +#define RAD_LISTEN_STATUS_FINISH (3) + +typedef enum radlog_dest_t { + RADLOG_STDOUT = 0, + RADLOG_FILES, + RADLOG_SYSLOG, + RADLOG_STDERR, + RADLOG_NULL, + RADLOG_NUM_DEST +} radlog_dest_t; + +typedef struct main_config_t { + struct main_config *next; + int refcount; + fr_ipaddr_t myip; /* from the command-line only */ + int port; /* from the command-line only */ + int log_auth; + int log_auth_badpass; + int log_auth_goodpass; + int allow_core_dumps; + int debug_level; + int proxy_requests; + int reject_delay; + int status_server; + int max_request_time; + int cleanup_delay; + int max_requests; +#ifdef DELETE_BLOCKED_REQUESTS + int kill_unresponsive_children; +#endif + char *log_file; + char *checkrad; + const char *pid_file; + rad_listen_t *listen; + int syslog_facility; + int radlog_fd; + radlog_dest_t radlog_dest; + CONF_SECTION *config; + const char *name; + const char *auth_badpass_msg; + const char *auth_goodpass_msg; +} MAIN_CONFIG_T; + +#define DEBUG if(debug_flag)log_debug +#define DEBUG2 if (debug_flag > 1)log_debug +#define DEBUG3 if (debug_flag > 2)log_debug +#define DEBUG4 if (debug_flag > 3)log_debug + +#if __GNUC__ >= 3 +#define RDEBUG(fmt, ...) if(request && request->radlog) request->radlog(L_DBG, 1, request, fmt, ## __VA_ARGS__) +#define RDEBUG2(fmt, ...) if(request && request->radlog) request->radlog(L_DBG, 2, request, fmt, ## __VA_ARGS__) +#define RDEBUG3(fmt, ...) if(request && request->radlog) request->radlog(L_DBG, 3, request, fmt, ## __VA_ARGS__) +#define RDEBUG4(fmt, ...) if(request && request->radlog) request->radlog(L_DBG, 4, request, fmt, ## __VA_ARGS__) +#else +#define RDEBUG DEBUG +#define RDEBUG2 DEBUG2 +#define RDEBUG3 DEBUG3 +#define RDEBUG4 DEBUG4 +#endif + +#define SECONDS_PER_DAY 86400 +#define MAX_REQUEST_TIME 30 +#define CLEANUP_DELAY 5 +#define MAX_REQUESTS 256 +#define RETRY_DELAY 5 +#define RETRY_COUNT 3 +#define DEAD_TIME 120 + +#define L_DBG 1 +#define L_AUTH 2 +#define L_INFO 3 +#define L_ERR 4 +#define L_PROXY 5 +#define L_ACCT 6 +#define L_CONS 128 + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +/* + * This definition of true as NOT false is definitive. :) Making + * it '1' can cause problems on stupid platforms. See articles + * on C portability for more information. + */ +#define TRUE (!FALSE) +#endif + +/* for paircompare_register */ +typedef int (*RAD_COMPARE_FUNC)(void *instance, REQUEST *,VALUE_PAIR *, VALUE_PAIR *, VALUE_PAIR *, VALUE_PAIR **); + +typedef enum request_fail_t { + REQUEST_FAIL_UNKNOWN = 0, + REQUEST_FAIL_NO_THREADS, /* no threads to handle it */ + REQUEST_FAIL_DECODE, /* rad_decode didn't like it */ + REQUEST_FAIL_PROXY, /* call to proxy modules failed */ + REQUEST_FAIL_PROXY_SEND, /* proxy_send didn't like it */ + REQUEST_FAIL_NO_RESPONSE, /* we weren't told to respond, so we reject */ + REQUEST_FAIL_HOME_SERVER, /* the home server didn't respond */ + REQUEST_FAIL_HOME_SERVER2, /* another case of the above */ + REQUEST_FAIL_HOME_SERVER3, /* another case of the above */ + REQUEST_FAIL_NORMAL_REJECT, /* authentication failure */ + REQUEST_FAIL_SERVER_TIMEOUT /* the server took too long to process the request */ +} request_fail_t; + +/* + * Global variables. + * + * We really shouldn't have this many. + */ +extern const char *progname; +extern int debug_flag; +extern const char *radacct_dir; +extern const char *radlog_dir; +extern const char *radlib_dir; +extern const char *radius_dir; +extern const char *radius_libdir; +extern uint32_t expiration_seconds; +extern int log_stripped_names; +extern int log_auth_detail; +extern const char *radiusd_version; +void radius_signal_self(int flag); + +#define RADIUS_SIGNAL_SELF_NONE (0) +#define RADIUS_SIGNAL_SELF_HUP (1 << 0) +#define RADIUS_SIGNAL_SELF_TERM (1 << 1) +#define RADIUS_SIGNAL_SELF_EXIT (1 << 2) +#define RADIUS_SIGNAL_SELF_DETAIL (1 << 3) +#define RADIUS_SIGNAL_SELF_NEW_FD (1 << 4) +#define RADIUS_SIGNAL_SELF_MAX (1 << 5) + + +/* + * Function prototypes. + */ + +/* acct.c */ +int rad_accounting(REQUEST *); + +/* session.c */ +int rad_check_ts(uint32_t nasaddr, unsigned int port, const char *user, + const char *sessionid); +int session_zap(REQUEST *request, uint32_t nasaddr, + unsigned int port, const char *user, + const char *sessionid, uint32_t cliaddr, + char proto,int session_time); + +/* radiusd.c */ +#undef debug_pair +void debug_pair(VALUE_PAIR *); +void debug_pair_list(VALUE_PAIR *); +int log_err (char *); + +/* util.c */ +void (*reset_signal(int signo, void (*func)(int)))(int); +void request_free(REQUEST **request); +int rad_mkdir(char *directory, int mode); +int rad_checkfilename(const char *filename); +void *rad_malloc(size_t size); /* calls exit(1) on error! */ +REQUEST *request_alloc(void); +REQUEST *request_alloc_fake(REQUEST *oldreq); +REQUEST *request_alloc_coa(REQUEST *request); +int request_data_add(REQUEST *request, + void *unique_ptr, int unique_int, + void *opaque, void (*free_opaque)(void *)); +void *request_data_get(REQUEST *request, + void *unique_ptr, int unique_int); +void *request_data_reference(REQUEST *request, + void *unique_ptr, int unique_int); +int rad_copy_string(char *dst, const char *src); +int rad_copy_variable(char *dst, const char *from); + +/* client.c */ +RADCLIENT_LIST *clients_init(void); +void clients_free(RADCLIENT_LIST *clients); +RADCLIENT_LIST *clients_parse_section(CONF_SECTION *section); +void client_free(RADCLIENT *client); +int client_add(RADCLIENT_LIST *clients, RADCLIENT *client); +#ifdef WITH_DYNAMIC_CLIENTS +void client_delete(RADCLIENT_LIST *clients, RADCLIENT *client); +RADCLIENT *client_create(RADCLIENT_LIST *clients, REQUEST *request); +#endif +RADCLIENT *client_find(const RADCLIENT_LIST *clients, + const fr_ipaddr_t *ipaddr); +RADCLIENT *client_findbynumber(const RADCLIENT_LIST *clients, + int number); +RADCLIENT *client_find_old(const fr_ipaddr_t *ipaddr); +int client_validate(RADCLIENT_LIST *clients, RADCLIENT *master, + RADCLIENT *c); +RADCLIENT *client_read(const char *filename, int in_server, int flag); + + +/* files.c */ +int pairlist_read(const char *file, PAIR_LIST **list, int complain); +void pairlist_free(PAIR_LIST **); + +/* version.c */ +void version(void); + +/* log.c */ +int vradlog(int, const char *, va_list ap); +int radlog(int, const char *, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif +; +int log_debug(const char *, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; +void vp_listdebug(VALUE_PAIR *vp); +void radlog_request(int lvl, int priority, REQUEST *request, const char *msg, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 4, 5))) +#endif +; + +/* auth.c */ +char *auth_name(char *buf, size_t buflen, REQUEST *request, int do_cli); +int rad_authenticate (REQUEST *); +int rad_postauth(REQUEST *); + +/* exec.c */ +int radius_exec_program(const char *, REQUEST *, int, + char *user_msg, int msg_len, + VALUE_PAIR *input_pairs, + VALUE_PAIR **output_pairs, + int shell_escape); + +/* timestr.c */ +int timestr_match(char *, time_t); + +/* valuepair.c */ +int paircompare_register(int attr, int otherattr, + RAD_COMPARE_FUNC func, + void *instance); +void paircompare_unregister(int attr, RAD_COMPARE_FUNC func); +int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, + VALUE_PAIR **reply); +void pairxlatmove(REQUEST *, VALUE_PAIR **to, VALUE_PAIR **from); +int radius_compare_vps(REQUEST *request, VALUE_PAIR *check, VALUE_PAIR *vp); +int radius_callback_compare(REQUEST *req, VALUE_PAIR *request, + VALUE_PAIR *check, VALUE_PAIR *check_pairs, + VALUE_PAIR **reply_pairs); +int radius_find_compare(int attribute); +VALUE_PAIR *radius_paircreate(REQUEST *request, VALUE_PAIR **vps, + int attribute, int type); +VALUE_PAIR *radius_pairmake(REQUEST *request, VALUE_PAIR **vps, + const char *attribute, const char *value, + int _operator); + +/* xlat.c */ +typedef size_t (*RADIUS_ESCAPE_STRING)(char *out, size_t outlen, const char *in); + +int radius_xlat(char * out, int outlen, const char *fmt, + REQUEST * request, RADIUS_ESCAPE_STRING func); +typedef size_t (*RAD_XLAT_FUNC)(void *instance, REQUEST *, char *, char *, size_t, RADIUS_ESCAPE_STRING func); +int xlat_register(const char *module, RAD_XLAT_FUNC func, + void *instance); +void xlat_unregister(const char *module, RAD_XLAT_FUNC func); +void xlat_free(void); + +/* threads.c */ +extern int thread_pool_init(CONF_SECTION *cs, int *spawn_flag); +extern int thread_pool_addrequest(REQUEST *, RAD_REQUEST_FUNP); +extern pid_t rad_fork(void); +extern pid_t rad_waitpid(pid_t pid, int *status); +extern int total_active_threads(void); +extern void thread_pool_lock(void); +extern void thread_pool_unlock(void); +extern void thread_pool_queue_stats(int *array); + +#ifndef HAVE_PTHREAD_H +#define rad_fork(n) fork() +#define rad_waitpid(a,b) waitpid(a,b, 0) +#endif + +/* mainconfig.c */ +/* Define a global config structure */ +extern struct main_config_t mainconfig; + +int read_mainconfig(int reload); +int free_mainconfig(void); +void hup_mainconfig(void); +void fr_suid_down(void); +void fr_suid_up(void); +void fr_suid_down_permanent(void); + +/* listen.c */ +void listen_free(rad_listen_t **head); +int listen_init(CONF_SECTION *cs, rad_listen_t **head); +rad_listen_t *proxy_new_listener(fr_ipaddr_t *ipaddr, int exists); +RADCLIENT *client_listener_find(const rad_listen_t *listener, + const fr_ipaddr_t *ipaddr, int src_port); +#ifdef WITH_STATS +RADCLIENT_LIST *listener_find_client_list(const fr_ipaddr_t *ipaddr, + int port); +rad_listen_t *listener_find_byipaddr(const fr_ipaddr_t *ipaddr, int port); +#endif + +/* event.c */ +int radius_event_init(CONF_SECTION *cs, int spawn_flag); +void radius_event_free(void); +int radius_event_process(void); +void radius_handle_request(REQUEST *request, RAD_REQUEST_FUNP fun); +int received_request(rad_listen_t *listener, + RADIUS_PACKET *packet, REQUEST **prequest, + RADCLIENT *client); +REQUEST *received_proxy_response(RADIUS_PACKET *packet); +void event_new_fd(rad_listen_t *listener); + +/* evaluate.c */ +int radius_evaluate_condition(REQUEST *request, int modreturn, int depth, + const char **ptr, int evaluate_it, int *presult); +int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs, + VALUE_PAIR *input_vps, const char *name); +void radius_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from); +#endif /*RADIUSD_H*/ diff --git a/projects/rlm_stg/realms.h b/projects/rlm_stg/realms.h new file mode 100644 index 00000000..6b03bcd5 --- /dev/null +++ b/projects/rlm_stg/realms.h @@ -0,0 +1,142 @@ +#ifndef REALMS_H +#define REALMS_H + +/* + * realms.h Structures, prototypes and global variables + * for realms + * + * Version: $Id: realms.h,v 1.1 2010/08/14 04:13:52 faust Exp $ + * + */ + +#include +RCSIDH(realms_h, "$Id: realms.h,v 1.1 2010/08/14 04:13:52 faust Exp $") + +#define HOME_TYPE_INVALID (0) +#define HOME_TYPE_AUTH (1) +#define HOME_TYPE_ACCT (2) +#ifdef WITH_COA +#define HOME_TYPE_COA (3) +#endif + +#define HOME_PING_CHECK_NONE (0) +#define HOME_PING_CHECK_STATUS_SERVER (1) +#define HOME_PING_CHECK_REQUEST (2) + +#define HOME_STATE_ALIVE (0) +#define HOME_STATE_ZOMBIE (1) +#define HOME_STATE_IS_DEAD (2) + +typedef struct home_server { + const char *name; + + const char *hostname; + const char *server; /* for internal proxying */ + + fr_ipaddr_t ipaddr; + + int port; + int type; /* auth/acct */ + + /* + * Maybe also have list of source IP/ports, && socket? + */ + + const char *secret; + + fr_event_t *ev; + struct timeval when; + + int response_window; + int no_response_fail; + int max_outstanding; /* don't overload it */ + int currently_outstanding; + int message_authenticator; + + struct timeval revive_time; + struct timeval zombie_period_start; + int zombie_period; /* unresponsive for T, mark it dead */ + + int state; + + int ping_check; + const char *ping_user_name; + const char *ping_user_password; + + int ping_interval; + int num_pings_to_alive; + int num_received_pings; + int ping_timeout; + + int revive_interval; /* if it doesn't support pings */ + CONF_SECTION *cs; +#ifdef WITH_COA + int coa_irt; + int coa_mrc; + int coa_mrt; + int coa_mrd; +#endif +#ifdef WITH_STATS + int number; + + fr_ipaddr_t src_ipaddr; /* preferred source IP address */ + + fr_stats_t stats; + + fr_stats_ema_t ema; +#endif +} home_server; + + +typedef enum home_pool_type_t { + HOME_POOL_INVALID = 0, + HOME_POOL_LOAD_BALANCE, + HOME_POOL_FAIL_OVER, + HOME_POOL_CLIENT_BALANCE, + HOME_POOL_CLIENT_PORT_BALANCE, + HOME_POOL_KEYED_BALANCE +} home_pool_type_t; + + +typedef struct home_pool_t { + const char *name; + home_pool_type_t type; + + int server_type; + CONF_SECTION *cs; + + const char *virtual_server; /* for pre/post-proxy */ + + home_server *fallback; + + int num_home_servers; + home_server *servers[1]; +} home_pool_t; + + +typedef struct _realm { + const char *name; + + int striprealm; + + home_pool_t *auth_pool; + home_pool_t *acct_pool; +} REALM; + +int realms_init(CONF_SECTION *config); +void realms_free(void); +REALM *realm_find(const char *name); /* name is from a packet */ +REALM *realm_find2(const char *name); /* ... with name taken from realm_find */ + +home_server *home_server_ldb(const char *realmname, home_pool_t *pool, REQUEST *request); +home_server *home_server_find(fr_ipaddr_t *ipaddr, int port); +int home_server_create_listeners(void *head); +#ifdef WITH_COA +home_server *home_server_byname(const char *name, int type); +#endif +#ifdef WITH_STATS +home_server *home_server_bynumber(int number); +#endif +home_pool_t *home_pool_byname(const char *name, int type); + +#endif /* REALMS_H */ diff --git a/projects/rlm_stg/rlm_stg.cpp b/projects/rlm_stg/rlm_stg.cpp new file mode 100644 index 00000000..f3860cf4 --- /dev/null +++ b/projects/rlm_stg/rlm_stg.cpp @@ -0,0 +1,348 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * FreeRADIUS module for data access via Stargazer + * + * $Revision: 1.8 $ + * $Date: 2010/08/14 04:15:08 $ + * + */ + +#include +#include +#include + +extern "C" { +//#include +#include "radiusd.h" +#include "modules.h" +//#include +} + +#include "stg_client.h" +#include "common.h" + +STG_CLIENT * cli; +volatile time_t stgTime; + +/* + * Define a structure for our module configuration. + * + * These variables do not need to be in a structure, but it's + * a lot cleaner to do so, and a pointer to the structure can + * be used as the instance handle. + */ +typedef struct rlm_stg_t { + char * server; + char * password; + uint32_t port; + uint32_t localPort; +} rlm_stg_t; + +/* + * A mapping of configuration file names to internal variables. + * + * Note that the string is dynamically allocated, so it MUST + * be freed. When the configuration file parse re-reads the string, + * it free's the old one, and strdup's the new one, placing the pointer + * to the strdup'd string into 'config.string'. This gets around + * buffer over-flows. + */ +static CONF_PARSER module_config[] = { + { "password", PW_TYPE_STRING_PTR, offsetof(rlm_stg_t,password), NULL, NULL}, + { "server", PW_TYPE_STRING_PTR, offsetof(rlm_stg_t,server), NULL, NULL}, + { "port", PW_TYPE_INTEGER, offsetof(rlm_stg_t,port), NULL, "5555" }, + { "local_port", PW_TYPE_INTEGER, offsetof(rlm_stg_t,localPort), NULL, "0" }, + + { NULL, -1, 0, NULL, NULL } /* end the list */ +}; + +/* + * Do any per-module initialization that is separate to each + * configured instance of the module. e.g. set up connections + * to external databases, read configuration files, set up + * dictionary entries, etc. + * + * If configuration information is given in the config section + * that must be referenced in later calls, store a handle to it + * in *instance otherwise put a null pointer there. + */ +static int stg_instantiate(CONF_SECTION *conf, void **instance) +{ + rlm_stg_t *data; + + /* + * Set up a storage area for instance data + */ + DEBUG("rlm_stg: stg_instantiate()"); + data = (rlm_stg_t *)rad_malloc(sizeof(rlm_stg_t)); + if (!data) { + return -1; + } + memset(data, 0, sizeof(rlm_stg_t)); + + /* + * If the configuration parameters can't be parsed, then + * fail. + */ + if (cf_section_parse(conf, data, module_config) < 0) { + free(data); + return -1; + } + + cli = new STG_CLIENT(); + cli->SetServer(data->server); + cli->SetPort(data->port); + cli->SetLocalPort(data->localPort); + cli->SetPassword(data->password); + if (cli->Start()) { + DEBUG("rlm_stg: stg_instantiate() error: '%s'", cli->GetError().c_str()); + return -1; + } + + *instance = data; + + return 0; +} + +/* + * Find the named user in this modules database. Create the set + * of attribute-value pairs to check and reply with for this user + * from the database. The authentication code only needs to check + * the password, the rest is done here. + */ +static int stg_authorize(void *instance, REQUEST *request) +{ + VALUE_PAIR *uname; + VALUE_PAIR *pwd; + VALUE_PAIR *svc; + DEBUG("rlm_stg: stg_authorize()"); + + /* quiet the compiler */ + instance = instance; + request = request; + + uname = pairfind(request->packet->vps, PW_USER_NAME); + if (uname) { + DEBUG("rlm_stg: stg_authorize() user name defined as '%s'", uname->vp_strvalue); + } else { + DEBUG("rlm_stg: stg_authorize() user name undefined"); + return RLM_MODULE_FAIL; + } + if (request->username) { + DEBUG("rlm_stg: stg_authorize() request username field: '%s'", request->username->vp_strvalue); + } + if (request->password) { + DEBUG("rlm_stg: stg_authorize() request password field: '%s'", request->password->vp_strvalue); + } + // Here we need to define Framed-Protocol + svc = pairfind(request->packet->vps, PW_SERVICE_TYPE); + if (svc) { + DEBUG("rlm_stg: stg_authorize() Service-Type defined as '%s'", svc->vp_strvalue); + if (cli->Authorize((const char *)request->username->vp_strvalue, (const char *)svc->vp_strvalue)) { + DEBUG("rlm_stg: stg_authorize() stg status: '%s'", cli->GetError().c_str()); + return RLM_MODULE_REJECT; + } + } else { + DEBUG("rlm_stg: stg_authorize() Service-Type undefined"); + if (cli->Authorize((const char *)request->username->vp_strvalue, "")) { + DEBUG("rlm_stg: stg_authorize() stg status: '%s'", cli->GetError().c_str()); + return RLM_MODULE_REJECT; + } + } + pwd = pairmake("Cleartext-Password", cli->GetUserPassword().c_str(), T_OP_SET); + pairadd(&request->config_items, pwd); + //pairadd(&request->reply->vps, uname); + + return RLM_MODULE_UPDATED; +} + +/* + * Authenticate the user with the given password. + */ +static int stg_authenticate(void *instance, REQUEST *request) +{ + /* quiet the compiler */ + VALUE_PAIR *svc; + instance = instance; + request = request; + DEBUG("rlm_stg: stg_authenticate()"); + svc = pairfind(request->packet->vps, PW_SERVICE_TYPE); + if (svc) { + DEBUG("rlm_stg: stg_authenticate() Service-Type defined as '%s'", svc->vp_strvalue); + if (cli->Authenticate((char *)request->username->vp_strvalue, (const char *)svc->vp_strvalue)) { + DEBUG("rlm_stg: stg_authenticate() stg status: '%s'", cli->GetError().c_str()); + return RLM_MODULE_REJECT; + } + } else { + DEBUG("rlm_stg: stg_authenticate() Service-Type undefined"); + if (cli->Authenticate((char *)request->username->vp_strvalue, "")) { + DEBUG("rlm_stg: stg_authenticate() stg status: '%s'", cli->GetError().c_str()); + return RLM_MODULE_REJECT; + } + } + + return RLM_MODULE_NOOP; +} + +/* + * Massage the request before recording it or proxying it + */ +static int stg_preacct(void *instance, REQUEST *request) +{ + /* quiet the compiler */ + instance = instance; + request = request; + DEBUG("rlm_stg: stg_preacct()"); + + return RLM_MODULE_OK; +} + +/* + * Write accounting information to this modules database. + */ +static int stg_accounting(void *instance, REQUEST *request) +{ + /* quiet the compiler */ + VALUE_PAIR * sttype; + VALUE_PAIR * svc; + VALUE_PAIR * sessid; + svc = pairfind(request->packet->vps, PW_SERVICE_TYPE); + instance = instance; + request = request; + DEBUG("rlm_stg: stg_accounting()"); + + sessid = pairfind(request->packet->vps, PW_ACCT_SESSION_ID); + if (!sessid) { + DEBUG("rlm_stg: stg_accounting() Acct-Session-ID undefined"); + return RLM_MODULE_FAIL; + } + sttype = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE); + if (sttype) { + DEBUG("Acct-Status-Type := %s", sttype->vp_strvalue); + if (svc) { + DEBUG("rlm_stg: stg_accounting() Service-Type defined as '%s'", svc->vp_strvalue); + if (cli->Account((const char *)sttype->vp_strvalue, (const char *)request->username->vp_strvalue, (const char *)svc->vp_strvalue, (const char *)sessid->vp_strvalue)) { + DEBUG("rlm_stg: stg_accounting error: '%s'", cli->GetError().c_str()); + return RLM_MODULE_FAIL; + } + } else { + DEBUG("rlm_stg: stg_accounting() Service-Type undefined"); + if (cli->Account((const char *)sttype->vp_strvalue, (const char *)request->username->vp_strvalue, "", (const char *)sessid->vp_strvalue)) { + DEBUG("rlm_stg: stg_accounting error: '%s'", cli->GetError().c_str()); + return RLM_MODULE_FAIL; + } + } + } else { + DEBUG("Acct-Status-Type := NULL"); + } + + return RLM_MODULE_OK; +} + +/* + * See if a user is already logged in. Sets request->simul_count to the + * current session count for this user and sets request->simul_mpp to 2 + * if it looks like a multilink attempt based on the requested IP + * address, otherwise leaves request->simul_mpp alone. + * + * Check twice. If on the first pass the user exceeds his + * max. number of logins, do a second pass and validate all + * logins by querying the terminal server (using eg. SNMP). + */ +static int stg_checksimul(void *instance, REQUEST *request) +{ + instance = instance; + DEBUG("rlm_stg: stg_checksimul()"); + + request->simul_count=0; + + return RLM_MODULE_OK; +} + +static int stg_postauth(void *instance, REQUEST *request) +{ + instance = instance; + VALUE_PAIR *fia; + VALUE_PAIR *svc; + struct in_addr fip; + DEBUG("rlm_stg: stg_postauth()"); + svc = pairfind(request->packet->vps, PW_SERVICE_TYPE); + if (svc) { + DEBUG("rlm_stg: stg_postauth() Service-Type defined as '%s'", svc->vp_strvalue); + if (cli->PostAuthenticate((const char *)request->username->vp_strvalue, (const char *)svc->vp_strvalue)) { + DEBUG("rlm_stg: stg_postauth() error: '%s'", cli->GetError().c_str()); + return RLM_MODULE_FAIL; + } + } else { + DEBUG("rlm_stg: stg_postauth() Service-Type undefined"); + if (cli->PostAuthenticate((const char *)request->username->vp_strvalue, "")) { + DEBUG("rlm_stg: stg_postauth() error: '%s'", cli->GetError().c_str()); + return RLM_MODULE_FAIL; + } + } + if (strncmp((const char *)svc->vp_strvalue, "Framed-User", 11) == 0) { + fip.s_addr = cli->GetFramedIP(); + DEBUG("rlm_stg: stg_postauth() ip = '%s'", inet_ntostring(fip.s_addr).c_str()); + fia = pairmake("Framed-IP-Address", inet_ntostring(fip.s_addr).c_str(), T_OP_SET); + pairadd(&request->reply->vps, fia); + } + + return RLM_MODULE_UPDATED; +} + +static int stg_detach(void *instance) +{ + DEBUG("rlm_stg: stg_detach()"); + cli->Stop(); + delete cli; + free(((struct rlm_stg_t *)instance)->server); + free(((struct rlm_stg_t *)instance)->password); + free(instance); + return 0; +} + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + * + * If the module needs to temporarily modify it's instantiation + * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE. + * The server will then take care of ensuring that the module + * is single-threaded. + */ +module_t rlm_stg = { + RLM_MODULE_INIT, + "stg", + RLM_TYPE_THREAD_SAFE, /* type */ + stg_instantiate, /* instantiation */ + stg_detach, /* detach */ + { + stg_authenticate, /* authentication */ + stg_authorize, /* authorization */ + stg_preacct, /* preaccounting */ + stg_accounting, /* accounting */ + stg_checksimul, /* checksimul */ + NULL, /* pre-proxy */ + NULL, /* post-proxy */ + stg_postauth /* post-auth */ + }, +}; diff --git a/projects/rlm_stg/stats.h b/projects/rlm_stg/stats.h new file mode 100644 index 00000000..f584cba6 --- /dev/null +++ b/projects/rlm_stg/stats.h @@ -0,0 +1,104 @@ +#ifndef FR_STATS_H +#define FR_STATS_H + +/* + * stats.h Structures and functions for statistics. + * + * Version: $Id: stats.h,v 1.1 2010/08/14 04:13:52 faust Exp $ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2005,2006,2007,2008 The FreeRADIUS server project + */ + +#include +RCSIDH(stats_h, "$Id: stats.h,v 1.1 2010/08/14 04:13:52 faust Exp $") + +#ifdef WITH_STATS_64BIT +typedef uint64_t fr_uint_t; +#else +typedef uint32_t fr_uint_t; +#endif + +#ifdef WITH_STATS +typedef struct fr_stats_t { + fr_uint_t total_requests; + fr_uint_t total_invalid_requests; + fr_uint_t total_dup_requests; + fr_uint_t total_responses; + fr_uint_t total_access_accepts; + fr_uint_t total_access_rejects; + fr_uint_t total_access_challenges; + fr_uint_t total_malformed_requests; + fr_uint_t total_bad_authenticators; + fr_uint_t total_packets_dropped; + fr_uint_t total_no_records; + fr_uint_t total_unknown_types; +} fr_stats_t; + +typedef struct fr_stats_ema_t { + int window; + + int f1, f10; + int ema1, ema10; + +} fr_stats_ema_t; + +extern fr_stats_t radius_auth_stats; +extern fr_stats_t radius_acct_stats; +#ifdef WITH_PROXY +extern fr_stats_t proxy_auth_stats; +extern fr_stats_t proxy_acct_stats; +#endif + +void radius_stats_init(int flag); +void request_stats_final(REQUEST *request); +void request_stats_reply(REQUEST *request); +void radius_stats_ema(fr_stats_ema_t *ema, + struct timeval *start, struct timeval *end); + +#define RAD_STATS_INC(_x) _x++ +#ifdef WITH_ACCOUNTING +#define RAD_STATS_TYPE_INC(_listener, _x) if (_listener->type == RAD_LISTEN_AUTH) { \ + radius_auth_stats._x++; \ + } else if (_listener->type == RAD_LISTEN_ACCT) { \ + radius_acct_stats._x++; } \ + _listener->stats._x++ + +#define RAD_STATS_CLIENT_INC(_listener, _client, _x) if (_listener->type == RAD_LISTEN_AUTH) \ + _client->auth->_x++; \ + else if (_listener->type == RAD_LISTEN_ACCT) \ + _client->acct->_x++ + +#else /* WITH_ACCOUNTING */ + +#define RAD_STATS_TYPE_INC(_listener, _x) { radius_auth_stats._x++; _listener->stats._x++; } + +#define RAD_STATS_CLIENT_INC(_listener, _client, _x) _client->auth->_x++ + +#endif /* WITH_ACCOUNTING */ + + +#else /* WITH_STATS */ +#define request_stats_init(_x) +#define request_stats_final(_x) + +#define RAD_STATS_INC(_x) +#define RAD_STATS_TYPE_INC(_listener, _x) +#define RAD_STATS_CLIENT_INC(_listener, _client, _x) + +#endif + +#endif /* FR_STATS_H */ diff --git a/projects/rlm_stg/stg_client.cpp b/projects/rlm_stg/stg_client.cpp new file mode 100644 index 00000000..7be0a55e --- /dev/null +++ b/projects/rlm_stg/stg_client.cpp @@ -0,0 +1,320 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Realization of data access via Stargazer for RADIUS + * + * $Revision: 1.8 $ + * $Date: 2010/04/16 12:30:02 $ + * + */ + +#include +#include +#include // close +#include + +#include "stg_client.h" + +using namespace std; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +STG_CLIENT::STG_CLIENT() + : port(0), + localPort(0), + sock(0) +{ +} +//----------------------------------------------------------------------------- +STG_CLIENT::~STG_CLIENT() +{ +} +//----------------------------------------------------------------------------- +void STG_CLIENT::SetServer(const string & host) +{ +STG_CLIENT::host = host; +} +//----------------------------------------------------------------------------- +void STG_CLIENT::SetPort(uint16_t port) +{ +STG_CLIENT::port = port; +} +//----------------------------------------------------------------------------- +void STG_CLIENT::SetLocalPort(uint16_t port) +{ +STG_CLIENT::localPort = port; +} +//----------------------------------------------------------------------------- +void STG_CLIENT::SetPassword(const string & password) +{ +STG_CLIENT::password = password; +} +//----------------------------------------------------------------------------- +uint32_t STG_CLIENT::GetFramedIP() const +{ +return framedIP; +} +//----------------------------------------------------------------------------- +void STG_CLIENT::InitEncrypt() +{ +unsigned char keyL[RAD_PASSWORD_LEN]; +memset(keyL, 0, RAD_PASSWORD_LEN); +strncpy((char *)keyL, password.c_str(), RAD_PASSWORD_LEN); +Blowfish_Init(&ctx, keyL, RAD_PASSWORD_LEN); +} +//----------------------------------------------------------------------------- +int STG_CLIENT::PrepareNet() +{ +sock = socket(AF_INET, SOCK_DGRAM, 0); +if (sock == -1) + { + errorStr = "Socket create error"; + return -1; + } + +struct hostent * he = NULL; +he = gethostbyname(host.c_str()); +if (he == NULL) + { + errorStr = "gethostbyname error"; + return -1; + } + +if (localPort != 0) + { + struct sockaddr_in localAddr; + localAddr.sin_family = AF_INET; + localAddr.sin_port = htons(localPort); + localAddr.sin_addr.s_addr = inet_addr("0.0.0.0");; + + if (bind(sock, (struct sockaddr *)&localAddr, sizeof(localAddr))) + { + errorStr = "Bind failed"; + return -1; + } + } + +outerAddr.sin_family = AF_INET; +outerAddr.sin_port = htons(port); +outerAddr.sin_addr.s_addr = *(uint32_t *)he->h_addr; + +outerAddrLen = sizeof(struct sockaddr_in); + +return 0; +} +//----------------------------------------------------------------------------- +void STG_CLIENT::FinalizeNet() +{ +close(sock); +} +//----------------------------------------------------------------------------- +int STG_CLIENT::Start() +{ +InitEncrypt(); + +return PrepareNet(); +} +//----------------------------------------------------------------------------- +int STG_CLIENT::Stop() +{ +FinalizeNet(); + +return 0; +} +//----------------------------------------------------------------------------- +string STG_CLIENT::GetUserPassword() const +{ +return userPassword; +} +//----------------------------------------------------------------------------- +int STG_CLIENT::Send(const RAD_PACKET & packet) +{ +char buf[RAD_MAX_PACKET_LEN]; + +Encrypt(buf, (char *)&packet, sizeof(RAD_PACKET) / 8); + +int res = sendto(sock, buf, sizeof(RAD_PACKET), 0, (struct sockaddr *)&outerAddr, outerAddrLen); + +if (res == -1) + errorStr = "Error sending data"; + +return res; +} +//----------------------------------------------------------------------------- +int STG_CLIENT::RecvData(RAD_PACKET * packet) +{ +char buf[RAD_MAX_PACKET_LEN]; +int res; + +outerAddrLen = sizeof(struct sockaddr_in); + +res = recvfrom(sock, buf, RAD_MAX_PACKET_LEN, 0, (struct sockaddr *)&outerAddr, &outerAddrLen); +if (res == -1) + { + errorStr = "Error receiving data"; + return -1; + } + +Decrypt((char *)packet, buf, res / 8); + +return 0; +} +//----------------------------------------------------------------------------- +int STG_CLIENT::Request(RAD_PACKET * packet, const std::string & login, const std::string & svc, uint8_t packetType) +{ +int res; + +memcpy((void *)&packet->magic, (void *)RAD_ID, RAD_MAGIC_LEN); +packet->protoVer[0] = '0'; +packet->protoVer[1] = '1'; +packet->packetType = packetType; +packet->ip = 0; +strncpy((char *)packet->login, login.c_str(), RAD_LOGIN_LEN); +strncpy((char *)packet->service, svc.c_str(), RAD_SERVICE_LEN); + +res = Send(*packet); +if (res == -1) + return -1; + +res = RecvData(packet); +if (res == -1) + return -1; + +if (strncmp((char *)packet->magic, RAD_ID, RAD_MAGIC_LEN)) + { + errorStr = "Magic invalid. Wanted: '"; + errorStr += RAD_ID; + errorStr += "', got: '"; + errorStr += (char *)packet->magic; + errorStr += "'"; + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int STG_CLIENT::Authorize(const string & login, const string & svc) +{ +RAD_PACKET packet; + +userPassword = ""; + +if (Request(&packet, login, svc, RAD_AUTZ_PACKET)) + return -1; + +if (packet.packetType != RAD_ACCEPT_PACKET) + return -1; + +userPassword = (char *)packet.password; + +return 0; +} +//----------------------------------------------------------------------------- +int STG_CLIENT::Authenticate(const string & login, const string & svc) +{ +RAD_PACKET packet; + +userPassword = ""; + +if (Request(&packet, login, svc, RAD_AUTH_PACKET)) + return -1; + +if (packet.packetType != RAD_ACCEPT_PACKET) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +int STG_CLIENT::PostAuthenticate(const string & login, const string & svc) +{ +RAD_PACKET packet; + +userPassword = ""; + +if (Request(&packet, login, svc, RAD_POST_AUTH_PACKET)) + return -1; + +if (packet.packetType != RAD_ACCEPT_PACKET) + return -1; + +if (svc == "Framed-User") + framedIP = packet.ip; +else + framedIP = 0; + +return 0; +} +//----------------------------------------------------------------------------- +int STG_CLIENT::Account(const std::string & type, const string & login, const string & svc, const string & sessid) +{ +RAD_PACKET packet; + +userPassword = ""; +strncpy((char *)packet.sessid, sessid.c_str(), RAD_SESSID_LEN); + +if (type == "Start") + { + if (Request(&packet, login, svc, RAD_ACCT_START_PACKET)) + return -1; + } +else if (type == "Stop") + { + if (Request(&packet, login, svc, RAD_ACCT_STOP_PACKET)) + return -1; + } +else if (type == "Interim-Update") + { + if (Request(&packet, login, svc, RAD_ACCT_UPDATE_PACKET)) + return -1; + } +else + { + if (Request(&packet, login, svc, RAD_ACCT_OTHER_PACKET)) + return -1; + } + +if (packet.packetType != RAD_ACCEPT_PACKET) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +void STG_CLIENT::Encrypt(char * dst, const char * src, int len8) +{ +// len8 - длина в 8-ми байтовых блоках +if (dst != src) + memcpy(dst, src, len8 * 8); + +for (int i = 0; i < len8; i++) + Blowfish_Encrypt(&ctx, (uint32_t *)(dst + i*8), (uint32_t *)(dst + i*8 + 4)); +} +//----------------------------------------------------------------------------- +void STG_CLIENT::Decrypt(char * dst, const char * src, int len8) +{ +// len8 - длина в 8-ми байтовых блоках +if (dst != src) + memcpy(dst, src, len8 * 8); + +for (int i = 0; i < len8; i++) + Blowfish_Decrypt(&ctx, (uint32_t *)(dst + i*8), (uint32_t *)(dst + i*8 + 4)); +} +//----------------------------------------------------------------------------- diff --git a/projects/rlm_stg/stg_client.h b/projects/rlm_stg/stg_client.h new file mode 100644 index 00000000..60ed6af8 --- /dev/null +++ b/projects/rlm_stg/stg_client.h @@ -0,0 +1,98 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Header file for client part of data access via Stargazer for RADIUS + * + * $Revision: 1.4 $ + * $Date: 2010/04/16 12:30:02 $ + * + */ + +#ifndef STG_CLIENT_H +#define STG_CLIENT_H + +#include + +#include +#include +#include // socklen_t + +#include "blowfish.h" +#include "rad_packets.h" + +class STG_CLIENT +{ +public: + STG_CLIENT(); + ~STG_CLIENT(); + + void SetServer(const std::string & host); + void SetPort(uint16_t port); + void SetLocalPort(uint16_t port); + void SetPassword(const std::string & password); + + int Start(); + int Stop(); + + std::string GetUserPassword() const; + + int Authorize(const std::string & login, const std::string & svc); + int Authenticate(const std::string & login, const std::string & svc); + int PostAuthenticate(const std::string & login, const std::string & svc); + int Account(const std::string & type, const std::string & login, const std::string & svc, const std::string & sessid); + + uint32_t GetFramedIP() const; + + + const std::string & GetError() const { return errorStr; }; + +private: + std::string host; + uint16_t port; + uint16_t localPort; + std::string password; + int sock; + std::string errorStr; + + struct sockaddr_in outerAddr; + socklen_t outerAddrLen; + + std::string userPassword; + + uint32_t framedIP; + + BLOWFISH_CTX ctx; + + int PrepareNet(); + void FinalizeNet(); + + void InitEncrypt(); + void Encrypt(char * dst, const char * src, int len8); + void Decrypt(char * dst, const char * src, int len8); + + int Request(RAD_PACKET * packet, const std::string & login, const std::string & svc, uint8_t packetType); + + int RecvData(RAD_PACKET * packet); + int Send(const RAD_PACKET & packet); + +}; + +#endif diff --git a/projects/rscriptd/Makefile b/projects/rscriptd/Makefile new file mode 100644 index 00000000..03e95fa7 --- /dev/null +++ b/projects/rscriptd/Makefile @@ -0,0 +1,94 @@ +############################################################################### +# $Id: Makefile,v 1.18 2010/02/11 12:22:01 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +PROG = rscriptd + +SRCS = ./main.cpp \ + ./listener.cpp \ + ./pidfile.cpp + +STGLIBS = -lstg_logger \ + -lstg_common \ + -lstg_crypto \ + -lscript_executer \ + -lconffiles + +LIBS += $(LIB_THREAD) + +#ifeq ($(OS),linux) +#LIBS += -ldl +#else +#LIBS += -lc +#endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +CXXFLAGS += -Wall +LDFLAGS += -Wl,-E -L$(DIR_LIB) -Wl,-rpath,$(PREFIX)/usr/lib/stg -Wl,-rpath-link,$(DIR_LIB) + +vpath %.so $(DIR_LIB) + +.PHONY: all clean distclean libs install uninstall +all: libs $(PROG) ../../Makefile.conf + +libs: + $(MAKE) -C $(DIR_LIBSRC) + +$(PROG): $(OBJS) $(STGLIBS) + $(CC) $^ $(LDFLAGS) $(LIBS) -o $(PROG) + +clean: + rm -f deps $(PROG) *.o tags *.*~ .OS + rm -f .OS + rm -f .store + rm -f .db.sql + rm -f core* + $(MAKE) -C $(DIR_LIBSRC) clean + +distclean: clean + rm -f ../../Makefile.conf + +install: install-bin install-data + +install-bin: + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/sbin + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/sbin/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) install + +install-data: + # Install etc + mkdir -m $(DATA_MODE) -p $(PREFIX)/etc/stargazer + install -m $(DATA_MODE) -o $(OWNER) ./rscriptd.conf $(PREFIX)/etc/stargazer/rscriptd.conf + +uninstall: uninstall-bin uninstall-data + +uninstall-bin: + rm -f $(PREFIX)/usr/sbin/$(PROG) + +uninstall-data: + # Uninstall etc + rm -f $(PREFIX)/etc/stragazer/rscriptd.conf + + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf + $(MAKE) -C $(DIR_LIBSRC) includes + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) -MM $$file` Makefile" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/rscriptd/build b/projects/rscriptd/build new file mode 100755 index 00000000..38302ee3 --- /dev/null +++ b/projects/rscriptd/build @@ -0,0 +1,144 @@ +#!/bin/sh + +# $Author: faust $ +# $Revision: 1.21 $ +# $Date: 2010/04/14 08:58:52 $ +###################################################### + +OS=unknown +sys=`uname -s` +release=`uname -r | cut -b1` +BUILD_DIR=`pwd` +CONFFILE="../../Makefile.conf" +PREFIX="/" +BIN_MODE=0755 +DATA_MODE=0644 +OWNER=root + +if [ -z $1 ] +then + MAKEOPTS="-j1" +else + if [ "$1" = "debug" ] + then + DEFS="-DDEBUG" + MAKEOPTS="-j1" + CXXFLAGS="$CXXFLAGS -g3 -W -Wall" + else + MAKEOPTS="-j1" + fi +fi + +CXXFLAGS="$CXXFLAGS -I/usr/local/include" +LDFLAGS="$LDFLAGS -L/usr/local/lib" + +if [ "$sys" = "Linux" ] +then + OS=linux + release="" + MAKE="make" +fi + +if [ "$sys" = "FreeBSD" ] +then + case $release in + 4) OS=bsd;; + 5) OS=bsd5;; + 6) OS=bsd5;; + 7) OS=bsd7;; + 8) OS=bsd7;; + *) OS=unknown;; + esac + MAKE="gmake" +fi + +if [ "$OS" = "unknown" ] +then + echo "#############################################################################" + echo "# Sorry, but rscriptd currently supported by Linux, FreeBSD 4.x, 5.x, 6.x #" + echo "#############################################################################" + exit 1 +fi + +echo "#############################################################################" +echo " Building rscriptd for $sys $release" +echo "#############################################################################" + +STG_LIBS="stg_logger.lib + stg_locker.lib + crypto.lib + common.lib + script_executer.lib + conffiles.lib" + +if [ "$OS" = "linux" ] +then + DEFS="$DEFS -DLINUX" + LIB_THREAD=-lpthread + SHELL="/bin/bash" +else + if [ "$OS" = "bsd" ] + then + DEFS="$DEFS -DFREE_BSD" + LIB_THREAD=-lc_r + else + DEFS="$DEFS -DFREE_BSD5" + if [ "$OS" = "bsd7" ] + then + LIB_THREAD=-lpthread + else + LIB_THREAD=-lc_r + fi + fi + SHELL="/usr/local/bin/bash" +fi + +echo -n "Checking endianess... " +echo "int main() { int probe = 0x00000001; return *(char *)&probe; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS build_check.c -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + echo "FAIL!" + echo "Endianess checking failed" + exit; +else + ./fake + if [ $? = 1 ] + then + ARCH=le + CXXFLAGS="$CXXFLAGS -DARCH_LE" + echo "Little Endian" + else + ARCH=be + CXXFLAGS="$CXXFLAGS -DARCH_BE" + echo "Big Endian" + fi +fi +rm -f fake +rm -f build_check.c + +echo "OS=$OS" > $CONFFILE +echo "STG_TIME=yes" >> $CONFFILE +echo "DIR_BUILD=$BUILD_DIR" >> $CONFFILE +echo "DIR_LIB=\$(DIR_BUILD)/../../lib" >> $CONFFILE +echo "DIR_LIBSRC=\$(DIR_BUILD)/../../stglibs" >> $CONFFILE +echo "DIR_INCLUDE=\$(DIR_BUILD)/../../include" >> $CONFFILE +echo "ARCH=$ARCH" >> $CONFFILE +echo "DEFS=$DEFS" >> $CONFFILE +echo -n "STG_LIBS=" >> $CONFFILE +for lib in $STG_LIBS +do + echo -n "$lib " >> $CONFFILE +done +echo "" >> $CONFFILE +echo "LIB_THREAD=$LIB_THREAD" >> $CONFFILE +echo "SHELL=$SHELL" >> $CONFFILE +echo "CXXFLAGS=$CXXFLAGS" >> $CONFFILE +echo "LDFLAGS=$LDFLAGS" >> $CONFFILE +echo "PREFIX=$PREFIX" >> $CONFFILE +echo "BIN_MODE=$BIN_MODE" >> $CONFFILE +echo "DATA_MODE=$DATA_MODE" >> $CONFFILE +echo "OWNER=$OWNER" >> $CONFFILE + +$MAKE $MAKEOPTS + diff --git a/projects/rscriptd/listener.cpp b/projects/rscriptd/listener.cpp new file mode 100644 index 00000000..87a85ebb --- /dev/null +++ b/projects/rscriptd/listener.cpp @@ -0,0 +1,499 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + * Author : Maxim Mamontov + */ + +#include +#include // readv +#include // for historical versions of BSD +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "listener.h" +#include "script_executer.h" +#include "stg_locker.h" +#include "common.h" + +//----------------------------------------------------------------------------- +LISTENER::LISTENER() + : WriteServLog(GetStgLogger()), + port(0), + running(false), + receiverStopped(true), + processorStopped(true), + userTimeout(0), + listenSocket(0) +{ +version = "rscriptd listener v.1.2"; + +pthread_mutex_init(&mutex, NULL); +} +//----------------------------------------------------------------------------- +void LISTENER::SetPassword(const string & p) +{ +password = p; +printfd(__FILE__, "Encryption initiated with password \'%s\'\n", password.c_str()); +InitEncrypt(&ctxS, password); +} +//----------------------------------------------------------------------------- +bool LISTENER::Start() +{ +printfd(__FILE__, "LISTENER::Start()\n"); +running = true; + +if (PrepareNet()) + { + return true; + } + +if (receiverStopped) + { + if (pthread_create(&receiverThread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + return true; + } + } + +if (processorStopped) + { + if (pthread_create(&processorThread, NULL, RunProcessor, this)) + { + errorStr = "Cannot create thread."; + return true; + } + } + +errorStr = ""; + +return false; +} +//----------------------------------------------------------------------------- +bool LISTENER::Stop() +{ +running = false; + +printfd(__FILE__, "LISTENER::Stop()\n"); + +usleep(500000); + +if (!processorStopped) + { + //5 seconds to thread stops itself + for (int i = 0; i < 25 && !processorStopped; i++) + { + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (!processorStopped) + { + //TODO pthread_cancel() + if (pthread_kill(processorThread, SIGINT)) + { + errorStr = "Cannot kill thread."; + return true; + } + printfd(__FILE__, "LISTENER killed Timeouter\n"); + } + } + +if (!receiverStopped) + { + //5 seconds to thread stops itself + for (int i = 0; i < 25 && !receiverStopped; i++) + { + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (!receiverStopped) + { + //TODO pthread_cancel() + if (pthread_kill(receiverThread, SIGINT)) + { + errorStr = "Cannot kill thread."; + return true; + } + printfd(__FILE__, "LISTENER killed Run\n"); + } + } + +pthread_join(receiverThread, NULL); +pthread_join(processorThread, NULL); + +pthread_mutex_destroy(&mutex); + +FinalizeNet(); + +std::for_each(users.begin(), users.end(), DisconnectUser(*this)); + +printfd(__FILE__, "LISTENER::Stoped successfully.\n"); + +return false; +} +//----------------------------------------------------------------------------- +void * LISTENER::Run(void * d) +{ +LISTENER * ia = static_cast(d); + +ia->Runner(); + +return NULL; +} +//----------------------------------------------------------------------------- +void LISTENER::Runner() +{ +receiverStopped = false; + +while (running) + { + RecvPacket(); + } + +receiverStopped = true; +} +//----------------------------------------------------------------------------- +void * LISTENER::RunProcessor(void * d) +{ +LISTENER * ia = static_cast(d); + +ia->ProcessorRunner(); + +return NULL; +} +//----------------------------------------------------------------------------- +void LISTENER::ProcessorRunner() +{ +processorStopped = false; + +while (running) + { + usleep(500000); + if (!pending.empty()) + ProcessPending(); + ProcessTimeouts(); + } + +processorStopped = true; +} +//----------------------------------------------------------------------------- +bool LISTENER::PrepareNet() +{ +listenSocket = socket(AF_INET, SOCK_DGRAM, 0); + +if (listenSocket < 0) + { + errorStr = "Cannot create socket."; + return true; + } + +printfd(__FILE__, "Port: %d\n", port); + +struct sockaddr_in listenAddr; +listenAddr.sin_family = AF_INET; +listenAddr.sin_port = htons(port); +listenAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); + +if (bind(listenSocket, (struct sockaddr*)&listenAddr, sizeof(listenAddr)) < 0) + { + errorStr = "LISTENER: Bind failed."; + return true; + } + +printfd(__FILE__, "LISTENER::PrepareNet() >>>> Start successfull.\n"); + +return false; +} +//----------------------------------------------------------------------------- +bool LISTENER::FinalizeNet() +{ +close(listenSocket); + +return false; +} +//----------------------------------------------------------------------------- +bool LISTENER::RecvPacket() +{ +struct iovec iov[2]; + +char buffer[RS_MAX_PACKET_LEN]; +RS_PACKET_HEADER packetHead; + +iov[0].iov_base = reinterpret_cast(&packetHead); +iov[0].iov_len = sizeof(packetHead); +iov[1].iov_base = buffer; +iov[1].iov_len = sizeof(buffer); + +size_t dataLen = 0; +while (dataLen < sizeof(buffer)) + { + if (!WaitPackets(listenSocket)) + { + if (!running) + return false; + continue; + } + int portion = readv(listenSocket, iov, 2); + if (portion < 0) + { + return true; + } + dataLen += portion; + } + +if (CheckHeader(packetHead)) + { + printfd(__FILE__, "Invalid packet or incorrect protocol version!\n"); + return true; + } + +std::string userLogin((char *)packetHead.login); +PendingData data; +data.login = userLogin; +data.ip = ntohl(packetHead.ip); +data.id = ntohl(packetHead.id); + +if (packetHead.packetType == RS_ALIVE_PACKET) + { + data.type = PendingData::ALIVE; + } +else if (packetHead.packetType == RS_CONNECT_PACKET) + { + data.type = PendingData::CONNECT; + if (GetParams(buffer, data)) + { + return true; + } + } +else if (packetHead.packetType == RS_DISCONNECT_PACKET) + { + data.type = PendingData::DISCONNECT; + if (GetParams(buffer, data)) + { + return true; + } + } + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +pending.push_back(data); + +return false; +} +//----------------------------------------------------------------------------- +bool LISTENER::GetParams(char * buffer, UserData & data) +{ +RS_PACKET_TAIL packetTail; + +Decrypt(&ctxS, (char *)&packetTail, buffer, sizeof(packetTail) / 8); + +if (strncmp((char *)packetTail.magic, RS_ID, RS_MAGIC_LEN)) + { + printfd(__FILE__, "Invalid crypto magic\n"); + return true; + } + +std::stringstream params; +params << data.login << " " + << inet_ntostring(data.ip) << " " + << ntohl(data.id) << " " + << (char *)packetTail.params; + +data.params = params.str(); + +return false; +} +//----------------------------------------------------------------------------- +void LISTENER::ProcessPending() +{ +printfd(__FILE__, "Pending data size: %d\n", pending.size()); +std::list::iterator it(pending.begin()); +while (it != pending.end()) + { + std::vector::iterator uit( + std::lower_bound( + users.begin(), + users.end(), + it->login) + ); + if (it->type == PendingData::CONNECT) + { + if (uit == users.end() || uit->login != it->login) + { + // Add new user + Connect(*it); + users.insert(uit, AliveData(static_cast(*it))); + } + else if (uit->login == it->login) + { + // Update already existing user + time(&uit->lastAlive); + uit->params = it->params; + } + } + else if (it->type == PendingData::ALIVE) + { + if (uit != users.end() && uit->login == it->login) + { + // Update existing user + time(&uit->lastAlive); + } + } + else if (it->type == PendingData::DISCONNECT) + { + if (uit != users.end() && uit->login == it->login.c_str()) + { + // Disconnect existing user + Disconnect(*uit); + users.erase(uit); + } + } + + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + pending.erase(it++); + } +} +//----------------------------------------------------------------------------- +void LISTENER::ProcessTimeouts() +{ +const std::vector::iterator it( + std::stable_partition( + users.begin(), + users.end(), + IsNotTimedOut(userTimeout) + ) + ); + +if (it != users.end()) + { + printfd(__FILE__, "Total users: %d, users to disconnect: %d\n", users.size(), std::distance(it, users.end())); + + std::for_each( + it, + users.end(), + DisconnectUser(*this) + ); + + users.erase(it, users.end()); + } +} +//----------------------------------------------------------------------------- +bool LISTENER::Connect(const UserData & data) const +{ +printfd(__FILE__, "Connect %s\n", data.login.c_str()); +if (access(scriptOnConnect.c_str(), X_OK) == 0) + { + if (ScriptExec(scriptOnConnect + " " + data.params)) + { + WriteServLog("Script %s cannot be executed for an unknown reason.", scriptOnConnect.c_str()); + return true; + } + } +else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnConnect.c_str()); + return true; + } +return false; +} +//----------------------------------------------------------------------------- +bool LISTENER::Disconnect(const UserData & data) const +{ +printfd(__FILE__, "Disconnect %s\n", data.login.c_str()); +if (access(scriptOnDisconnect.c_str(), X_OK) == 0) + { + if (ScriptExec(scriptOnDisconnect + " " + data.params)) + { + WriteServLog("Script %s cannot be executed for an unknown reson.", scriptOnDisconnect.c_str()); + return true; + } + } +else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnDisconnect.c_str()); + return true; + } +return false; +} +//----------------------------------------------------------------------------- +void LISTENER::InitEncrypt(BLOWFISH_CTX * ctx, const string & password) +{ +unsigned char keyL[PASSWD_LEN]; +memset(keyL, 0, PASSWD_LEN); +strncpy((char *)keyL, password.c_str(), PASSWD_LEN); +Blowfish_Init(ctx, keyL, PASSWD_LEN); +} +//----------------------------------------------------------------------------- +void LISTENER::Decrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8) +{ +if (dst != src) + memcpy(dst, src, len8 * 8); + +for (int i = 0; i < len8; i++) + Blowfish_Decrypt(ctx, (uint32_t *)(dst + i * 8), (uint32_t *)(dst + i * 8 + 4)); +} +//----------------------------------------------------------------------------- +bool LISTENER::CheckHeader(const RS_PACKET_HEADER & header) const +{ +if (strncmp((char *)header.magic, RS_ID, RS_MAGIC_LEN)) + { + return true; + } +if (strncmp((char *)header.protoVer, "02", RS_PROTO_VER_LEN)) + { + return true; + } +return false; +} +//----------------------------------------------------------------------------- +bool LISTENER::WaitPackets(int sd) const +{ +fd_set rfds; +FD_ZERO(&rfds); +FD_SET(sd, &rfds); + +struct timeval tv; +tv.tv_sec = 0; +tv.tv_usec = 500000; + +int res = select(sd + 1, &rfds, NULL, NULL, &tv); +if (res == -1) // Error + { + if (errno != EINTR) + { + printfd(__FILE__, "Error on select: '%s'\n", strerror(errno)); + } + return false; + } + +if (res == 0) // Timeout + { + return false; + } + +return true; +} +//----------------------------------------------------------------------------- diff --git a/projects/rscriptd/listener.h b/projects/rscriptd/listener.h new file mode 100644 index 00000000..4a012c77 --- /dev/null +++ b/projects/rscriptd/listener.h @@ -0,0 +1,148 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + * Author : Maxim Mamontov + */ + +#include + +#include +#include +#include +#include + +#include "os_int.h" +#include "blowfish.h" +#include "rs_packets.h" +#include "stg_logger.h" + +struct UserData +{ + std::string params; + std::string login; + uint32_t ip; + uint32_t id; +}; + +struct PendingData : public UserData +{ + enum {CONNECT, ALIVE, DISCONNECT} type; +}; + +struct AliveData : public UserData +{ + explicit AliveData(const UserData & data) + : UserData(data), + lastAlive(time(NULL)) + {}; + bool operator<(const std::string & rvalue) const { return login < rvalue; }; + time_t lastAlive; +}; + +class IsNotTimedOut : public std::unary_function { + public: + IsNotTimedOut(double to) : timeout(to), now(time(NULL)) {} + bool operator()(const AliveData & data) const + { + return difftime(now, data.lastAlive) < timeout; + } + private: + double timeout; + time_t now; +}; + +class LISTENER +{ +public: + LISTENER(); + ~LISTENER(){}; + + void SetPort(uint16_t p) { port = p; }; + void SetPassword(const std::string & p); + void SetUserTimeout(int t) { userTimeout = t; }; + void SetScriptOnConnect(const std::string & script) { scriptOnConnect = script; }; + void SetScriptOnDisconnect(const std::string & script) { scriptOnDisconnect = script; }; + + bool Start(); + bool Stop(); + bool IsRunning() const { return !receiverStopped && !processorStopped; }; + + const std::string & GetStrError() const { return errorStr; }; + const std::string & GetVersion() const { return version; }; + +private: + // Threading stuff + static void * Run(void * self); + static void * RunProcessor(void * self); + void Runner(); + void ProcessorRunner(); + // Networking stuff + bool PrepareNet(); + bool FinalizeNet(); + bool WaitPackets(int sd) const; + bool RecvPacket(); + // Parsing stuff + bool CheckHeader(const RS_PACKET_HEADER & header) const; + bool GetParams(char * buffer, UserData & data); + // Processing stuff + void ProcessPending(); + void ProcessTimeouts(); + bool Disconnect(const UserData & data) const; + bool Connect(const UserData & data) const; + // Decryption stuff + void InitEncrypt(BLOWFISH_CTX * ctx, const std::string & password); + void Decrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8); + + BLOWFISH_CTX ctxS; + STG_LOGGER & WriteServLog; + + mutable std::string errorStr; + std::string scriptOnConnect; + std::string scriptOnDisconnect; + std::string password; + uint16_t port; + + bool running; + bool receiverStopped; + bool processorStopped; + std::vector users; + std::list pending; + int userTimeout; + + pthread_t receiverThread; + pthread_t processorThread; + pthread_mutex_t mutex; + + int listenSocket; + + std::string version; + + friend class DisconnectUser; +}; + +class DisconnectUser : public std::unary_function { + public: + DisconnectUser(LISTENER & l) : listener(l) {}; + void operator()(const UserData & data) + { + listener.Disconnect(data); + }; + private: + LISTENER & listener; +}; +//----------------------------------------------------------------------------- diff --git a/projects/rscriptd/main.cpp b/projects/rscriptd/main.cpp new file mode 100644 index 00000000..4c53c2c7 --- /dev/null +++ b/projects/rscriptd/main.cpp @@ -0,0 +1,439 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.19 $ + $Author: faust $ + $Date: 2010/09/10 06:37:45 $ + */ + +#include +#include +#include +#include +#include +#include // creat +#include + +#include +#include +#include + +#include "common.h" +#include "stg_logger.h" +#include "script_executer.h" +#include "conffiles.h" +#include "listener.h" +#include "pidfile.h" +#include "version.h" + +using namespace std; + +#ifdef DEBUG +# define MAIN_DEBUG 1 +# define NO_DAEMON 1 +#endif + +#define START_FILE "/._ST_ART_ED_" + +static bool childExited = false; +set executersPid; +static pid_t stgChildPid; +volatile time_t stgTime = time(NULL); + +class STG_STOPPER +{ +public: + STG_STOPPER() { nonstop = true; } + bool GetStatus() const { return nonstop; }; + void Stop(const char * __file__, int __line__) + { + #ifdef NO_DAEMON + printfd(__FILE__, "rscriptd stopped at %s:%d\n", __file__, __line__); + #endif + nonstop = false; + } +private: + bool nonstop; +}; +//----------------------------------------------------------------------------- +STG_STOPPER nonstop; +//----------------------------------------------------------------------------- +void CatchPROF(int) +{ +} +//----------------------------------------------------------------------------- +void CatchUSR1(int) +{ +} +//----------------------------------------------------------------------------- +void CatchTERM(int sig) +{ +/* + *Function Name:CatchINT + *Parameters: sig_num - signal number + *Description: INT signal handler + *Returns: Nothing + */ +STG_LOGGER & WriteServLog = GetStgLogger(); +WriteServLog("Shutting down... %d", sig); + +nonstop.Stop(__FILE__, __LINE__); + +struct sigaction newsa, oldsa; +sigset_t sigmask; + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGTERM); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGTERM, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGINT, &newsa, &oldsa); +} +//----------------------------------------------------------------------------- +void CatchPIPE(int) +{ +STG_LOGGER & WriteServLog = GetStgLogger(); +WriteServLog("Broken pipe!"); +} +//----------------------------------------------------------------------------- +void CatchHUP(int) +{ +} +//----------------------------------------------------------------------------- +void CatchCHLD(int) +{ +int status; +pid_t childPid; +childPid = waitpid(-1, &status, WNOHANG); + +set::iterator pid; +pid = executersPid.find(childPid); +if (pid != executersPid.end()) + { + executersPid.erase(pid); + } +if (childPid == stgChildPid) + { + childExited = true; + } +} +//----------------------------------------------------------------------------- +void SetSignalHandlers() +{ +struct sigaction newsa, oldsa; +sigset_t sigmask; + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGTERM); +newsa.sa_handler = CatchTERM; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGTERM, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGUSR1); +newsa.sa_handler = CatchUSR1; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGUSR1, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +newsa.sa_handler = CatchTERM; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGINT, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGPIPE); +newsa.sa_handler = CatchPIPE; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGPIPE, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGHUP); +newsa.sa_handler = CatchHUP; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGHUP, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGCHLD); +newsa.sa_handler = CatchCHLD; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGCHLD, &newsa, &oldsa); +} +//----------------------------------------------------------------------------- +int StartScriptExecuter(char * procName, int msgKey, int * msgID) +{ +STG_LOGGER & WriteServLog = GetStgLogger(); + +if (*msgID == -11) // If msgID == -11 - first call. Create queue + { + for (int i = 0; i < 2; i++) + { + *msgID = msgget(msgKey, IPC_CREAT | IPC_EXCL | 0600); + + if (*msgID == -1) + { + *msgID = msgget(msgKey, 0); + if (*msgID == -1) + { + WriteServLog("Message queue not created."); + return -1; + } + else + { + msgctl(*msgID, IPC_RMID, NULL); + } + } + else + { + WriteServLog("Message queue created successfully. msgKey=%d msgID=%d", msgKey, *msgID); + break; + } + } + } + +pid_t executerPid = fork(); + +switch (executerPid) + { + case -1: // Failure + WriteServLog("Fork error!"); + return -1; + + case 0: // Child + //close(0); + //close(1); + //close(2); + //setsid(); + Executer(msgKey, *msgID, executerPid, procName); + return 1; + + default: // Parent + if (executersPid.empty()) + Executer(msgKey, *msgID, executerPid, NULL); + executersPid.insert(executerPid); + } +return 0; +} +//----------------------------------------------------------------------------- +#ifdef NO_DAEMON +int ForkAndWait(const string &) +#else +int ForkAndWait(const string & confDir) +#endif +{ +#ifndef NO_DAEMON +stgChildPid = fork(); +string startFile = confDir + START_FILE; +unlink(startFile.c_str()); + +switch (stgChildPid) + { + case -1: // Failure + return -1; + break; + + case 0: // Child + //close(0); + close(1); + close(2); + setsid(); + break; + + default: // Parent + for (int i = 0; i < 120 * 5; i++) + { + if (access(startFile.c_str(), F_OK) == 0) + { + //printf("Fork successfull. Exit.\n"); + unlink(startFile.c_str()); + exit(0); + } + + if (childExited) + { + unlink(startFile.c_str()); + exit(1); + } + usleep(200000); + } + unlink(startFile.c_str()); + exit(1); + break; + } +#endif +return 0; +} +//----------------------------------------------------------------------------- +void KillExecuters() +{ +set::iterator pid; +pid = executersPid.begin(); +while (pid != executersPid.end()) + { + printfd(__FILE__, "KillExecuters pid=%d\n", *pid); + kill(*pid, SIGUSR1); + ++pid; + } +} +//----------------------------------------------------------------------------- +int main(int argc, char * argv[]) +{ + +/* + Initialization order: + - Logger + - Config + - Set signal nandlers + - Fork and exit + */ + +CONFIGFILE * cfg = NULL; +LISTENER * listener = NULL; +int msgID = -11; +int execNum = 0; +int execMsgKey = 0; + +string logFileName; +string confDir; +string password; +string onConnect; +string onDisconnect; +int port; +int userTimeout; + +if (getuid()) + { + printf("You must be root. Exit.\n"); + exit(1); + } + +if (argc == 2) + cfg = new CONFIGFILE(argv[1]); +else + cfg = new CONFIGFILE("/etc/rscriptd/rscriptd.conf"); + +if (cfg->Error()) + { + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog.SetLogFileName("/var/log/rscriptd.log"); + WriteServLog("Error reading config file!"); + delete cfg; + return EXIT_FAILURE; + } + +cfg->ReadString("LogFileName", &logFileName, "/var/log/rscriptd.log"); +cfg->ReadInt("ExecutersNum", &execNum, 1); +cfg->ReadInt("ExecMsgKey", &execMsgKey, 5555); +cfg->ReadString("ConfigDir", &confDir, "/etc/rscriptd"); +cfg->ReadString("Password", &password, ""); +cfg->ReadInt("Port", &port, 5555); +cfg->ReadInt("UserTimeout", &userTimeout, 60); +cfg->ReadString("ScriptOnConnect", &onConnect, "/etc/rscriptd/OnConnect"); +cfg->ReadString("ScriptOnDisconnect", &onDisconnect, "/etc/rscriptd/OnDisconnect"); + +SetSignalHandlers(); +if (ForkAndWait(confDir) < 0) + { + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog("Fork error!"); + delete cfg; + return EXIT_FAILURE; + } + +STG_LOGGER & WriteServLog = GetStgLogger(); +PIDFile pidFile("/var/run/rscriptd.pid"); +WriteServLog.SetLogFileName(logFileName); +WriteServLog("rscriptd v. %s", SERVER_VERSION); + +for (int i = 0; i < execNum; i++) + { + int ret = StartScriptExecuter(argv[0], execMsgKey, &msgID); + if (ret < 0) + { + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog("Start Script Executer error!"); + delete cfg; + return EXIT_FAILURE; + } + if (ret == 1) + { + delete cfg; + return EXIT_SUCCESS; + } + } + +listener = new LISTENER(); +listener->SetPort(port); +listener->SetPassword(password); +listener->SetUserTimeout(userTimeout); +listener->SetScriptOnConnect(onConnect); +listener->SetScriptOnDisconnect(onDisconnect); + +listener->Start(); + +WriteServLog("rscriptd started successfully."); +WriteServLog("+++++++++++++++++++++++++++++++++++++++++++++"); + +#ifndef NO_DAEMON +string startFile(confDir + START_FILE); +creat(startFile.c_str(), S_IRUSR); +#endif + +while (nonstop.GetStatus()) + { + usleep(100000); + } + +listener->Stop(); + +WriteServLog("+++++++++++++++++++++++++++++++++++++++++++++"); + +int res = msgctl(msgID, IPC_RMID, NULL); +if (res) + WriteServLog("Queue was not removed. id=%d", msgID); +else + WriteServLog("Queue removed successfully."); + +KillExecuters(); + +WriteServLog("rscriptd stopped successfully."); +WriteServLog("---------------------------------------------"); + +delete listener; +delete cfg; +return EXIT_SUCCESS; +} +//----------------------------------------------------------------------------- + diff --git a/projects/rscriptd/pidfile.cpp b/projects/rscriptd/pidfile.cpp new file mode 100644 index 00000000..5f3f4979 --- /dev/null +++ b/projects/rscriptd/pidfile.cpp @@ -0,0 +1,53 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.1 $ + $Date: 2010/02/11 12:32:25 $ + $Author: faust $ + */ + +/* + * An implementation of RAII pid-file writer + */ + +#include +#include + +#include "pidfile.h" + +PIDFile::PIDFile(const std::string & fn) + : fileName(fn) +{ +if (fileName != "") + { + std::ofstream pf(fileName.c_str()); + pf << getpid() << std::endl; + pf.close(); + } +} + +PIDFile::~PIDFile() +{ +if (fileName != "") + { + unlink(fileName.c_str()); + } +} diff --git a/projects/rscriptd/pidfile.h b/projects/rscriptd/pidfile.h new file mode 100644 index 00000000..a8a7bb30 --- /dev/null +++ b/projects/rscriptd/pidfile.h @@ -0,0 +1,44 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Header file for RAII pid-file writer + */ + +/* + $Revision: 1.1 $ + $Date: 2010/02/11 12:32:25 $ + $Author: faust $ + */ + +#ifndef __PID_FILE_H__ +#define __PID_FILE_H__ + +#include + +class PIDFile { +public: + PIDFile(const std::string & fn); + ~PIDFile(); +private: + std::string fileName; +}; + +#endif diff --git a/projects/rscriptd/rscriptd.conf b/projects/rscriptd/rscriptd.conf new file mode 100644 index 00000000..d668d46a --- /dev/null +++ b/projects/rscriptd/rscriptd.conf @@ -0,0 +1,8 @@ +LogFileName=/var/log/rscriptd.log +ExecutersNum=1 +ConfigDir=/etc/rscriptd +Password=123456 +Port=9999 +UserTimeout=60 +ScriptOnConnect=/etc/rscriptd/OnConnect +ScriptOnDisconnect=/etc/rscriptd/OnDisconnect diff --git a/projects/sgauth/Makefile b/projects/sgauth/Makefile new file mode 100644 index 00000000..47488cc2 --- /dev/null +++ b/projects/sgauth/Makefile @@ -0,0 +1,97 @@ +############################################################################### +# $Id: Makefile,v 1.18 2009/08/06 12:50:55 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +PROG = sgauth + +SRCS = ./main.cpp \ + ./web.cpp + +STGLIBS = -lconffiles \ + -lstg_crypto \ + -lstg_common \ + -lia_auth_c \ + -lcommon_settings + +LIBS += $(LIB_THREAD) \ + -lexpat + +ifeq ($(OS),linux) +LIBS += -ldl +else +LIBS += -lintl \ + -lc +endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +CXXFLAGS += -Wall +LDFLAGS += -Wl,-E -L$(DIR_LIB) -Wl,-rpath,$(PREFIX)/usr/lib/stg -Wl,-rpath-link,$(DIR_LIB) + +vpath %.so $(DIR_LIB) + +.PHONY: all clean distclean libs install uninstall install-bin install-data uninstall-bin uninstall-data +all: libs $(PROG) ../../Makefile.conf + +libs: + $(MAKE) -C $(DIR_LIBSRC) + +$(PROG): $(OBJS) $(STGLIBS) + $(CC) $^ $(LDFLAGS) -o $(PROG) $(LIBS) + +clean: + rm -f deps $(PROG) *.o tags *.*~ .OS + rm -f .OS + rm -f .store + rm -f .db.sql + rm -f core* + rm -f css.h + $(MAKE) -C $(DIR_LIBSRC) clean + +distclean: clean + rm -f ../../Makefile.conf + +install: install-bin install-data + +install-bin: + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/sbin + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/sbin/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) install + +install-data: + # Install etc + mkdir -m $(DATA_MODE) -p $(PREFIX)/etc + install -m $(DATA_MODE) -o $(OWNER) ./sgauth.conf $(PREFIX)/etc/sgauth.conf + +uninstall: uninstall-bin uninstall-data + +uninstall-bin: + rm -f $(PREFIX)/usr/sbin/$(PROG) + +uninstall-data: + # Uninstall etc + rm -f $(PREFIX)/etc/stargazer/sgauth.conf + + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf sgauth.css + $(MAKE) -C $(DIR_LIBSRC) includes + @>deps ;\ + ./make_css.sh + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) -MM $$file` Makefile" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/sgauth/build b/projects/sgauth/build new file mode 100755 index 00000000..e61b957c --- /dev/null +++ b/projects/sgauth/build @@ -0,0 +1,142 @@ +#!/bin/sh + +# $Author: faust $ +# $Revision: 1.24 $ +# $Date: 2010/04/14 08:59:02 $ +###################################################### + +OS=unknown +sys=`uname -s` +release=`uname -r | cut -b1` +BUILD_DIR=`pwd` +CONFFILE="../../Makefile.conf" +PREFIX="/" +BIN_MODE=0755 +DATA_MODE=0644 +OWNER=root + +if [ -z $1 ] +then + MAKEOPTS="-j1" +else + if [ "$1" = "debug" ] + then + DEFS="-DDEBUG" + MAKEOPTS="-j1" + CXXFLAGS="$CXXFLAGS -g3 -W -Wall" + else + MAKEOPTS="-j1" + fi +fi + +CXXFLAGS="$CXXFLAGS -I/usr/local/include" +LDFLAGS="$CXXFLAGS -L/usr/local/lib" + +if [ "$sys" = "Linux" ] +then + OS=linux + release="" + MAKE="make" +fi + +if [ "$sys" = "FreeBSD" ] +then + case $release in + 4) OS=bsd;; + 5) OS=bsd5;; + 6) OS=bsd5;; + 7) OS=bsd7;; + 8) OS=bsd7;; + *) OS=unknown;; + esac + MAKE="gmake" +fi + +if [ "$OS" = "unknown" ] +then + echo "#############################################################################" + echo "# Sorry, but sgauth currently supported by Linux, FreeBSD 4.x, 5.x, 6.x #" + echo "#############################################################################" + exit 1 +fi + +echo "#############################################################################" +echo " Building sgauth for $sys $release" +echo "#############################################################################" + +STG_LIBS="crypto.lib + common.lib + common_settings.lib + conffiles.lib + ia_auth_c.lib" + +if [ "$OS" = "linux" ] +then + DEFS="$DEFS -DLINUX" + LIB_THREAD=-lpthread + SHELL="/bin/bash" +else + if [ "$OS" = "bsd" ] + then + DEFS="$DEFS -DFREE_BSD" + LIB_THREAD=-lc_r + else + DEFS="$DEFS -DFREE_BSD5" + if [ "$OS" = "bsd7" ] + then + LIB_THREAD=-lpthread + else + LIB_THREAD=-lc_r + fi + fi + SHELL="/usr/local/bin/bash" +fi + +echo -n "Checking endianess... " +echo "int main() { int probe = 0x00000001; return *(char *)&probe; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS build_check.c -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + echo "FAIL!" + echo "Endianess checking failed" + exit; +else + ./fake + if [ $? = 1 ] + then + ARCH=le + CXXFLAGS="$CXXFLAGS -DARCH_LE" + echo "Little Endian" + else + ARCH=be + CXXFLAGS="$CXXFLAGS -DARCH_BE" + echo "Big Endian" + fi +fi +rm -f fake +rm -f build_check.c + +echo "OS=$OS" > $CONFFILE +echo "STG_TIME=yes" >> $CONFFILE +echo "DIR_BUILD=$BUILD_DIR" >> $CONFFILE +echo "DIR_LIB=\$(DIR_BUILD)/../../lib" >> $CONFFILE +echo "DIR_LIBSRC=\$(DIR_BUILD)/../../stglibs" >> $CONFFILE +echo "DIR_INCLUDE=\$(DIR_BUILD)/../../include" >> $CONFFILE +echo "ARCH=$ARCH" >> $CONFFILE +echo "DEFS=$DEFS" >> $CONFFILE +echo -n "STG_LIBS=" >> $CONFFILE +for lib in $STG_LIBS +do + echo -n "$lib " >> $CONFFILE +done +echo "" >> $CONFFILE +echo "LIB_THREAD=$LIB_THREAD" >> $CONFFILE +echo "SHELL=$SHELL" >> $CONFFILE +echo "CXXFLAGS=$CXXFLAGS" >> $CONFFILE +echo "LDFLAGS=$LDFLAGS" >> $CONFFILE +echo "PREFIX=$PREFIX" >> $CONFFILE +echo "BIN_MODE=$BIN_MODE" >> $CONFFILE +echo "DATA_MODE=$DATA_MODE" >> $CONFFILE +echo "OWNER=$OWNER" >> $CONFFILE +$MAKE $MAKEOPTS + diff --git a/projects/sgauth/main.cpp b/projects/sgauth/main.cpp new file mode 100644 index 00000000..1aae7aa6 --- /dev/null +++ b/projects/sgauth/main.cpp @@ -0,0 +1,570 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.13 $ + $Date: 2010/04/14 09:01:29 $ + $Author: faust $ + */ + + +#include +#include + +#ifndef WIN32 +#include +#include +#include +#include +#include +#include "conffiles.h" +#endif + +#include +#include +#include + +#include "ia_auth_c.h" +#include "common.h" +#include "common_settings.h" +#include "web.h" + +int mes; +char infoText[256]; +char messageText[256]; + +const int winKOI = 0; + +IA_CLIENT_PROT * clnp; +WEB * web = NULL; + +using namespace std; + +time_t stgTime; + +//----------------------------------------------------------------------------- +class SETTINGS: public COMMON_SETTINGS +{ +public: + SETTINGS(); + virtual ~SETTINGS(){}; + virtual int Reload(){ return 0; }; + void SetConfFile(string confFile); + virtual int ReadSettings(); + + virtual string GetStrError() const; + + string GetServerName() const; + uint16_t GetServerPort() const; + uint16_t GetLocalPort() const; + + string GetLogin() const; + string GetPassword() const; + + bool GetDaemon() const; + bool GetShowPid() const; + bool GetNoWeb() const; + bool GetReconnect() const; + int GetRefreshPeriod() const; + uint32_t GetListenWebIP() const; + + void Print() const; + +private: + string login; + string password; + string serverName; + int port; + int localPort; + uint32_t listenWebIP; + int refreshPeriod; + + bool daemon; + bool noWeb; + bool reconnect; + bool showPid; + + string confFile; +}; +//----------------------------------------------------------------------------- +SETTINGS::SETTINGS() +{ +confFile = "/etc/sgauth.conf"; +} +//----------------------------------------------------------------------------- +void SETTINGS::SetConfFile(string confFile) +{ +SETTINGS::confFile = confFile; +} +//----------------------------------------------------------------------------- +int SETTINGS::ReadSettings() +{ +CONFIGFILE * cf; + +cf = new CONFIGFILE(confFile); +string tmp; +int e = cf->Error(); + +if (e) + { + printf("Cannot read file.\n"); + delete cf; + return -1; + } + +cf->ReadString("Login", &login, "/?--?--?*"); +if (login == "/?--?--?*") + { + strError = "Parameter \'Login\' not found."; + delete cf; + return -1; + } + +cf->ReadString("Password", &password, "/?--?--?*"); +if (login == "/?--?--?*") + { + strError = "Parameter \'Password\' not found."; + delete cf; + return -1; + } + +cf->ReadString("ServerName", &serverName, "?*?*?"); +if (serverName == "?*?*?") + { + strError = "Parameter \'ServerName\' not found."; + delete cf; + return -1; + } + +cf->ReadString("ListenWebIP", &tmp, "127.0.0.1"); +listenWebIP = inet_addr(tmp.c_str()); +if (listenWebIP == INADDR_NONE) + { + strError = "Parameter \'ListenWebIP\' is not valid."; + delete cf; + return -1; + } + +cf->ReadString("ServerPort", &tmp, "5555"); +if (ParseIntInRange(tmp, 1, 65535, &port)) + { + strError = "Parameter \'ServerPort\' is not valid."; + delete cf; + return -1; + } + +cf->ReadString("LocalPort", &tmp, "0"); +if (ParseIntInRange(tmp, 0, 65535, &localPort)) + { + strError = "Parameter \'LocalPort\' is not valid."; + delete cf; + return -1; + } + +printf("LocalPort=%d\n", localPort); + +cf->ReadString("RefreshPeriod", &tmp, "5"); +if (ParseIntInRange(tmp, 1, 24*3600, &refreshPeriod)) + { + strError = "Parameter \'RefreshPeriod\' is not valid."; + delete cf; + return -1; + } + +cf->ReadString("Reconnect", &tmp, "yes"); +if (ParseYesNo(tmp, &reconnect)) + { + strError = "Parameter \'Reconnect\' is not valid."; + delete cf; + return -1; + } + +cf->ReadString("Daemon", &tmp, "yes"); +if (ParseYesNo(tmp, &daemon)) + { + strError = "Parameter \'Daemon\' is not valid."; + delete cf; + return -1; + } + +cf->ReadString("ShowPid", &tmp, "no"); +if (ParseYesNo(tmp, &showPid)) + { + strError = "Parameter \'ShowPid\' is not valid."; + delete cf; + return -1; + } + +cf->ReadString("DisableWeb", &tmp, "no"); +if (ParseYesNo(tmp, &noWeb)) + { + strError = "Parameter \'DisableWeb\' is not valid."; + delete cf; + return -1; + } + +delete cf; +return 0; +} +//----------------------------------------------------------------------------- +string SETTINGS::GetStrError() const +{ +return strError; +} +//----------------------------------------------------------------------------- +string SETTINGS::GetLogin() const +{ +return login; +} +//----------------------------------------------------------------------------- +string SETTINGS::GetPassword() const +{ +return password; +} +//----------------------------------------------------------------------------- +string SETTINGS::GetServerName() const +{ +return serverName; +} +//----------------------------------------------------------------------------- +uint16_t SETTINGS::GetServerPort() const +{ +return port; +} +//----------------------------------------------------------------------------- +uint16_t SETTINGS::GetLocalPort() const +{ +return localPort; +} +//----------------------------------------------------------------------------- +int SETTINGS::GetRefreshPeriod() const +{ +return refreshPeriod; +} +//----------------------------------------------------------------------------- +bool SETTINGS::GetDaemon() const +{ +return daemon; +} +//----------------------------------------------------------------------------- +bool SETTINGS::GetNoWeb() const +{ +return noWeb; +} +//----------------------------------------------------------------------------- +bool SETTINGS::GetShowPid() const +{ +return showPid; +} +//----------------------------------------------------------------------------- +bool SETTINGS::GetReconnect() const +{ +return reconnect; +} +//----------------------------------------------------------------------------- +uint32_t SETTINGS::GetListenWebIP() const +{ +return listenWebIP; +} +//----------------------------------------------------------------------------- +void SETTINGS::Print() const +{ +cout << "login = " << login << endl; +cout << "password = " << password << endl; +cout << "ip = " << serverName << endl; +cout << "port = " << port << endl; +cout << "localPort = " << localPort << endl; +cout << "listenWebIP = " << inet_ntostring(listenWebIP) << endl; +cout << "DisableWeb = " << noWeb << endl; +cout << "refreshPeriod = " << refreshPeriod << endl; +cout << "daemon = " << daemon << endl; +cout << "reconnect = " << reconnect << endl; +} +//----------------------------------------------------------------------------- +void Usage() +{ +printf("sgauth \n"); //TODO change to correct +} +//----------------------------------------------------------------------------- +void EventsFn(int) +{ +LOADSTAT ls; +clnp->GetStat(&ls); +} +//----------------------------------------------------------------------------- +void SetDirName(const vector & dn, void *) +{ +for (int j = 0; j < DIR_NUM; j++) + { + if (winKOI) + { + string dir; + KOIToWin(dn[j], &dir); + if (web) + web->SetDirName(dir, j); + } + else + { + if (web) + web->SetDirName(dn[j], j); + } + } +} +//----------------------------------------------------------------------------- +void StatUpdate(const LOADSTAT & ls, void *) +{ +if (web) + web->UpdateStat(ls); +} +//----------------------------------------------------------------------------- +void StatusChanged(int, void *) +{ + +} +//----------------------------------------------------------------------------- +void ShowMessage(const string & message, int i, int, int, void *) +{ +if (web) + web->AddMessage(message, i); +} +//----------------------------------------------------------------------------- +void ShowError(const string & message, int, void *) +{ +if (web) + web->AddMessage(message, 0); +} +//----------------------------------------------------------------------------- +#ifndef WIN32 +void CatchUSR1(int) +{ +if (clnp->GetAuthorized()) + { + cout << "Connect" << endl; + clnp->Connect(); + } +} +//----------------------------------------------------------------------------- +void CatchUSR2(int) +{ +cout << "Disconnect" << endl; +clnp->Disconnect(); +} +//----------------------------------------------------------------------------- +void CatchTERM(int) +{ +cout << "Terminated" << endl; +clnp->Disconnect(); +sleep(2); +exit(0); +} +//----------------------------------------------------------------------------- +static void SetSignalHandlers() +{ +struct sigaction newsa, oldsa; +sigset_t sigmask; + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGTERM); +newsa.sa_handler = CatchTERM; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGTERM, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +newsa.sa_handler = CatchTERM; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGINT, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGUSR1); +newsa.sa_handler = CatchUSR1; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGUSR1, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGUSR2); +newsa.sa_handler = CatchUSR2; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGUSR2, &newsa, &oldsa); + +return; +} +#endif +//----------------------------------------------------------------------------- +int main(int argc, char *argv[]) +{ +//int port; +//char *endptr; + +SETTINGS settings; + +#ifndef WIN32 +if (argc == 2) +#else +if(0) +#endif + { + settings.SetConfFile(argv[1]); + if (settings.ReadSettings()) + { + printf("ReadSettingsError\n"); + printf("%s\n", settings.GetStrError().c_str()); + exit(-1); + } + settings.Print(); + } +else + { + /*if (argc != 5) + { + Usage(); + exit(1); + } + else + { + string serverName(argv[1]); + port = strtol(argv[2], &endptr, 10); + if (*endptr != 0) + { + printf("Invalid port!\n"); + exit(1); + } + login = argv[3]; + passwd = argv[4]; + }*/ + } + +//settings.Print(); + +#ifndef WIN32 +if (settings.GetDaemon()) + { + /*close(0); + close(1); + close(2);*/ + + switch (fork()) + { + case -1: // ìÁÖÁ + exit(1); + break; + + case 0: // ðÏÔÏÍÏË + setsid(); + break; + + default: // ïÓÎÏ×ÎÏÊ ÐÒÏÃÅÓÓ + exit(0); + break; + } + } + + + +#endif + +clnp = new IA_CLIENT_PROT(settings.GetServerName(), settings.GetServerPort(), settings.GetLocalPort()); + +if (!settings.GetNoWeb()) + { + web = new WEB(); + web->SetRefreshPagePeriod(settings.GetRefreshPeriod()); + web->SetListenAddr(settings.GetListenWebIP()); + web->Start(); + } + +clnp->SetLogin(settings.GetLogin()); +clnp->SetPassword(settings.GetPassword()); + +clnp->SetStatusChangedCb(StatusChanged, NULL); +clnp->SetInfoCb(ShowMessage, NULL); +clnp->SetErrorCb(ShowError, NULL); +clnp->SetDirNameCb(SetDirName, NULL); +clnp->SetStatChangedCb(StatUpdate, NULL); +clnp->SetReconnect(settings.GetReconnect()); + + +clnp->Start(); + +SetSignalHandlers(); + +#ifdef LINUX +for (int i = 1; i < argc; i++) + memset(argv[i], 0, strlen(argv[i])); + +if(argc > 1) + strcpy(argv[1], "Connecting..."); +#endif + +#ifdef FREEBSD +setproctitle("Connecting..."); +#endif +clnp->Connect(); + +while (1) + { + #ifdef WIN32 + Sleep(200); + #else + usleep(200000); + #endif + + char state[20]; + + if (clnp->GetAuthorized()) + { + if (settings.GetShowPid()) + sprintf(state, "On %d", getpid()); + else + strcpy(state, "Online"); + } + else + { + if (settings.GetShowPid()) + sprintf(state, "Off %d", getpid()); + else + strcpy(state, "Offline"); + } + + #ifdef LINUX + for (int i = 1; i < argc; i++) + memset(argv[i], 0, strlen(argv[i])); + if(argc > 1) + strcpy(argv[1], state); + #endif + + #ifdef FREEBSD + setproctitle(state); + #endif + + #ifdef FREEBSD_5 + setproctitle(state); + #endif + } + +return 0; +} +//----------------------------------------------------------------------------- + + diff --git a/projects/sgauth/make_css.sh b/projects/sgauth/make_css.sh new file mode 100755 index 00000000..b4de011e --- /dev/null +++ b/projects/sgauth/make_css.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +OUT_FILE=css.h + + +echo "const char * css =" > $OUT_FILE +echo "\"/*------*/\\\\n\"" >> $OUT_FILE +sed -e 's/$/\\n"/g' -e 's/^/"/g' sgauth.css >> $OUT_FILE +echo ";" >> $OUT_FILE + diff --git a/projects/sgauth/readme b/projects/sgauth/readme new file mode 100644 index 00000000..08d510ee --- /dev/null +++ b/projects/sgauth/readme @@ -0,0 +1,13 @@ +äÌÑ ËÏÍÉÐÉÌÑÃÉÉ ÐÒÏÇÒÁÍÍÙ ÚÁÐÕÓÔÉÔÅ ÓËÒÉÐÔ ./build + +ðÒÏÇÒÁÍÍÁ ÚÁÐÕÓËÁÅÔÓÑ ÓÏ ÓÌÅÄÕÀÝÉÍÉ ÐÁÒÁÍÅÔÒÁÍÉ: + > sgauth +ÉÌÉ + > sgauth +ÉÌÉ + > sgauth + +÷ ÐÏÓÌÅÄÎÅÍ ÓÌÕÞÁÅ ÉÓÐÏÌØÚÕÅÔÓÑ ËÏÎÆÉÇÕÒÁÃÉÏÎÎÙÊ ÆÁÊÌ /etc/sgauth.conf + +ðÒÉ ÐÏÄËÌÀÞÅÎÉÉ ÂÒÁÕÚÅÒÏÍ ÐÏ ÁÄÒÅÓÕ http://localhost:5580 +ÍÏÖÎÏ ÐÏÓÍÏÔÒÅÔØ ÓÔÁÔÉÓÔÉËÕ. diff --git a/projects/sgauth/sgauth.conf b/projects/sgauth/sgauth.conf new file mode 100644 index 00000000..1b7d4356 --- /dev/null +++ b/projects/sgauth/sgauth.conf @@ -0,0 +1,37 @@ +#Stargazer server ip +ServerName=192.168.1.2 + +#Stargazer server port +#Default value 5555 +ServerPort=5555 + +#User's login +Login=test + +# +# +LocalPort=12345 + +#User's password +Password=1234567 + +# +#Default value yes +#Reconnect=no + +# +#Default value yes +#Daemon=yes + +#Refresh web page period +#Default value 10 +#RefreshPeriod=10 + +# +#Default value 127.0.0.1 +ListenWebIP=0.0.0.0 + +#Default value no +DisableWeb=no + +#ShowPid=no diff --git a/projects/sgauth/sgauth.css b/projects/sgauth/sgauth.css new file mode 100644 index 00000000..f816dbf9 --- /dev/null +++ b/projects/sgauth/sgauth.css @@ -0,0 +1,78 @@ +H3 +{ +color: black; +} + +body +{ +background-color: silver; +} + +#TraffTable +{ +background-color: white; +} + +#TraffTableCaptionRow +{ +background-color: silver; +} + +#TraffTableCaptionCellC, +#TraffTableUMCellC, +#TraffTableDMCellC, +#TraffTableUSCellC, +#TraffTableDSCellC +{ +background-color: silver; +} + +#TraffTableDMRow, +#TraffTableDSRow +{ +background-color: #f2f0cc; +} + +#TraffTableUMRow, +#TraffTableUSRow +{ +background-color: white; +} + +#ConnectionStateOnline +{ +color: green; +font-size: 20px +} + +#ConnectionStateOffline +{ +color: red; +font-size: 20px +} + +p +{ +padding: 2px; +margin: 0px; +} + +#MessagesTable +{ +background-color: white; +} + +#MessagesTableRowC +{ +background-color: silver; +} + + +#MessagesTableRow0, +#MessagesTableRow2, +#MessagesTableRow4, +#MessagesTableRow6, +#MessagesTableRow8 +{ +background-color: #f2f0cc; +} diff --git a/projects/sgauth/web.cpp b/projects/sgauth/web.cpp new file mode 100644 index 00000000..caec33a0 --- /dev/null +++ b/projects/sgauth/web.cpp @@ -0,0 +1,446 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.7 $ + $Date: 2010/03/15 12:58:17 $ + */ + +#include +#include +#include +#include + +#include "web.h" +#include "common.h" +#include "ia_auth_c.h" + +extern WEB * web; +extern IA_CLIENT_PROT * clnp; + +#define LISTEN_PORT (5580) + +#include "css.h" + +//--------------------------------------------------------------------------- +#ifndef WIN32 +void * RunWeb(void *) +#else +unsigned long WINAPI RunWeb(void *) +#endif +{ +while (1) + web->Run(); +return NULL; +} +//--------------------------------------------------------------------------- +WEB::WEB() + : res(0), + listenSocket(0), + outerSocket(0), + refreshPeriod(0), + listenWebAddr(0), + outerAddrLen(0) +{ +#ifdef WIN32 +res = WSAStartup(MAKEWORD(2,0), &wsaData); +#endif + +for (int i = 0; i < DIR_NUM; i++) + dirName[i] = "-"; + +refreshPeriod = 5; +} +//--------------------------------------------------------------------------- +void WEB::Start() +{ +#ifdef WIN32 +unsigned long pt; +CreateThread( + NULL, // pointer to thread security attributes + 16384, // initial thread stack size, in bytes + RunWeb, // pointer to thread function + NULL, // argument for new thread + 0, // CREATE_SUSPENDED, // creation flags + &pt // pointer to returned thread identifier + ); +#else +pthread_create(&thread, NULL, RunWeb, NULL); +#endif +} +//--------------------------------------------------------------------------- +void WEB::PrepareNet() +{ +listenSocket = socket(PF_INET, SOCK_STREAM, 0); +listenAddr.sin_family = AF_INET; +listenAddr.sin_port = htons(LISTEN_PORT); +listenAddr.sin_addr.s_addr = listenWebAddr; + +int lng = 1; + +#ifndef WIN32 +if (0 != setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &lng, 4)) + { + printf("Setsockopt Fail\n"); + printf(">>> Error %s\n", strerror(errno)); + } +#else +//??? TODO +#endif + + +res = bind(listenSocket, (struct sockaddr*)&listenAddr, sizeof(listenAddr)); + +if (res == -1) + { + printf("Bind failed.\n"); + exit(0); + } + +res = listen(listenSocket, 0); +if (res == -1) + { + printf("Listen failed.\n"); + exit(0); + } + +outerAddrLen = sizeof(outerAddr); +} +//--------------------------------------------------------------------------- +void WEB::SetRefreshPagePeriod(int p) +{ +refreshPeriod = p; +if (refreshPeriod <= 0 || refreshPeriod > 24*3600) + refreshPeriod = 5; +} +//--------------------------------------------------------------------------- +void WEB::SetListenAddr(uint32_t ip) +{ +listenWebAddr = ip; +} +//--------------------------------------------------------------------------- +void WEB::Run() +{ +PrepareNet(); +char recvBuffer[4096]; +while (1) + { + outerSocket = accept(listenSocket, (struct sockaddr*)&outerAddr, &outerAddrLen); + if (outerSocket == -1) + { + printf(">>> Error %s\n", strerror(errno)); + continue; + } + recv(outerSocket, recvBuffer, sizeof(recvBuffer), 0); + + if (strncmp(recvBuffer, "GET /sgauth.css", strlen("GET /sgauth.css")) == 0) + { + SendCSS(); + //printf("(1) recvBuffer=%s\n", recvBuffer); + } + else if (strncmp(recvBuffer, "GET /disconnect", strlen("GET /disconnect")) == 0) + { + clnp->Disconnect(); + Redirect("/"); + //printf("(2) recvBuffer=%s\n", recvBuffer); + } + else if (strncmp(recvBuffer, "GET /connect", strlen("GET /connect")) == 0) + { + clnp->Connect(); + Redirect("/"); + //printf("(3) recvBuffer=%s\n", recvBuffer); + } + else if (strncmp(recvBuffer, "GET /exit", strlen("GET /exit")) == 0) + { + Redirect("/"); + clnp->Disconnect(); + #ifdef WIN32 + Sleep(1000); + #else + usleep(1000000); + #endif + exit(0); + } + else + { + SendReply(); + //printf("(4) recvBuffer=%s\n", recvBuffer); + } + + #ifdef WIN32 + closesocket(outerSocket); + #else + close(outerSocket); + #endif + } +} +//--------------------------------------------------------------------------- +int WEB::Redirect(const char * url) +{ +const char * redirect = + "HTTP/1.0 200 OK\n" + "Content-Type: text/html\n" + "Connection: close" + "\n\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n\n"; + +char buff[2000]; +sprintf(buff, redirect, url); +send(outerSocket, buff, strlen(buff), 0); + +return 0; +} +//--------------------------------------------------------------------------- +int WEB::SendReply() +{ +int j, rowNum; + +const char * replyHeader = + "HTTP/1.0 200 OK\n" + "Content-Type: text/html\n" + "Connection: close" + "\n\n" + "\n" + "\n" + "\n" + "\n" + "sgauth\n" + "" + "\n" + "\n" + "

Stargazer

\n"; + +const char * replyFooter = "\n\n"; + +char replyHeaderBuffer[2000]; +sprintf(replyHeaderBuffer, replyHeader, refreshPeriod); + +send(outerSocket, replyHeaderBuffer, strlen(replyHeaderBuffer), 0); + +char str[512]; + +int st = clnp->GetAuthorized(); + +sprintf(str, "%s

\n", gettext("Connect")); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str, "%s

\n", gettext("Disconnect")); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str, "%s

\n", gettext("Refresh")); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str, "%s

\n", gettext("Exit")); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str, "

%s

\n" , st ? "ConnectionStateOnline":"ConnectionStateOffline", st ? "Online":"Offline"); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str, "

%s: %.3f

\n" , gettext("Cash"), ls.cash / 1000.0); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str, "

%s: %s

\n" , + gettext("PrepaidTraffic"), + ls.freeMb[0] == 'C' ? ls.freeMb + 1 : ls.freeMb); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str, "\n"); +res = send(outerSocket, str, strlen(str), 0); +sprintf(str, " \n"); +res = send(outerSocket, str, strlen(str), 0); +sprintf(str, " \n"); +res = send(outerSocket, str, strlen(str), 0); + +rowNum = 0; +for (j = 0; j < DIR_NUM; j++) + { + if (dirName[j][0] == 0) + continue; + string s; + KOIToWin(dirName[j], &s);// +++++++++ sigsegv ========== TODO too long dir name crashes sgauth + sprintf(str, " \n", rowNum++, s.c_str()); + send(outerSocket, str, strlen(str), 0); + } + +sprintf(str," \n"); +send(outerSocket, str, strlen(str), 0); + +sprintf(str," \n"); +send(outerSocket, str, strlen(str), 0); + +sprintf(str," \n", gettext("Month Upload")); +send(outerSocket, str, strlen(str), 0); + +rowNum = 0; +for (j = 0; j < DIR_NUM; j++) + { + if (dirName[j][0] == 0) + continue; + sprintf(str," \n", rowNum++, IntToKMG(ls.mu[j], ST_F)); + res = send(outerSocket, str, strlen(str), 0); + } + +sprintf(str," \n"); +res = send(outerSocket, str, strlen(str), 0); +sprintf(str," \n"); +res = send(outerSocket, str, strlen(str), 0); +sprintf(str," \n", gettext("Month Download")); +res = send(outerSocket, str, strlen(str), 0); + +rowNum = 0; +for (j = 0; j < DIR_NUM; j++) + { + if (dirName[j][0] == 0) + continue; + sprintf(str," \n", rowNum++, IntToKMG(ls.md[j], ST_F)); + res = send(outerSocket, str, strlen(str), 0); + } +sprintf(str," \n"); +res = send(outerSocket, str, strlen(str), 0); + + +sprintf(str," \n"); +res = send(outerSocket, str, strlen(str), 0); +sprintf(str," \n", gettext("Session Upload")); +res = send(outerSocket, str, strlen(str), 0); + +rowNum = 0; +for (j = 0; j < DIR_NUM; j++) + { + if (dirName[j][0] == 0) + continue; + sprintf(str," \n", rowNum++, IntToKMG(ls.su[j], ST_F)); + res = send(outerSocket, str, strlen(str), 0); + } + +sprintf(str," \n"); +res = send(outerSocket, str, strlen(str), 0); +sprintf(str," \n"); +res = send(outerSocket, str, strlen(str), 0); +sprintf(str," \n", gettext("Session Download")); +res = send(outerSocket, str, strlen(str), 0); + +rowNum = 0; +for (j = 0; j < DIR_NUM; j++) + { + if (dirName[j][0] == 0) + continue; + sprintf(str," \n", j, IntToKMG(ls.sd[j], ST_F)); + res = send(outerSocket, str, strlen(str), 0); + } + +sprintf(str," \n"); +res = send(outerSocket, str, strlen(str), 0); + +sprintf(str,"
 %s
%s%s
%s%s
%s%s
%s%s
\n"); +res = send(outerSocket, str, strlen(str), 0); + +rowNum = 0; +if (!messages.empty()) + { + sprintf(str," \n"); + res = send(outerSocket, str, strlen(str), 0); + + sprintf(str," \n"); + send(outerSocket, str, strlen(str), 0); + sprintf(str," \n"); + send(outerSocket, str, strlen(str), 0); + sprintf(str," \n"); + send(outerSocket, str, strlen(str), 0); + sprintf(str," \n"); + send(outerSocket, str, strlen(str), 0); + + list::reverse_iterator it; + it = messages.rbegin(); + while (it != messages.rend()) + { + sprintf(str," \n", rowNum); + send(outerSocket, str, strlen(str), 0); + sprintf(str," \n", it->recvTime.c_str()); + send(outerSocket, str, strlen(str), 0); + sprintf(str," \n", it->msg.c_str()); + send(outerSocket, str, strlen(str), 0); + sprintf(str," \n"); + send(outerSocket, str, strlen(str), 0); + ++it; + ++rowNum; + } + + sprintf(str,"
DateText
%s%s
\n"); + res = send(outerSocket, str, strlen(str), 0); + } + +time_t t = time(NULL); +sprintf(str,"Îáíîâëåíî: %s" , ctime(&t)); +res = send(outerSocket, str, strlen(str), 0); + +send(outerSocket, replyFooter, strlen(replyFooter), 0); + +return 0; +} +//--------------------------------------------------------------------------- +int WEB::SendCSS() +{ +const char * replyHeader = + "HTTP/1.0 200 OK\n" + "Content-Type: text/css\n" + "Connection: close\n\n"; + +const char * replyFooter= "\n\n"; + +send(outerSocket, replyHeader, strlen(replyHeader), 0); +send(outerSocket, css, strlen(css), 0); +send(outerSocket, replyFooter, strlen(replyFooter), 0); + +return 0; +} +//--------------------------------------------------------------------------- +void WEB::SetDirName(const string & dn, int n) +{ +web->dirName[n] = dn; +} +//--------------------------------------------------------------------------- +void WEB::AddMessage(const string & message, int type) +{ +time_t t = time(NULL); +STG_MESSAGE m; + +m.msg = message; +m.type = type; +m.recvTime = ctime(&t); + +messages.push_back(m); + +if (messages.size() > MAX_MESSAGES) + messages.pop_front(); + +} +//--------------------------------------------------------------------------- +void WEB::UpdateStat(const LOADSTAT & ls) +{ +memcpy((void*)&(WEB::ls), &ls, sizeof(LOADSTAT)); +} +//--------------------------------------------------------------------------- + diff --git a/projects/sgauth/web.h b/projects/sgauth/web.h new file mode 100644 index 00000000..b734f16b --- /dev/null +++ b/projects/sgauth/web.h @@ -0,0 +1,101 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.3 $ + $Date: 2007/12/17 08:39:08 $ + */ + +#ifndef WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include + +#include "stg_const.h" +#include "ia_packets.h" + +using namespace std; + +#define MAX_MESSAGES (10) +//----------------------------------------------------------------------------- +struct STG_MESSAGE +{ +string msg; +string recvTime; +int type; +}; +//----------------------------------------------------------------------------- +class WEB +{ +public: + WEB(); + void Run(); + void SetDirName(const string & dn, int n); + void SetRefreshPagePeriod(int p); + void SetListenAddr(uint32_t ip); + void AddMessage(const string & message, int type); + void UpdateStat(const LOADSTAT & ls); + void Start(); +private: + + void PrepareNet(); + + #ifdef WIN32 + WSADATA wsaData; + #else + pthread_t thread; + #endif + + int SendReply(); + int SendCSS(); + int Redirect(const char * url); + + string dirName[DIR_NUM]; + int res; + int listenSocket; + int outerSocket; + struct sockaddr_in listenAddr; + struct sockaddr_in outerAddr; + int refreshPeriod; + + uint32_t listenWebAddr; + LOADSTAT ls; + + list messages; + + #ifndef WIN32 + socklen_t outerAddrLen; + #else + int outerAddrLen; + #endif +}; +//----------------------------------------------------------------------------- + diff --git a/projects/sgconf/CHANGES b/projects/sgconf/CHANGES new file mode 100644 index 00000000..e69de29b diff --git a/projects/sgconf/Makefile b/projects/sgconf/Makefile new file mode 100644 index 00000000..f9f0042b --- /dev/null +++ b/projects/sgconf/Makefile @@ -0,0 +1,99 @@ +############################################################################### +# $Id: Makefile,v 1.21 2010/02/11 12:34:14 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +PROG = sgconf + +SRCS = ./main.cpp \ + ./common_sg.cpp + +STGLIBS = -lconffiles \ + -lstg_common \ + -lstg_crypto \ + -lsrvconf + +LIBS += -lexpat \ + $(LIB_THREAD) + +ifeq ($(OS),linux) +LIBS += -ldl +else +LIBS += -lc \ + -liconv +endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +ifeq ($(OS),bsd) +SEARCH_DIRS += -I/usr/local/include +CXXFLAGS += -DHAVE_DECL_GETOPT=1 +endif + +ifeq ($(OS),bsd5) +SEARCH_DIRS += -I/usr/local/include +CXXFLAGS += -DHAVE_DECL_GETOPT=1 +endif + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +CXXFLAGS += -Wall +LDFLAGS += -Wl,-E -L$(DIR_LIB) -Wl,-rpath,$(PREFIX)/usr/lib/stg -Wl,-rpath-link,$(DIR_LIB) + +vpath %.so $(DIR_LIB) + +.PHONY: all clean distclean libs install uninstall install-bin install-data uninstall-bin uninstall-data +all: libs $(PROG) ../../Makefile.conf + +libs: + $(MAKE) -C $(DIR_LIBSRC) + +$(PROG): $(OBJS) $(STGLIBS) + $(CC) $^ $(LDFLAGS) $(LIBS) -o $(PROG) + +clean: + rm -f deps $(PROG) *.o tags *.*~ .OS + rm -f .OS + rm -f .store + rm -f .db.sql + rm -f core* + $(MAKE) -C $(DIR_LIBSRC) clean + +distclean: clean + rm -f ../../Makefile.conf + +install: install-bin install-data + +install-bin: + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/sbin + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/sbin/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) install + +install-data: + +uninstall: uninstall-bin uninstall-data + +uninstall-bin: + rm -f $(PREFIX)/usr/sbin/$(PROG) + +uninstall-data: + + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf + $(MAKE) -C $(DIR_LIBSRC) includes + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) -MM $$file` Makefile" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/sgconf/README.txt b/projects/sgconf/README.txt new file mode 100644 index 00000000..cbdf9aca --- /dev/null +++ b/projects/sgconf/README.txt @@ -0,0 +1,3 @@ +Compiling: +> ./build + diff --git a/projects/sgconf/build b/projects/sgconf/build new file mode 100755 index 00000000..434d6b03 --- /dev/null +++ b/projects/sgconf/build @@ -0,0 +1,161 @@ +#!/bin/sh + +# $Author: faust $ +# $Revision: 1.21 $ +# $Date: 2010/04/14 08:59:11 $ +###################################################### + +OS=unknown +sys=`uname -s` +release=`uname -r | cut -b1` +BUILD_DIR=`pwd` +CONFFILE="../../Makefile.conf" +PREFIX="/" +BIN_MODE=0755 +DATA_MODE=0644 +OWNER=root + +if [ -z $1 ] +then + MAKEOPTS="-j1" +else + if [ "$1" = "debug" ] + then + DEFS="-DDEBUG" + MAKEOPTS="-j1" + CXXFLAGS="$CXXFLAGS -g3 -W -Wall" + else + MAKEOPTS="-j1" + fi +fi + +CXXFLAGS="$CXXFLAGS -I/usr/local/include" +LDFLAGS="$LDFLAGS -L/usr/local/lib" + +if [ "$sys" = "Linux" ] +then + OS=linux + release="" + MAKE="make" +fi + +if [ "$sys" = "FreeBSD" ] +then + case $release in + 4) OS=bsd;; + 5) OS=bsd5;; + 6) OS=bsd5;; + 7) OS=bsd7;; + 8) OS=bsd7;; + *) OS=unknown;; + esac + MAKE="gmake" +fi + +if [ "$OS" = "unknown" ] +then + echo "#############################################################################" + echo "# Sorry, but sgconf currently supported by Linux, FreeBSD 4.x, 5.x, 6.x #" + echo "#############################################################################" + exit 1 +fi + +echo "#############################################################################" +echo " Building sgconf for $sys $release" +echo "#############################################################################" + +STG_LIBS="conffiles.lib + crypto.lib + common.lib + srvconf.lib" + +if [ "$OS" = "linux" ] +then + DEFS="$DEFS -DLINUX" + LIB_THREAD=-lpthread + SHELL="/bin/bash" +else + if [ "$OS" = "bsd" ] + then + DEFS="$DEFS -DFREE_BSD" + else + DEFS="$DEFS -DFREE_BSD5" + if [ "$OS" = "bsd7" ] + then + LIB_THREAD=-lpthread + else + LIB_THREAD=-lc_r + fi + fi + SHELL="/usr/local/bin/bash" +fi + +echo -n "Checking endianess... " +echo "int main() { int probe = 0x00000001; return *(char *)&probe; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS build_check.c -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + echo "FAIL!" + echo "Endianess checking failed" + exit; +else + ./fake + if [ $? = 1 ] + then + ARCH=le + CXXFLAGS="$CXXFLAGS -DARCH_LE" + echo "Little Endian" + else + ARCH=be + CXXFLAGS="$CXXFLAGS -DARCH_BE" + echo "Big Endian" + fi +fi +rm -f fake + +echo -n "Checking for -lexpat... " +echo "int main() { return 0; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS build_check.c -lexpat -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + CHECK_EXPAT=no + echo "no" +else + CHECK_EXPAT=yes + echo "yes" +fi +rm -f fake +rm -f build_check.c + +if [ "$CHECK_EXPAT" != "yes" ] +then + echo "-lexpat not found!" + exit 1 +fi + +echo "OS=$OS" > $CONFFILE +echo "STG_TIME=yes" >> $CONFFILE +echo "DIR_BUILD=$BUILD_DIR" >> $CONFFILE +echo "DIR_LIB=\$(DIR_BUILD)/../../lib" >> $CONFFILE +echo "DIR_LIBSRC=\$(DIR_BUILD)/../../stglibs" >> $CONFFILE +echo "DIR_INCLUDE=\$(DIR_BUILD)/../../include" >> $CONFFILE +echo "ARCH=$ARCH" >> $CONFFILE +echo "CHECK_EXPAT=$CHECK_EXPAT" >> $CONFFILE +echo "DEFS=$DEFS" >> $CONFFILE +echo -n "STG_LIBS=" >> $CONFFILE +for lib in $STG_LIBS +do + echo -n "$lib " >> $CONFFILE +done +echo "" >> $CONFFILE +echo "LIB_THREAD=$LIB_THREAD" >> $CONFFILE +echo "SHELL=$SHELL" >> $CONFFILE +echo "CXXFLAGS=$CXXFLAGS" >> $CONFFILE +echo "LDFLAGS=$LDFLAGS" >> $CONFFILE +echo "PREFIX=$PREFIX" >> $CONFFILE +echo "BIN_MODE=$BIN_MODE" >> $CONFFILE +echo "DATA_MODE=$DATA_MODE" >> $CONFFILE +echo "OWNER=$OWNER" >> $CONFFILE + +$MAKE $MAKEOPTS + diff --git a/projects/sgconf/common_sg.cpp b/projects/sgconf/common_sg.cpp new file mode 100644 index 00000000..77d7476f --- /dev/null +++ b/projects/sgconf/common_sg.cpp @@ -0,0 +1,479 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: faust $ + $Revision: 1.12 $ + $Date: 2009/06/08 10:02:28 $ + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common_sg.h" +#include "version_sg.h" +#include "common.h" +#include "sg_error_codes.h" + +using namespace std; + +const int usageConf = 0; +const int usageInfo = 1; + +const int TO_KOI8 = 0; +const int FROM_KOI8 = 1; +//----------------------------------------------------------------------------- +struct GetUserCbData +{ + void * data; + bool * result; +}; +//--------------------------------------------------------------------------- +struct HelpParams +{ + string setActionName; + string getActionName; + string valueName; + string valueParam; +}; +//--------------------------------------------------------------------------- +void Usage(int usageType) +{ +printf("Sgconf version: %s\n\n", VERSION_SG); + +char action[4]; +if (usageType == usageConf) + strcpy(action, "set"); +else + strcpy(action, "get"); + +printf("To add or to set cash use:\n"); +printf("sgconf set -s -p -a -w -u -c \n"); +printf("sgconf set -s -p -a -w -u -v \n"); +printf("To get cash use:\n"); +printf("sgconf get -s -p -a -w -u -c\n\n"); + +HelpParams hp[] = +{ + {"set tariff", "get tariff", "-t", ""}, + {"set credit", "get credit", "-r", ""}, + {"set password", "get password", "-o", ""}, + {"set prepaid traffic", "get prepaid traffic", "-e", ""}, + {"set IP-addresses", "get IP-addresses", "-I", "<*|ip_addr[,ip_addr...]>"}, + {"set name", "get name", "-A", ""}, + {"set note", "get note", "-N", ""}, + {"set street address", "get street address", "-D", "

"}, + {"set email", "get email", "-L", ""}, + {"set phone", "get phone", "-P", ""}, + {"set group", "get group", "-G", ""}, + {"set/unset down", "get down", "-d", "<0/1>"}, + {"set/unset \'passive\'", "get \'passive\'", "-i", "<0/1>"}, + {"set/unset \'disableDetailStat\'", "get \'disableDetailStat\'", "--disable-stat", "<0/1>"}, + {"set/unset \'alwaysOnline\'", "get \'alwaysOnline\'", "--always-online", "<0/1>"}, +}; + +for (unsigned i = 0; i < sizeof(hp) / sizeof(HelpParams); i++) + { + printf("To %s use:\n", hp[i].setActionName.c_str()); + printf("sgconf set -s -p -a -w -u %s %s\n", + hp[i].valueName.c_str(), hp[i].valueParam.c_str()); + printf("To %s use:\n", hp[i].getActionName.c_str()); + printf("sgconf get -s -p -a -w -u %s\n\n", + hp[i].valueName.c_str()); + } + +printf("To set user\'s upload traffic value use:\n"); +printf("sgconf set -s -p -a -w -u --u0 [--u1 ...]\n"); +printf("To get user\'s upload traffic value use:\n"); +printf("sgconf get -s -p -a -w -u --u0 [--u1 ...]\n\n"); + +printf("To set user\'s download traffic value use:\n"); +printf("sgconf set -s -p -a -w -u --d0 [--d1 ...]\n"); +printf("To get user\'s download traffic value use:\n"); +printf("sgconf get -s -p -a -w -u --d0 [--d1 ...]\n\n"); + +printf("To set userdata<0...9> use:\n"); +printf("sgconf set -s -p -a -w -u --ud0 [--ud1 ...]\n"); +printf("To get userdata<0...9> use:\n"); +printf("sgconf get -s -p -a -w -u --ud0 [--ud1 ...]\n\n"); + +printf("To send message use:\n"); +printf("sgconf set -s -p -a -w -u -m \n\n"); + +printf("To create user use:\n"); +printf("sgconf set -s -p -a -w -u -n\n\n"); + +printf("To delete user use:\n"); +printf("sgconf set -s -p -a -w -u -l\n\n"); +} +//--------------------------------------------------------------------------- +void UsageConf() +{ +Usage(usageConf); +} +//--------------------------------------------------------------------------- +void UsageInfo() +{ +Usage(usageInfo); +} +//--------------------------------------------------------------------------- +int CheckLogin(const char * login) +{ +for (int i = 0; i < (int)strlen(login); i++) + { + if (!(( login[i] >= 'a' && login[i] <= 'z') + || (login[i] >= 'A' && login[i] <= 'Z') + || (login[i] >= '0' && login[i] <= '9') + || login[i] == '_' + || login[i] == '-')) + { + return 1; + } + } +return 0; +} +//----------------------------------------------------------------------------- +short int ParseServerPort(const char * p) +{ +int port; +if (str2x(p, port) != 0) + { + printf("Incorresct server port %s\n", p); + exit(NETWORK_ERR_CODE); + } +return (short)port; +} +//----------------------------------------------------------------------------- +char * ParseAdminLogin(char * adm) +{ +if (CheckLogin(adm)) + { + printf("Incorresct admin login %s\n", adm); + exit(PARAMETER_PARSING_ERR_CODE); + } +return adm; +} +//----------------------------------------------------------------------------- +char * ParsePassword(char * pass) +{ +if (strlen(pass) >= ADM_PASSWD_LEN) + { + printf("Password too big %s\n", pass); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return pass; +} +//----------------------------------------------------------------------------- +char * ParseUser(char * usr) +{ +if (CheckLogin(usr)) + { + printf("Incorresct user login %s\n", usr); + exit(PARAMETER_PARSING_ERR_CODE); + } +return usr; +} +//----------------------------------------------------------------------------- +void ConvertKOI8(const string & src, string * dst, int encType) +{ +iconv_t cd; +char * ob = new char[src.size() * 2 + 1]; +char * ib = new char[src.size() + 1]; + +strcpy(ib, src.c_str()); + +char * outbuf = ob; +char * inbuf = ib; + +setlocale(LC_ALL, ""); + +char charsetF[100]; +char charsetT[100]; + +if (encType == TO_KOI8) + { + strcpy(charsetF, nl_langinfo(CODESET)); + strcpy(charsetT, "koi8-r"); + } +else + { + strcpy(charsetT, nl_langinfo(CODESET)); + strcpy(charsetF, "koi8-r"); + } + +size_t nconv = 1; + +size_t insize = strlen(ib); +size_t outsize = insize * 2 + 1; + +insize = src.size(); + +cd = iconv_open(charsetT, charsetF); +if (cd == (iconv_t) -1) + { + if (errno != EINVAL) + printf("error iconv_open\n"); + else + { + printf("Warning: iconv from %s to %s failed\n", charsetF, charsetT); + *dst = src; + return; + } + + exit(ICONV_ERR_CODE); + } + +#if defined(FREE_BSD) || defined(FREE_BSD5) +nconv = iconv(cd, (const char **)&inbuf, &insize, &outbuf, &outsize); +#else +nconv = iconv(cd, &inbuf, &insize, &outbuf, &outsize); +#endif +//printf("charsetT=%s charsetF=%s\n", charsetT, charsetF); +//printf("ib=%s ob=%s\n", ib, ob); +//printf("nconv=%d outsize=%d\n", nconv, outsize); +if (nconv == (size_t) -1) + { + if (errno != EINVAL) + { + printf("iconv error\n"); + exit(ICONV_ERR_CODE); + } + } + +*outbuf = L'\0'; + +iconv_close(cd); +*dst = ob; + +delete[] ob; +delete[] ib; +} +//----------------------------------------------------------------------------- +void ConvertFromKOI8(const string & src, string * dst) +{ +ConvertKOI8(src, dst, FROM_KOI8); +} +//----------------------------------------------------------------------------- +void ConvertToKOI8(const string & src, string * dst) +{ +ConvertKOI8(src, dst, TO_KOI8); +} +//----------------------------------------------------------------------------- +int RecvSetUserAnswer(const char * ans, void * d) +{ +GetUserCbData * gucbd; +gucbd = (GetUserCbData *)d; + +bool * result = gucbd->result; + +//REQUEST * req = (REQUEST *)gucbd->data; + +//printf("ans=%s\n", ans); +if (strcasecmp("Ok", ans) == 0) + *result = true; +else + *result = false; + +return 0; +} +//----------------------------------------------------------------------------- +struct StringReqParams +{ + string name; + RESETABLE reqParam; + string * value; +}; +//----------------------------------------------------------------------------- +void RecvUserData(USERDATA * ud, void * d) +{ +GetUserCbData * gucbd; +gucbd = (GetUserCbData *)d; + +bool * result = gucbd->result; + +REQUEST * req = (REQUEST *)gucbd->data; + +if (ud->login == "") + { + *result = false; + return; + } + +if (!req->cash.res_empty()) + cout << "cash=" << ud->cash << endl; + +if (!req->credit.res_empty()) + cout << "credit=" << ud->credit << endl; + +if (!req->down.res_empty()) + cout << "down=" << ud->down << endl; + +if (!req->passive.res_empty()) + cout << "passive=" << ud->passive << endl; + +if (!req->disableDetailStat.res_empty()) + cout << "disableDetailStat=" << ud->disableDetailStat << endl; + +if (!req->alwaysOnline.res_empty()) + cout << "alwaysOnline=" << ud->alwaysOnline << endl; + +if (!req->prepaidTraff.res_empty()) + cout << "prepaidTraff=" << ud->prepaidTraff << endl; + +for (int i = 0; i < DIR_NUM; i++) + { + if (!req->u[i].res_empty()) + cout << "u" << i << "=" << ud->stat.mu[i] << endl; + if (!req->d[i].res_empty()) + cout << "d" << i << "=" << ud->stat.md[i] << endl; + } + +for (int i = 0; i < USERDATA_NUM; i++) + { + if (!req->ud[i].res_empty()) + { + string str; + ConvertFromKOI8(ud->userData[i], &str); + cout << "userdata" << i << "=" << str << endl; + } + } + +StringReqParams strReqParams[] = +{ + {"note", req->note, &ud->note}, + {"name", req->name, &ud->name}, + {"address", req->address, &ud->address}, + {"email", req->email, &ud->email}, + {"phone", req->phone, &ud->phone}, + {"group", req->group, &ud->group}, + {"tariff", req->tariff, &ud->tariff}, + {"password", req->usrPasswd, &ud->password}, + {"ip", req->ips, &ud->ips} // IP-address of user +}; +for (unsigned i = 0; i < sizeof(strReqParams) / sizeof(StringReqParams); i++) + { + if (!strReqParams[i].reqParam.res_empty()) + { + string str; + ConvertFromKOI8(*strReqParams[i].value, &str); + cout << strReqParams[i].name << "=" << str << endl; + } + } +*result = true; +} +//----------------------------------------------------------------------------- +int ProcessSetUser(const std::string &server, + int port, + const std::string &admLogin, + const std::string &admPasswd, + const std::string &str, + void * data, + bool isMessage) +{ +SERVCONF sc; + +bool result = false; + + +sc.SetServer(server.c_str()); // õÓÔÁÎÁ×ÌÉ×ÁÅÍ ÉÍÑ ÓÅÒ×ÅÒÁ Ó ËÏÔÏÒÇÏ ÚÁÂÉÒÁÔØ ÉÎÆÕ +sc.SetPort(port); // ÁÄÍÉÎÓËÉÊ ÐÏÒÔ ÓÅÒ×ÅÒÁÐÏÒÔ +sc.SetAdmLogin(admLogin.c_str()); // ÷ÙÓÔÁ×ÌÑÅÍ ÌÏÇÉÎ É ÐÁÒÏÌØ ÁÄÍÉÎÁ +sc.SetAdmPassword(admPasswd.c_str()); + +// TODO Good variable name :) +GetUserCbData gucbd; + +gucbd.data = data; +gucbd.result = &result; + +if (isMessage) + { + sc.SetSendMessageCb(RecvSetUserAnswer, &gucbd); + sc.MsgUser(str.c_str()); + } +else + { + sc.SetChgUserCb(RecvSetUserAnswer, &gucbd); + sc.ChgUser(str.c_str()); + } + +if (result) + { + printf("Ok\n"); + return 0; + } +else + { + printf("Error\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int ProcessGetUser(const std::string &server, + int port, + const std::string &admLogin, + const std::string &admPasswd, + const std::string &login, + void * data) +{ +SERVCONF sc; + +bool result = false; + +sc.SetServer(server.c_str()); // õÓÔÁÎÁ×ÌÉ×ÁÅÍ ÉÍÑ ÓÅÒ×ÅÒÁ Ó ËÏÔÏÒÇÏ ÚÁÂÉÒÁÔØ ÉÎÆÕ +sc.SetPort(port); // ÁÄÍÉÎÓËÉÊ ÐÏÒÔ ÓÅÒ×ÅÒÁÐÏÒÔ +sc.SetAdmLogin(admLogin.c_str()); // ÷ÙÓÔÁ×ÌÑÅÍ ÌÏÇÉÎ É ÐÁÒÏÌØ ÁÄÍÉÎÁ +sc.SetAdmPassword(admPasswd.c_str()); + +// TODO Good variable name :) +GetUserCbData gucbd; + +gucbd.data = data; +gucbd.result = &result; + +sc.SetGetUserDataRecvCb(RecvUserData, &gucbd); +sc.GetUser(login.c_str()); + +if (result) + { + printf("Ok\n"); + return 0; + } +else + { + printf("Error\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + + diff --git a/projects/sgconf/common_sg.h b/projects/sgconf/common_sg.h new file mode 100644 index 00000000..d36ed750 --- /dev/null +++ b/projects/sgconf/common_sg.h @@ -0,0 +1,64 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: faust $ + $Revision: 1.5 $ + $Date: 2009/06/08 10:02:28 $ + */ + + +#ifndef COMMON_SG_H +#define COMMON_SG_H + +#include + +#include "servconf.h" +#include "request.h" + +void UsageConf(); +void UsageInfo(); + +char * ParseUser(char * usr); +char * ParsePassword(char * pass); +char * ParseAdminLogin(char * adm); +short int ParseServerPort(const char * p); +void ParseAnyString(const char * c, string * msg, const char * enc = "cp1251"); +int CheckLogin(const char * login); +void ConvertFromKOI8(const std::string & src, std::string * dst); +void ConvertToKOI8(const std::string & src, std::string * dst); + +int ProcessGetUser(const std::string &server, + int port, + const std::string &admLogin, + const std::string &admPasswd, + const std::string &login, + void * data); + +int ProcessSetUser(const std::string &server, + int port, + const std::string &admLogin, + const std::string &admPasswd, + const std::string &str, + void * data, + bool isMessage = false); + +#endif + diff --git a/projects/sgconf/main.cpp b/projects/sgconf/main.cpp new file mode 100644 index 00000000..d2671be4 --- /dev/null +++ b/projects/sgconf/main.cpp @@ -0,0 +1,1095 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: faust $ + $Revision: 1.25 $ + $Date: 2010/03/25 14:37:43 $ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "request.h" +#include "common.h" +#include "netunit.h" +#include "common_sg.h" +#include "sg_error_codes.h" + +using namespace std; + +time_t stgTime; + +int ParseReplyGet(void * data, list * ans); +//int ParseReplySet(void * data, list * ans); + +struct option long_options_get[] = { +{"server", 1, 0, 's'}, //Server +{"port", 1, 0, 'p'}, //Port +{"admin", 1, 0, 'a'}, //Admin +{"admin_pass", 1, 0, 'w'}, //passWord +{"user", 1, 0, 'u'}, //User +{"addcash", 0, 0, 'c'}, //Add Cash +//{"setcash", 0, 0, 'v'}, //Set Cash +{"credit", 0, 0, 'r'}, //cRedit +{"tariff", 0, 0, 't'}, //Tariff +{"message", 0, 0, 'm'}, //message +{"password", 0, 0, 'o'}, //password +{"down", 0, 0, 'd'}, //down +{"passive", 0, 0, 'i'}, //passive +{"disable-stat",0, 0, 'S'}, //disable detail stat +{"always-online",0, 0, 'O'}, //always online +{"u0", 0, 0, 500}, //U0 +{"u1", 0, 0, 501}, //U1 +{"u2", 0, 0, 502}, //U2 +{"u3", 0, 0, 503}, //U3 +{"u4", 0, 0, 504}, //U4 +{"u5", 0, 0, 505}, //U5 +{"u6", 0, 0, 506}, //U6 +{"u7", 0, 0, 507}, //U7 +{"u8", 0, 0, 508}, //U8 +{"u9", 0, 0, 509}, //U9 +{"d0", 0, 0, 600}, //D0 +{"d1", 0, 0, 601}, //D1 +{"d2", 0, 0, 602}, //D2 +{"d3", 0, 0, 603}, //D3 +{"d4", 0, 0, 604}, //D4 +{"d5", 0, 0, 605}, //D5 +{"d6", 0, 0, 606}, //D6 +{"d7", 0, 0, 607}, //D7 +{"d8", 0, 0, 608}, //D8 +{"d9", 0, 0, 609}, //D9 + +{"ud0", 0, 0, 700}, //UserData0 +{"ud1", 0, 0, 701}, //UserData1 +{"ud2", 0, 0, 702}, //UserData2 +{"ud3", 0, 0, 703}, //UserData3 +{"ud4", 0, 0, 704}, //UserData4 +{"ud5", 0, 0, 705}, //UserData5 +{"ud6", 0, 0, 706}, //UserData6 +{"ud7", 0, 0, 707}, //UserData7 +{"ud8", 0, 0, 708}, //UserData8 +{"ud9", 0, 0, 709}, //UserData9 + +{"prepaid", 0, 0, 'e'}, //prepaid traff +{"create", 0, 0, 'n'}, //create +{"delete", 0, 0, 'l'}, //delete + +{"note", 0, 0, 'N'}, //Note +{"name", 0, 0, 'A'}, //nAme +{"address", 0, 0, 'D'}, //aDdress +{"email", 0, 0, 'L'}, //emaiL +{"phone", 0, 0, 'P'}, //phone +{"group", 0, 0, 'G'}, //Group +{"ip", 0, 0, 'I'}, //IP-address of user + +{0, 0, 0, 0}}; + +struct option long_options_set[] = { +{"server", 1, 0, 's'}, //Server +{"port", 1, 0, 'p'}, //Port +{"admin", 1, 0, 'a'}, //Admin +{"admin_pass", 1, 0, 'w'}, //passWord +{"user", 1, 0, 'u'}, //User +{"addcash", 1, 0, 'c'}, //Add Cash +{"setcash", 1, 0, 'v'}, //Set Cash +{"credit", 1, 0, 'r'}, //cRedit +{"tariff", 1, 0, 't'}, //Tariff +{"message", 1, 0, 'm'}, //message +{"password", 1, 0, 'o'}, //password +{"down", 1, 0, 'd'}, //down +{"passive", 1, 0, 'i'}, //passive +{"disable-stat",1, 0, 'S'}, //disable detail stat +{"always-online",1, 0, 'O'}, //always online +{"u0", 1, 0, 500}, //U0 +{"u1", 1, 0, 501}, //U1 +{"u2", 1, 0, 502}, //U2 +{"u3", 1, 0, 503}, //U3 +{"u4", 1, 0, 504}, //U4 +{"u5", 1, 0, 505}, //U5 +{"u6", 1, 0, 506}, //U6 +{"u7", 1, 0, 507}, //U7 +{"u8", 1, 0, 508}, //U8 +{"u9", 1, 0, 509}, //U9 +{"d0", 1, 0, 600}, //D0 +{"d1", 1, 0, 601}, //D1 +{"d2", 1, 0, 602}, //D2 +{"d3", 1, 0, 603}, //D3 +{"d4", 1, 0, 604}, //D4 +{"d5", 1, 0, 605}, //D5 +{"d6", 1, 0, 606}, //D6 +{"d7", 1, 0, 607}, //D7 +{"d8", 1, 0, 608}, //D8 +{"d9", 1, 0, 609}, //D9 + +{"ud0", 1, 0, 700}, //UserData +{"ud1", 1, 0, 701}, //UserData1 +{"ud2", 1, 0, 702}, //UserData2 +{"ud3", 1, 0, 703}, //UserData3 +{"ud4", 1, 0, 704}, //UserData4 +{"ud5", 1, 0, 705}, //UserData5 +{"ud6", 1, 0, 706}, //UserData6 +{"ud7", 1, 0, 707}, //UserData7 +{"ud8", 1, 0, 708}, //UserData8 +{"ud9", 1, 0, 709}, //UserData9 + +{"prepaid", 1, 0, 'e'}, //prepaid traff +{"create", 1, 0, 'n'}, //create +{"delete", 1, 0, 'l'}, //delete + +{"note", 1, 0, 'N'}, //Note +{"name", 1, 0, 'A'}, //nAme +{"address", 1, 0, 'D'}, //aDdress +{"email", 1, 0, 'L'}, //emaiL +{"phone", 1, 0, 'P'}, //phone +{"group", 1, 0, 'G'}, //Group +{"ip", 0, 0, 'I'}, //IP-address of user + +{0, 0, 0, 0}}; + +//----------------------------------------------------------------------------- +double ParseCash(const char * c, string * message) +{ +//-c 123.45:log message +double cash; +char * msg; +char * str; +str = new char[strlen(c)]; + +strcpy(str, c); +msg = strchr(str, ':'); + +if (msg) + { + *message = msg + 1; + str[msg - str] = 0; + } +else + *message = ""; + +if (strtodouble2(str, cash) != 0) + { + printf("Incorrect cash value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +delete[] str; +return cash; +} +//----------------------------------------------------------------------------- +double ParseCredit(const char * c) +{ +double credit; +if (strtodouble2(c, credit) != 0) + { + printf("Incorrect credit value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return credit; +} +//----------------------------------------------------------------------------- +double ParsePrepaidTraffic(const char * c) +{ +double credit; +if (strtodouble2(c, credit) != 0) + { + printf("Incorrect prepaid traffic value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return credit; +} +//----------------------------------------------------------------------------- +int64_t ParseTraff(const char * c) +{ +int64_t traff; +if (str2x(c, traff) != 0) + { + printf("Incorrect credit value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return traff; +} +//----------------------------------------------------------------------------- +bool ParseDownPassive(const char * dp) +{ +if (!(dp[1] == 0 && (dp[0] == '1' || dp[0] == '0'))) + { + printf("Incorrect value %s\n", dp); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return dp[0] - '0'; +} +//----------------------------------------------------------------------------- +string ParseTariff(const char * t, int &chgType) +{ +int l = strlen(t); +char * s; +s = new char[l]; +char * s1, * s2; +string ss; + +strcpy(s, t); + +s1 = strtok(s, ":"); + +if (strlen(s1) >= TARIFF_NAME_LEN) + { + printf("Tariff name too big %s\n", s1); + exit(PARAMETER_PARSING_ERR_CODE); + } + +//*tariff = s; + +if (CheckLogin(s1)) + { + printf("Incorrect tariff value %s\n", t); + exit(PARAMETER_PARSING_ERR_CODE); + } + +s2 = strtok(NULL, ":"); + +chgType = -1; + +if (s2 == NULL) + { + chgType = TARIFF_NOW; + ss = s; + delete[] s; + return ss; + } + + +if (strcmp(s2, "now") == 0) + chgType = TARIFF_NOW; + +if (strcmp(s2, "delayed") == 0) + chgType = TARIFF_DEL; + +if (strcmp(s2, "recalc") == 0) + chgType = TARIFF_REC; + +if (chgType < 0) + { + printf("Incorrect tariff value %s\n", t); + exit(PARAMETER_PARSING_ERR_CODE); + } + +ss = s; +delete[] s; +return ss; +} +//----------------------------------------------------------------------------- +void ParseAnyString(const char * c, string * msg, const char * enc) +{ +iconv_t cd; +char * ob = new char[strlen(c) + 1]; +char * ib = new char[strlen(c) + 1]; + +strcpy(ib, c); + +char * outbuf = ob; +char * inbuf = ib; + +setlocale(LC_ALL, ""); + +char charsetF[255]; +strncpy(charsetF, nl_langinfo(CODESET), 255); + +const char * charsetT = enc; + +size_t nconv = 1; + +size_t insize = strlen(ib); +size_t outsize = strlen(ib); + +insize = strlen(c); + +cd = iconv_open(charsetT, charsetF); +if (cd == (iconv_t) -1) + { + if (errno == EINVAL) + { + printf("Warning: iconv from %s to %s failed\n", charsetF, charsetT); + *msg = c; + return; + } + else + printf("error iconv_open\n"); + + exit(ICONV_ERR_CODE); + } + +#if defined(FREE_BSD) || defined(FREE_BSD5) +nconv = iconv (cd, (const char**)&inbuf, &insize, &outbuf, &outsize); +#else +nconv = iconv (cd, &inbuf, &insize, &outbuf, &outsize); +#endif +//printf("nconv=%d outsize=%d\n", nconv, outsize); +if (nconv == (size_t) -1) + { + if (errno != EINVAL) + { + printf("iconv error\n"); + exit(ICONV_ERR_CODE); + } + } + +*outbuf = L'\0'; + +iconv_close(cd); +*msg = ob; + +delete[] ob; +delete[] ib; +} +//----------------------------------------------------------------------------- +void CreateRequestSet(REQUEST * req, char * r) +{ +const int strLen = 10024; +char str[strLen]; +memset(str, 0, strLen); + +r[0] = 0; + +if (!req->usrMsg.res_empty()) + { + string msg; + Encode12str(msg, req->usrMsg); + sprintf(str, "", req->login.const_data().c_str(), msg.c_str()); + //sprintf(str, "\n", req->login, msg); + strcat(r, str); + return; + } + +if (req->deleteUser) + { + sprintf(str, "", req->login.const_data().c_str()); + strcat(r, str); + //printf("%s\n", r); + return; + } + +if (req->createUser) + { + sprintf(str, " ", req->login.const_data().c_str()); + strcat(r, str); + //printf("%s\n", r); + return; + } + +strcat(r, "\n"); +sprintf(str, "\n", req->login.const_data().c_str()); +strcat(r, str); +if (!req->credit.res_empty()) + { + sprintf(str, "\n", req->credit.const_data()); + strcat(r, str); + } + +if (!req->prepaidTraff.res_empty()) + { + sprintf(str, "\n", req->prepaidTraff.const_data()); + strcat(r, str); + } + +if (!req->cash.res_empty()) + { + string msg; + Encode12str(msg, req->message); + sprintf(str, "\n", req->cash.const_data(), msg.c_str()); + strcat(r, str); + } + +if (!req->setCash.res_empty()) + { + string msg; + Encode12str(msg, req->message); + sprintf(str, "\n", req->setCash.const_data(), msg.c_str()); + strcat(r, str); + } + +if (!req->usrPasswd.res_empty()) + { + sprintf(str, "\n", req->usrPasswd.const_data().c_str()); + strcat(r, str); + } + +if (!req->down.res_empty()) + { + sprintf(str, "\n", req->down.const_data()); + strcat(r, str); + } + +if (!req->passive.res_empty()) + { + sprintf(str, "\n", req->passive.const_data()); + strcat(r, str); + } + +if (!req->disableDetailStat.res_empty()) + { + sprintf(str, "\n", req->disableDetailStat.const_data()); + strcat(r, str); + } + +if (!req->alwaysOnline.res_empty()) + { + sprintf(str, "\n", req->alwaysOnline.const_data()); + strcat(r, str); + } + +// IP-address of user +if (!req->ips.res_empty()) + { + sprintf(str, "\n", req->ips.const_data().c_str()); + strcat(r, str); + } + +int uPresent = false; +int dPresent = false; +for (int i = 0; i < DIR_NUM; i++) + { + if (!req->u[i].res_empty()) + { + if (!uPresent && !dPresent) + { + sprintf(str, "u[i].const_data(); + //sprintf(str, "MU%d=\"%lld\" ", i, req->u[i].const_data()); + sprintf(str, "MU%d=\"%s\" ", i, ss.str().c_str()); + strcat(r, str); + } + if (!req->d[i].res_empty()) + { + if (!uPresent && !dPresent) + { + sprintf(str, "d[i].const_data(); + sprintf(str, "MD%d=\"%s\" ", i, ss.str().c_str()); + strcat(r, str); + } + } +if (uPresent || dPresent) + { + strcat(r, "/>"); + } + +//printf("%s\n", r); + +if (!req->tariff.res_empty()) + { + switch (req->chgTariff) + { + case TARIFF_NOW: + sprintf(str, "\n", req->tariff.const_data().c_str()); + strcat(r, str); + break; + case TARIFF_REC: + sprintf(str, "\n", req->tariff.const_data().c_str()); + strcat(r, str); + break; + case TARIFF_DEL: + sprintf(str, "\n", req->tariff.const_data().c_str()); + strcat(r, str); + break; + } + + } + +if (!req->note.res_empty()) + { + string note; + Encode12str(note, req->note); + sprintf(str, "", note.c_str()); + strcat(r, str); + } + +if (!req->name.res_empty()) + { + string name; + Encode12str(name, req->name); + sprintf(str, "", name.c_str()); + strcat(r, str); + } + +if (!req->address.res_empty()) + { + string address; + Encode12str(address, req->address); + sprintf(str, "
", address.c_str()); + strcat(r, str); + } + +if (!req->email.res_empty()) + { + string email; + Encode12str(email, req->email); + sprintf(str, "", email.c_str()); + strcat(r, str); + } + +if (!req->phone.res_empty()) + { + string phone; + Encode12str(phone, req->phone); + sprintf(str, "", phone.c_str()); + strcat(r, str); + } + +if (!req->group.res_empty()) + { + string group; + Encode12str(group, req->group); + sprintf(str, "", group.c_str()); + strcat(r, str); + } + +for (int i = 0; i < USERDATA_NUM; i++) + { + if (!req->ud[i].res_empty()) + { + string ud; + Encode12str(ud, req->ud[i]); + sprintf(str, "", i, ud.c_str()); + strcat(r, str); + } + } + +strcat(r, "\n"); +} +//----------------------------------------------------------------------------- +int CheckParameters(REQUEST * req) +{ +int u = false; +int d = false; +int ud = false; +int a = !req->admLogin.res_empty() + && !req->admPasswd.res_empty() + && !req->server.res_empty() + && !req->port.res_empty() + && !req->login.res_empty(); + +int b = !req->cash.res_empty() + || !req->setCash.res_empty() + || !req->credit.res_empty() + || !req->prepaidTraff.res_empty() + || !req->tariff.res_empty() + || !req->usrMsg.res_empty() + || !req->usrPasswd.res_empty() + + || !req->note.res_empty() + || !req->name.res_empty() + || !req->address.res_empty() + || !req->email.res_empty() + || !req->phone.res_empty() + || !req->group.res_empty() + || !req->ips.res_empty() // IP-address of user + + || !req->createUser + || !req->deleteUser; + + +for (int i = 0; i < DIR_NUM; i++) + { + if (req->u[i].res_empty()) + { + u = true; + break; + } + } + +for (int i = 0; i < DIR_NUM; i++) + { + if (req->d[i].res_empty()) + { + d = true; + break; + } + } + +for (int i = 0; i < DIR_NUM; i++) + { + if (req->ud[i].res_empty()) + { + ud = true; + break; + } + } + + +//printf("a=%d, b=%d, u=%d, d=%d ud=%d\n", a, b, u, d, ud); +return a && (b || u || d || ud); +} +//----------------------------------------------------------------------------- +int CheckParametersGet(REQUEST * req) +{ +return CheckParameters(req); +} +//----------------------------------------------------------------------------- +int CheckParametersSet(REQUEST * req) +{ +return CheckParameters(req); +} +//----------------------------------------------------------------------------- +int mainGet(int argc, char **argv) +{ +int c; +REQUEST req; +RESETABLE t1; +int missedOptionArg = false; + +const char * short_options_get = "s:p:a:w:u:crtmodieNADLPGISO"; +int option_index = -1; + +while (1) + { + option_index = -1; + c = getopt_long(argc, argv, short_options_get, long_options_get, &option_index); + if (c == -1) + break; + + switch (c) + { + case 's': //server + req.server = optarg; + break; + + case 'p': //port + req.port = ParseServerPort(optarg); + //req.portReq = 1; + break; + + case 'a': //admin + req.admLogin = ParseAdminLogin(optarg); + break; + + case 'w': //admin password + req.admPasswd = ParsePassword(optarg); + break; + + case 'o': //change user password + req.usrPasswd = " "; + break; + + case 'u': //user + req.login = ParseUser(optarg); + break; + + case 'c': //get cash + req.cash = 1; + break; + + case 'r': //credit + req.credit = 1; + break; + + case 'd': //down + req.down = 1; + break; + + case 'i': //passive + req.passive = 1; + break; + + case 't': //tariff + req.tariff = " "; + break; + + case 'e': //Prepaid Traffic + req.prepaidTraff = 1; + break; + + case 'N': //Note + req.note = " "; + break; + + case 'A': //nAme + req.name = " "; + break; + + case 'D': //aDdress + req.address =" "; + break; + + case 'L': //emaiL + req.email = " "; + break; + + case 'P': //phone + req.phone = " "; + break; + + case 'G': //Group + req.group = " "; + break; + + case 'I': //IP-address of user + req.ips = " "; + break; + + case 'S': //Detail stat status + req.disableDetailStat = " "; + break; + + case 'O': //Always online status + req.alwaysOnline = " "; + break; + + case 500: //U + case 501: + case 502: + case 503: + case 504: + case 505: + case 506: + case 507: + case 508: + case 509: + //printf("U%d\n", c - 500); + req.u[c - 500] = 1; + break; + + case 600: //D + case 601: + case 602: + case 603: + case 604: + case 605: + case 606: + case 607: + case 608: + case 609: + //printf("D%d\n", c - 600); + req.d[c - 600] = 1; + break; + + case 700: //UserData + case 701: + case 702: + case 703: + case 704: + case 705: + case 706: + case 707: + case 708: + case 709: + //printf("UD%d\n", c - 700); + req.ud[c - 700] = " "; + break; + + case '?': + case ':': + //printf ("Unknown option \n"); + missedOptionArg = true; + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + +if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + UsageInfo(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +if (missedOptionArg || !CheckParametersGet(&req)) + { + //printf("Parameter needed\n"); + UsageInfo(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return ProcessGetUser(req.server, req.port, req.admLogin, req.admPasswd, req.login, &req); +} +//----------------------------------------------------------------------------- +int mainSet(int argc, char **argv) +{ +string str; + +int c; +bool isMessage = false; +REQUEST req; + +RESETABLE t1; + +const char * short_options_set = "s:p:a:w:u:c:r:t:m:o:d:i:e:v:nlN:A:D:L:P:G:I:S:O:"; + +int missedOptionArg = false; + +while (1) + { + int option_index = -1; + + c = getopt_long(argc, argv, short_options_set, long_options_set, &option_index); + + if (c == -1) + break; + + switch (c) + { + case 's': //server + req.server = optarg; + break; + + case 'p': //port + req.port = ParseServerPort(optarg); + //req.portReq = 1; + break; + + case 'a': //admin + req.admLogin = ParseAdminLogin(optarg); + break; + + case 'w': //admin password + req.admPasswd = ParsePassword(optarg); + break; + + case 'o': //change user password + req.usrPasswd = ParsePassword(optarg); + break; + + case 'u': //user + req.login = ParseUser(optarg); + break; + + case 'c': //add cash + req.cash = ParseCash(optarg, &req.message); + break; + + case 'v': //set cash + req.setCash = ParseCash(optarg, &req.message); + break; + + case 'r': //credit + req.credit = ParseCredit(optarg); + break; + + case 'd': //down + req.down = ParseDownPassive(optarg); + break; + + case 'i': //passive + req.passive = ParseDownPassive(optarg); + break; + + case 't': //tariff + req.tariff = ParseTariff(optarg, req.chgTariff); + break; + + case 'm': //message + ParseAnyString(optarg, &str); + req.usrMsg = str; + isMessage = true; + break; + + case 'e': //Prepaid Traffic + req.prepaidTraff = ParsePrepaidTraffic(optarg); + break; + + case 'n': //Create User + req.createUser = true; + break; + + case 'l': //Delete User + req.deleteUser = true; + break; + + case 'N': //Note + ParseAnyString(optarg, &str); + req.note = str; + break; + + case 'A': //nAme + ParseAnyString(optarg, &str, "koi8-r"); + req.name = str; + break; + + case 'D': //aDdress + ParseAnyString(optarg, &str); + req.address = str; + break; + + case 'L': //emaiL + ParseAnyString(optarg, &str); + req.email = str; + //printf("EMAIL=%s\n", optarg); + break; + + case 'P': //phone + ParseAnyString(optarg, &str); + req.phone = str; + break; + + case 'G': //Group + ParseAnyString(optarg, &str); + req.group = str; + break; + + case 'I': //IP-address of user + ParseAnyString(optarg, &str); + req.ips = str; + break; + + case 'S': + req.disableDetailStat = ParseDownPassive(optarg); + break; + + case 'O': + req.alwaysOnline = ParseDownPassive(optarg); + break; + + case 500: //U + case 501: + case 502: + case 503: + case 504: + case 505: + case 506: + case 507: + case 508: + case 509: + //printf("U%d\n", c - 500); + req.u[c - 500] = ParseTraff(optarg); + break; + + case 600: //D + case 601: + case 602: + case 603: + case 604: + case 605: + case 606: + case 607: + case 608: + case 609: + //printf("D%d\n", c - 600); + req.d[c - 600] = ParseTraff(optarg); + break; + + case 700: //UserData + case 701: + case 702: + case 703: + case 704: + case 705: + case 706: + case 707: + case 708: + case 709: + ParseAnyString(optarg, &str); + //printf("UD%d\n", c - 700); + req.ud[c - 700] = str; + break; + + case '?': + //printf("Missing option argument\n"); + missedOptionArg = true; + break; + + case ':': + //printf("Missing option argument\n"); + missedOptionArg = true; + break; + + default: + printf("?? getopt returned character code 0%o ??\n", c); + } + } + +if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +if (missedOptionArg || !CheckParametersSet(&req)) + { + //printf("Parameter needed\n"); + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +const int rLen = 20000; +char rstr[rLen]; +memset(rstr, 0, rLen); + +CreateRequestSet(&req, rstr); +return ProcessSetUser(req.server, req.port, req.admLogin, req.admPasswd, rstr, NULL, isMessage); +} +//----------------------------------------------------------------------------- +int main(int argc, char **argv) +{ +if (argc <= 2) + { + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +if (strcmp(argv[1], "get") == 0) + { + //printf("get\n"); + return mainGet(argc - 1, argv + 1); + } +else if (strcmp(argv[1], "set") == 0) + { + //printf("set\n"); + return mainSet(argc - 1, argv + 1); + } +else + { + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } +return UNKNOWN_ERR_CODE; +} +//----------------------------------------------------------------------------- + diff --git a/projects/sgconf/parser.cpp b/projects/sgconf/parser.cpp new file mode 100644 index 00000000..20dd0614 --- /dev/null +++ b/projects/sgconf/parser.cpp @@ -0,0 +1,128 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: nobunaga $ + $Revision: 1.4 $ + $Date: 2008/05/11 08:15:07 $ + */ + + +#include +#include +#include +#include + +#include +#include + +#include "common.h" +#include "netunit.h" +#include "request.h" + +using namespace std; + +int parse_depth = 0; +XML_Parser parser; +//--------------------------------------------------------------------------- +int ParseAns(void * data, const char *el, const char **attr) +{ +if (strcasecmp(attr[1], "ok") == 0) + { + return 0; + } +if (strcasecmp(attr[1], "error") == 0) + { + return 1; + } +if (strcasecmp(attr[1], "err") == 0) + { + return 1; + } +return -1; +} +//--------------------------------------------------------------------------- +void StartElement(void *data, const char *el, const char **attr) +{ +parse_depth++; +if (parse_depth == 1) + { + if (ParseAns(data, el, attr) < 0) + { + printf("Unexpected token\n"); + exit(UNKNOWN_ERR_CODE); + } + if (ParseAns(data, el, attr) == 1) + { + printf("User not found\n"); + exit(USER_NOT_FOUND_ERR_CODE); + } + return; + } +} +//----------------------------------------------------------------------------- +void EndElement(void *data, const char *el) +{ +parse_depth--; +} +//--------------------------------------------------------------------------- +int ParseReply(void * data, list * ans) +{ +int done = 0; +int len; + +parse_depth = 0; +parser = XML_ParserCreate(NULL); + +if (!parser) + { + printf("Couldn't allocate memory for parser\n"); + exit(UNKNOWN_ERR_CODE); + } + +XML_ParserReset(parser, NULL); +XML_SetElementHandler(parser, StartElement, EndElement); + +list::iterator n = ans->begin(); +while (n != ans->end()) + { + len = strlen(n->c_str()); + + if (++n == ans->end()) + done = 1; + n--; + + if (XML_Parse(parser, n->c_str(), len, done) == XML_STATUS_ERROR) + { + char s[128]; + printf(s, "Parse error at line %d:\n%s\n", + XML_GetCurrentLineNumber(parser), + XML_ErrorString(XML_GetErrorCode(parser))); + exit(UNKNOWN_ERR_CODE); + } + + ++n; + } + +XML_ParserFree(parser); +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/sgconf/request.h b/projects/sgconf/request.h new file mode 100644 index 00000000..dbd7d6db --- /dev/null +++ b/projects/sgconf/request.h @@ -0,0 +1,104 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: faust $ + $Revision: 1.14 $ + $Date: 2009/06/08 10:02:28 $ + */ + +#ifndef request_h +#define request_h + +#include +#include "resetable.h" +#include "stg_const.h" +#include "os_int.h" + +#define TARIFF_NOW (0) +#define TARIFF_DEL (1) +#define TARIFF_REC (2) + +using namespace std; +//----------------------------------------------------------------------------- +struct REQUEST +{ + +REQUEST() +{ + for (int i = 0; i < DIR_NUM; i++) + { + u[i].reset(); + d[i].reset(); + } + + for (int i = 0; i < USERDATA_NUM; i++) + ud[i].reset(); + + createUser = false; + deleteUser = false; +} + +RESETABLE server; +RESETABLE port; +RESETABLE admLogin; +RESETABLE admPasswd; +RESETABLE login; + +RESETABLE tariff; +int chgTariff; + +RESETABLE cash; +RESETABLE setCash; +string message; +bool createUser; +bool deleteUser; + +RESETABLE usrMsg; +RESETABLE credit; +RESETABLE usrPasswd; +RESETABLE down; +RESETABLE passive; +RESETABLE disableDetailStat; +RESETABLE alwaysOnline; +RESETABLE prepaidTraff; + +RESETABLE u[DIR_NUM]; +RESETABLE d[DIR_NUM]; + +RESETABLE ud[USERDATA_NUM]; + +RESETABLE note; +RESETABLE name; +RESETABLE address; +RESETABLE email; +RESETABLE phone; +RESETABLE group; +RESETABLE ips; // IP-address of user +}; +//----------------------------------------------------------------------------- + +#endif + + diff --git a/projects/sgconf/sg_error_codes.h b/projects/sgconf/sg_error_codes.h new file mode 100644 index 00000000..250bd474 --- /dev/null +++ b/projects/sgconf/sg_error_codes.h @@ -0,0 +1,51 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: nobunaga $ + $Revision: 1.2 $ + $Date: 2008/05/11 08:15:08 $ + */ + + + +#ifndef STG_ERROR_CODES_H +#define STG_ERROR_CODES_H + +#ifndef ENODATA +#define ENODATA 61 +#endif + +#ifndef EBADMSG +#define EBADMSG 74 +#endif + +#define NETWORK_ERR_CODE (1) +#define LOGIN_OR_PASS_ERR_CODE (2) +#define USER_NOT_FOUND_ERR_CODE (3) +#define TARIFF_NOT_FOUND_ERR_CODE (4) +#define PARAMETER_PARSING_ERR_CODE (5) +#define UNKNOWN_ERR_CODE (6) +#define ICONV_ERR_CODE (7) + + +#endif + + diff --git a/projects/sgconf/sgconfg b/projects/sgconf/sgconfg new file mode 100755 index 00000000..fbd32837 --- /dev/null +++ b/projects/sgconf/sgconfg @@ -0,0 +1,4 @@ +#!/bin/bash + +LD_LIBRARY_PATH=../../lib ./sgconf get -s localhost -p5555 -aadmin -w123456 $* + diff --git a/projects/sgconf/sgconfs b/projects/sgconf/sgconfs new file mode 100755 index 00000000..e27108e7 --- /dev/null +++ b/projects/sgconf/sgconfs @@ -0,0 +1,4 @@ +#!/bin/bash + +LD_LIBRARY_PATH=../../lib ./sgconf set -s localhost -p5555 -aadmin -w123456 $* + diff --git a/projects/sgconf/sginfo.cpp b/projects/sgconf/sginfo.cpp new file mode 100644 index 00000000..86352599 --- /dev/null +++ b/projects/sgconf/sginfo.cpp @@ -0,0 +1,1084 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: faust $ + $Revision: 1.3 $ + $Date: 2009/06/22 15:57:49 $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "request.h" +#include "common.h" +#include "netunit.h" +#include "common_sg.h" +#include "sg_error_codes.h" + +using namespace std; + +time_t stgTime; + +int ParseReplyGet(void * data, list * ans); +int ParseReplySet(void * data, list * ans); + +struct option long_options_get[] = { +{"server", 1, 0, 's'}, //Server +{"port", 1, 0, 'p'}, //Port +{"admin", 1, 0, 'a'}, //Admin +{"admin_pass", 1, 0, 'w'}, //passWord +{"user", 1, 0, 'u'}, //User +{"addcash", 0, 0, 'c'}, //Add Cash +//{"setcash", 0, 0, 'v'}, //Set Cash +{"credit", 0, 0, 'r'}, //cRedit +{"tariff", 0, 0, 't'}, //Tariff +{"message", 0, 0, 'm'}, //message +{"password", 0, 0, 'o'}, //password +{"down", 0, 0, 'd'}, //down +{"passive", 0, 0, 'i'}, //passive +{"u0", 0, 0, 500}, //U0 +{"u1", 0, 0, 501}, //U1 +{"u2", 0, 0, 502}, //U2 +{"u3", 0, 0, 503}, //U3 +{"u4", 0, 0, 504}, //U4 +{"u5", 0, 0, 505}, //U5 +{"u6", 0, 0, 506}, //U6 +{"u7", 0, 0, 507}, //U7 +{"u8", 0, 0, 508}, //U8 +{"u9", 0, 0, 509}, //U9 +{"d0", 0, 0, 600}, //D0 +{"d1", 0, 0, 601}, //D1 +{"d2", 0, 0, 602}, //D2 +{"d3", 0, 0, 603}, //D3 +{"d4", 0, 0, 604}, //D4 +{"d5", 0, 0, 605}, //D5 +{"d6", 0, 0, 606}, //D6 +{"d7", 0, 0, 607}, //D7 +{"d8", 0, 0, 608}, //D8 +{"d9", 0, 0, 609}, //D9 + +{"ud0", 0, 0, 700}, //UserData0 +{"ud1", 0, 0, 701}, //UserData1 +{"ud2", 0, 0, 702}, //UserData2 +{"ud3", 0, 0, 703}, //UserData3 +{"ud4", 0, 0, 704}, //UserData4 +{"ud5", 0, 0, 705}, //UserData5 +{"ud6", 0, 0, 706}, //UserData6 +{"ud7", 0, 0, 707}, //UserData7 +{"ud8", 0, 0, 708}, //UserData8 +{"ud9", 0, 0, 709}, //UserData9 + +{"prepaid", 0, 0, 'e'}, //prepaid traff +{"create", 0, 0, 'n'}, //create +{"delete", 0, 0, 'l'}, //delete + +{"note", 0, 0, 'N'}, //Note +{"name", 0, 0, 'A'}, //nAme +{"address", 0, 0, 'D'}, //aDdress +{"email", 0, 0, 'L'}, //emaiL +{"phone", 0, 0, 'P'}, //phone +{"group", 0, 0, 'G'}, //Group + +{0, 0, 0, 0}}; + +struct option long_options_set[] = { +{"server", 1, 0, 's'}, //Server +{"port", 1, 0, 'p'}, //Port +{"admin", 1, 0, 'a'}, //Admin +{"admin_pass", 1, 0, 'w'}, //passWord +{"user", 1, 0, 'u'}, //User +{"addcash", 1, 0, 'c'}, //Add Cash +{"setcash", 1, 0, 'v'}, //Set Cash +{"credit", 1, 0, 'r'}, //cRedit +{"tariff", 1, 0, 't'}, //Tariff +{"message", 1, 0, 'm'}, //message +{"password", 1, 0, 'o'}, //password +{"down", 1, 0, 'd'}, //down +{"passive", 1, 0, 'i'}, //passive +{"u0", 1, 0, 500}, //U0 +{"u1", 1, 0, 501}, //U1 +{"u2", 1, 0, 502}, //U2 +{"u3", 1, 0, 503}, //U3 +{"u4", 1, 0, 504}, //U4 +{"u5", 1, 0, 505}, //U5 +{"u6", 1, 0, 506}, //U6 +{"u7", 1, 0, 507}, //U7 +{"u8", 1, 0, 508}, //U8 +{"u9", 1, 0, 509}, //U9 +{"d0", 1, 0, 600}, //D0 +{"d1", 1, 0, 601}, //D1 +{"d2", 1, 0, 602}, //D2 +{"d3", 1, 0, 603}, //D3 +{"d4", 1, 0, 604}, //D4 +{"d5", 1, 0, 605}, //D5 +{"d6", 1, 0, 606}, //D6 +{"d7", 1, 0, 607}, //D7 +{"d8", 1, 0, 608}, //D8 +{"d9", 1, 0, 609}, //D9 + +{"ud0", 1, 0, 700}, //UserData +{"ud1", 1, 0, 701}, //UserData1 +{"ud2", 1, 0, 702}, //UserData2 +{"ud3", 1, 0, 703}, //UserData3 +{"ud4", 1, 0, 704}, //UserData4 +{"ud5", 1, 0, 705}, //UserData5 +{"ud6", 1, 0, 706}, //UserData6 +{"ud7", 1, 0, 707}, //UserData7 +{"ud8", 1, 0, 708}, //UserData8 +{"ud9", 1, 0, 709}, //UserData9 + +{"prepaid", 1, 0, 'e'}, //prepaid traff +{"create", 1, 0, 'n'}, //create +{"delete", 1, 0, 'l'}, //delete + +{"note", 1, 0, 'N'}, //Note +{"name", 1, 0, 'A'}, //nAme +{"address", 1, 0, 'D'}, //aDdress +{"email", 1, 0, 'L'}, //emaiL +{"phone", 1, 0, 'P'}, //phone +{"group", 1, 0, 'G'}, //Group + +{0, 0, 0, 0}}; + +//----------------------------------------------------------------------------- +void CreateRequestGet(REQUEST * req, char * r) +{ +string r1; +r1 = "login.const_data() + "\"/>\n"; +strcpy(r, r1.c_str()); +} +//----------------------------------------------------------------------------- +double ParseCash(const char * c, string * message) +{ +//-c 123.45:log message +double cash; +char * msg; +char * str; +str = new char[strlen(c)]; + +strcpy(str, c); +msg = strchr(str, ':'); + +if (msg) + { + *message = msg + 1; + str[msg - str] = 0; + } +else + *message = ""; + +if (strtodouble2(str, cash) != 0) + { + printf("Incorrect cash value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +delete[] str; +return cash; +} +//----------------------------------------------------------------------------- +double ParseCredit(const char * c) +{ +double credit; +if (strtodouble2(c, credit) != 0) + { + printf("Incorrect credit value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return credit; +} +//----------------------------------------------------------------------------- +double ParsePrepaidTraffic(const char * c) +{ +double credit; +if (strtodouble2(c, credit) != 0) + { + printf("Incorrect prepaid traffic value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return credit; +} +//----------------------------------------------------------------------------- +int64_t ParseTraff(const char * c) +{ +int64_t traff; +if (str2x(c, traff) != 0) + { + printf("Incorrect credit value %s\n", c); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return traff; +} +//----------------------------------------------------------------------------- +bool ParseDownPassive(const char * dp) +{ +if (!(dp[1] == 0 && (dp[0] == '1' || dp[0] == '0'))) + { + printf("Incorrect value %s\n", dp); + exit(PARAMETER_PARSING_ERR_CODE); + } + +return dp[0] - '0'; +} +//----------------------------------------------------------------------------- +string ParseTariff(const char * t, int &chgType) +{ +int l = strlen(t); +char * s; +s = new char[l]; +char * s1, * s2; +string ss; + +strcpy(s, t); + +s1 = strtok(s, ":"); + +if (strlen(s1) >= TARIFF_NAME_LEN) + { + printf("Tariff name too big %s\n", s1); + exit(PARAMETER_PARSING_ERR_CODE); + } + +//*tariff = s; + +if (CheckLogin(s1)) + { + printf("Incorrect tariff value %s\n", t); + exit(PARAMETER_PARSING_ERR_CODE); + } + +s2 = strtok(NULL, ":"); + +chgType = -1; + +if (s2 == NULL) + { + chgType = TARIFF_NOW; + ss = s; + delete[] s; + return ss; + } + + +if (strcmp(s2, "now") == 0) + chgType = TARIFF_NOW; + +if (strcmp(s2, "delayed") == 0) + chgType = TARIFF_DEL; + +if (strcmp(s2, "recalc") == 0) + chgType = TARIFF_REC; + +if (chgType < 0) + { + printf("Incorrect tariff value %s\n", t); + exit(PARAMETER_PARSING_ERR_CODE); + } + +ss = s; +delete[] s; +return ss; +} +//----------------------------------------------------------------------------- +void ParseAnyString(const char * c, string * msg) +{ +iconv_t cd; +char * ob = new char[strlen(c) + 1]; +char * ib = new char[strlen(c) + 1]; + +strcpy(ib, c); + +char * outbuf = ob; +char * inbuf = ib; + +setlocale(LC_ALL, ""); + +char charsetF[255]; +strncpy(charsetF, nl_langinfo(CODESET), 255); + +char * charsetT = "koi8-r"; + +size_t nconv = 1; + +size_t insize = strlen(ib); +size_t outsize = strlen(ib); + +insize = strlen(c); + +cd = iconv_open(charsetT, charsetF); +if (cd == (iconv_t) -1) + { + if (errno == EINVAL) + { + printf("Warning: iconv from %s to %s failed\n", charsetF, charsetT); + *msg = c; + return; + } + else + printf("error iconv_open\n"); + + exit(ICONV_ERR_CODE); + } + +nconv = iconv (cd, &inbuf, &insize, &outbuf, &outsize); +//printf("nconv=%d outsize=%d\n", nconv, outsize); +if (nconv == (size_t) -1) + { + if (errno != EINVAL) + { + printf("iconv error\n"); + exit(ICONV_ERR_CODE); + } + } + +*outbuf = L'\0'; + +iconv_close(cd); +*msg = ob; + +delete[] ob; +delete[] ib; +} +//----------------------------------------------------------------------------- +void CreateRequestSet(REQUEST * req, char * r) +{ +const int strLen = 10024; +char str[strLen]; +memset(str, 0, strLen); + +r[0] = 0; + +if (!req->usrMsg.res_empty()) + { + int len = req->usrMsg.const_data().length() * 2 + 1; + char * msg = new char[len]; + memset(msg, 0, len); + Encode12(msg, req->usrMsg.const_data().c_str(), req->usrMsg.const_data().length()); + + sprintf(str, "", req->login.const_data().c_str(), msg); + //sprintf(str, "\n", req->login, msg); + strcat(r, str); + + delete[] msg; + return; + } + +if (req->deleteUser) + { + sprintf(str, "", req->login.const_data().c_str()); + strcat(r, str); + //printf("%s\n", r); + return; + } + +if (req->createUser) + { + sprintf(str, " ", req->login.const_data().c_str()); + strcat(r, str); + //printf("%s\n", r); + return; + } + +strcat(r, "\n"); +sprintf(str, "\n", req->login.const_data().c_str()); +strcat(r, str); +if (!req->credit.res_empty()) + { + sprintf(str, "\n", req->credit.const_data()); + strcat(r, str); + } + +if (!req->prepaidTraff.res_empty()) + { + sprintf(str, "\n", req->prepaidTraff.const_data()); + strcat(r, str); + } + +if (!req->cash.res_empty()) + { + int len = req->message.length() * 2 + 1; + char * msg = new char[len]; + memset(msg, 0, len); + + Encode12(msg, req->message.c_str(), req->message.length()); + sprintf(str, "\n", req->cash.const_data(), msg); + strcat(r, str); + delete[] msg; + } + +if (!req->setCash.res_empty()) + { + int len = req->message.length() * 2 + 1; + char * msg = new char[len]; + memset(msg, 0, len); + Encode12(msg, req->message.c_str(), req->message.length()); + sprintf(str, "\n", req->setCash.const_data(), msg); + strcat(r, str); + delete[] msg; + } + +if (!req->usrPasswd.res_empty()) + { + sprintf(str, "\n", req->usrPasswd.const_data().c_str()); + strcat(r, str); + } + +if (!req->down.res_empty()) + { + sprintf(str, "\n", req->down.const_data()); + strcat(r, str); + } + +if (!req->passive.res_empty()) + { + sprintf(str, "\n", req->passive.const_data()); + strcat(r, str); + } + +int uPresent = false; +int dPresent = false; +for (int i = 0; i < DIR_NUM; i++) + { + if (!req->u[i].res_empty()) + { + if (!uPresent && !dPresent) + { + sprintf(str, "u[i].const_data(); + //sprintf(str, "MU%d=\"%lld\" ", i, req->u[i].const_data()); + sprintf(str, "MU%d=\"%s\" ", i, ss.str().c_str()); + strcat(r, str); + } + if (!req->d[i].res_empty()) + { + if (!uPresent && !dPresent) + { + sprintf(str, "d[i].const_data(); + sprintf(str, "MD%d=\"%s\" ", i, ss.str().c_str()); + strcat(r, str); + } + } +if (uPresent || dPresent) + { + strcat(r, "/>"); + } + +//printf("%s\n", r); + +if (!req->tariff.res_empty()) + { + switch (req->chgTariff) + { + case TARIFF_NOW: + sprintf(str, "\n", req->tariff.const_data().c_str()); + strcat(r, str); + break; + case TARIFF_REC: + sprintf(str, "\n", req->tariff.const_data().c_str()); + strcat(r, str); + break; + case TARIFF_DEL: + sprintf(str, "\n", req->tariff.const_data().c_str()); + strcat(r, str); + break; + } + + } + +if (!req->note.res_empty()) + { + int len = req->note.const_data().length() * 2 + 1; + char * note = new char[len]; + memset(note, 0, len); + + Encode12(note, req->note.const_data().c_str(), req->note.const_data().length()); + + sprintf(str, "", note); + strcat(r, str); + delete[] note; + } + +if (!req->name.res_empty()) + { + int len = req->note.const_data().length() * 2 + 1; + char * name = new char[len]; + memset(name, 0, len); + + Encode12(name, req->name.const_data().c_str(), req->name.const_data().length()); + + sprintf(str, "", name); + strcat(r, str); + delete[] name; + } + +if (!req->address.res_empty()) + { + int len = req->note.const_data().length() * 2 + 1; + char * address = new char[len]; + memset(address, 0, len); + + Encode12(address, req->address.const_data().c_str(), req->address.const_data().length()); + + sprintf(str, "
", address); + strcat(r, str); + delete[] address; + } + +if (!req->email.res_empty()) + { + int len = req->note.const_data().length() * 2 + 1; + char * email = new char[len]; + memset(email, 0, len); + + Encode12(email, req->email.const_data().c_str(), req->email.const_data().length()); + + sprintf(str, "", email); + strcat(r, str); + delete[] email; + } + +if (!req->phone.res_empty()) + { + int len = req->note.const_data().length() * 2 + 1; + char * phone = new char[len]; + memset(phone, 0, len); + + Encode12(phone, req->phone.const_data().c_str(), req->phone.const_data().length()); + + sprintf(str, "", phone); + strcat(r, str); + delete[] phone; + } + +if (!req->group.res_empty()) + { + int len = req->note.const_data().length() * 2 + 1; + char * group = new char[len]; + memset(group, 0, len); + + Encode12(group, req->group.const_data().c_str(), req->group.const_data().length()); + + sprintf(str, "", group); + strcat(r, str); + delete[] group; + } + +for (int i = 0; i < USERDATA_NUM; i++) + { + if (!req->ud[i].res_empty()) + { + int len = req->ud[i].const_data().length() * 2 + 1; + char * ud = new char[len]; + memset(ud, 0, len); + + Encode12(ud, req->ud[i].const_data().c_str(), req->ud[i].const_data().length()); + + sprintf(str, "", i, ud); + strcat(r, str); + delete[] ud; + } + } + +strcat(r, "\n"); +} +//----------------------------------------------------------------------------- +int CheckParameters(REQUEST * req) +{ +int u = false; +int d = false; +int ud = false; +int a = !req->admLogin.res_empty() + && !req->admPasswd.res_empty() + && !req->server.res_empty() + && !req->port.res_empty() + && !req->login.res_empty(); + +int b = !req->cash.res_empty() + || !req->setCash.res_empty() + || !req->credit.res_empty() + || !req->prepaidTraff.res_empty() + || !req->tariff.res_empty() + || !req->usrMsg.res_empty() + || !req->usrPasswd.res_empty() + + || !req->note.res_empty() + || !req->name.res_empty() + || !req->address.res_empty() + || !req->email.res_empty() + || !req->phone.res_empty() + || !req->group.res_empty(); + +for (int i = 0; i < DIR_NUM; i++) + { + if (req->u[i].res_empty()) + { + u = true; + break; + } + } + +for (int i = 0; i < DIR_NUM; i++) + { + if (req->d[i].res_empty()) + { + d = true; + break; + } + } + +for (int i = 0; i < DIR_NUM; i++) + { + if (req->ud[i].res_empty()) + { + ud = true; + break; + } + } + + +//printf("a=%d, b=%d, u=%d, d=%d ud=%d\n", a, b, u, d, ud); +return a && (b || u || d || ud); +} +//----------------------------------------------------------------------------- +int CheckParametersGet(REQUEST * req) +{ +return CheckParameters(req); +} +//----------------------------------------------------------------------------- +int CheckParametersSet(REQUEST * req) +{ +return CheckParameters(req); +} +//----------------------------------------------------------------------------- +int mainGet(int argc, char **argv) +{ +int c; +REQUEST req; +RESETABLE t1; + +char * short_options_get = "s:p:a:w:u:crtmodieNADLPG"; +int option_index = -1; + +while (1) + { + option_index = -1; + c = getopt_long(argc, argv, short_options_get, long_options_get, &option_index); + if (c == -1) + break; + + switch (c) + { + case 's': //server + req.server = optarg; + break; + + case 'p': //port + req.port = ParseServerPort(optarg); + //req.portReq = 1; + break; + + case 'a': //admin + req.admLogin = ParseAdminLogin(optarg); + break; + + case 'w': //admin password + req.admPasswd = ParsePassword(optarg); + break; + + case 'o': //change user password + req.usrPasswd = ParsePassword(optarg); + break; + + case 'u': //user + req.login = ParseUser(optarg); + break; + + case 'c': //get cash + req.cash = 1; + break; + + case 'r': //credit + req.credit = 1; + break; + + case 'd': //down + req.down = 1; + break; + + case 'i': //passive + req.passive = 1; + break; + + case 't': //tariff + req.tariff = " "; + break; + + case 'e': //Prepaid Traffic + req.prepaidTraff = 1; + break; + + case 'N': //Note + req.note = " "; + break; + + case 'A': //nAme + req.name = " "; + break; + + case 'D': //aDdress + req.address =" "; + break; + + case 'L': //emaiL + req.email = " "; + break; + + case 'P': //phone + req.phone = " "; + break; + + case 'G': //Group + req.group = " "; + break; + + case 500: //U + case 501: + case 502: + case 503: + case 504: + case 505: + case 506: + case 507: + case 508: + case 509: + //printf("U%d\n", c - 500); + req.u[c - 500] = 1; + break; + + case 600: //D + case 601: + case 602: + case 603: + case 604: + case 605: + case 606: + case 607: + case 608: + case 609: + //printf("D%d\n", c - 600); + req.d[c - 600] = 1; + break; + + case 700: //UserData + case 701: + case 702: + case 703: + case 704: + case 705: + case 706: + case 707: + case 708: + case 709: + //printf("UD%d\n", c - 700); + req.ud[c - 700] = " "; + break; + + case '?': + //printf ("Unknown option \n"); + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + +if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + UsageInfo(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +if (CheckParametersGet(&req) == 0) + { + //printf("Parameter needed\n"); + UsageInfo(); + exit(PARAMETER_PARSING_ERR_CODE); + } + + +const int rLen = 20000; +char rstr[rLen]; +memset(rstr, 0, rLen); + +CreateRequestGet(&req, rstr); +Process(req.server, req.port, req.admLogin, req.admPasswd, rstr, ParseReplyGet); + +return 0; +} +//----------------------------------------------------------------------------- +int mainSet(int argc, char **argv) +{ +string str; + +int c; +REQUEST req; + +RESETABLE t1; + +char * short_options_set = "s:p:a:w:u:c:r:t:m:o:d:i:e:v:nlN:A:D:L:P:G:"; + +while (1) + { + int option_index = -1; + + c = getopt_long(argc, argv, short_options_set, long_options_set, &option_index); + if (c == -1) + break; + + switch (c) + { + case 's': //server + req.server = optarg; + break; + + case 'p': //port + req.port = ParseServerPort(optarg); + //req.portReq = 1; + break; + + case 'a': //admin + req.admLogin = ParseAdminLogin(optarg); + break; + + case 'w': //admin password + req.admPasswd = ParsePassword(optarg); + break; + + case 'o': //change user password + req.usrPasswd = ParsePassword(optarg); + break; + + case 'u': //user + req.login = ParseUser(optarg); + break; + + case 'c': //add cash + req.cash = ParseCash(optarg, &req.message); + break; + + case 'v': //set cash + req.setCash = ParseCash(optarg, &req.message); + break; + + case 'r': //credit + req.credit = ParseCredit(optarg); + break; + + case 'd': //down + req.down = ParseDownPassive(optarg); + break; + + case 'i': //passive + req.passive = ParseDownPassive(optarg); + break; + + case 't': //tariff + req.tariff = ParseTariff(optarg, req.chgTariff); + break; + + case 'm': //message + //ParseMessage(optarg, &req.usrMsg); + req.usrMsg = optarg; + break; + + case 'e': //Prepaid Traffic + req.prepaidTraff = ParsePrepaidTraffic(optarg); + break; + + case 'n': //Create User + req.createUser = true; + break; + + case 'l': //Delete User + req.deleteUser = true; + break; + + case 'N': //Note + ParseAnyString(optarg, &str); + req.note = str; + break; + + case 'A': //nAme + ParseAnyString(optarg, &str); + req.name = str; + break; + + case 'D': //aDdress + ParseAnyString(optarg, &str); + req.address = str; + break; + + case 'L': //emaiL + ParseAnyString(optarg, &str); + req.email = str; + break; + + case 'P': //phone + ParseAnyString(optarg, &str); + req.phone = str; + break; + + case 'G': //Group + ParseAnyString(optarg, &str); + req.group = str; + break; + + case 500: //U + case 501: + case 502: + case 503: + case 504: + case 505: + case 506: + case 507: + case 508: + case 509: + //printf("U%d\n", c - 500); + req.u[c - 500] = ParseTraff(optarg); + break; + + case 600: //D + case 601: + case 602: + case 603: + case 604: + case 605: + case 606: + case 607: + case 608: + case 609: + //printf("D%d\n", c - 600); + req.d[c - 600] = ParseTraff(optarg); + break; + + case 700: //UserData + case 701: + case 702: + case 703: + case 704: + case 705: + case 706: + case 707: + case 708: + case 709: + ParseAnyString(optarg, &str); + //printf("UD%d\n", c - 700); + req.ud[c - 700] = str; + break; + + case '?': + //printf ("Unknown option \n"); + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + +if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +if (CheckParametersSet(&req) == 0) + { + //printf("Parameter needed\n"); + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +const int rLen = 20000; +char rstr[rLen]; +memset(rstr, 0, rLen); + +CreateRequestGet(&req, rstr); +Process(req.server, req.port, req.admLogin, req.admPasswd, rstr, ParseReplySet); +//Process(&req); + +return 0; +} +//----------------------------------------------------------------------------- +int main(int argc, char **argv) +{ +if (argc <= 2) + { + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } + +if (strcmp(argv[1], "get")) + { + return mainGet(argc - 1, argv + 1); + } +else if (strcmp(argv[1], "set")) + { + return mainGet(argc - 1, argv + 1); + } +else + { + UsageConf(); + exit(PARAMETER_PARSING_ERR_CODE); + } +return UNKNOWN_ERR_CODE; +} +//----------------------------------------------------------------------------- + diff --git a/projects/sgconf/version_sg.h b/projects/sgconf/version_sg.h new file mode 100644 index 00000000..f9664eb8 --- /dev/null +++ b/projects/sgconf/version_sg.h @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Author: faust $ + $Revision: 1.3 $ + $Date: 2009/08/05 09:29:35 $ + */ + + +#ifndef VERSION_SG_H +#define VERSION_SG_H + +#define VERSION_SG "1.08.9" + +#endif + + diff --git a/projects/stargazer/.#Makefile.1.45 b/projects/stargazer/.#Makefile.1.45 new file mode 100644 index 00000000..a9108931 --- /dev/null +++ b/projects/stargazer/.#Makefile.1.45 @@ -0,0 +1,141 @@ +############################################################################### +# $Id: Makefile,v 1.45 2009/03/20 15:47:10 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +PROG = stargazer + +SRCS = ./admin.cpp \ + ./admins.cpp \ + ./main.cpp \ + ./curr_ip.cpp \ + ./settings.cpp \ + ./stg_timer.cpp \ + ./tariff.cpp \ + ./tariffs.cpp \ + ./traffcounter.cpp \ + ./user.cpp \ + ./user_property.cpp \ + ./users.cpp \ + ./plugin_runner.cpp \ + ./store_loader.cpp \ + ./pidfile.cpp + +STGLIBS = -lstg_logger \ + -lstg_locker \ + -lstg_common \ + -lscript_executer \ + -ldotconfpp + +LIBS += -lexpat + +ifeq ($(OS),linux) +LIBS += $(LIB_THREAD) \ + -ldl +else +LIBS += $(LIB_THREAD) \ + -lc +endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +CXXFLAGS += -Wall -W +LDFLAGS += -Wl,-E -L$(DIR_LIB) -Wl,-rpath,$(PREFIX)/usr/lib/stg -Wl,-rpath-link,$(DIR_LIB) +vpath %.so $(DIR_LIB) + +.PHONY: all clean distclean libs plugins install uninstall install-bin install-data +all: libs plugins $(PROG) + +libs: + $(MAKE) -C $(DIR_LIBSRC) + +plugins: libs + $(MAKE) -C $(DIR_PLUGINS) + +$(PROG): $(OBJS) $(STGLIBS) + $(CC) $^ $(LDFLAGS) $(LIBS) -o $(PROG) + +clean: + rm -f deps $(PROG) *.o tags *.*~ .OS + rm -f .OS + rm -f .store + rm -f .db.sql + rm -f core* + $(MAKE) -C $(DIR_LIBSRC) clean + $(MAKE) -C $(DIR_PLUGINS) clean + +distclean: clean + rm -f $(DIR_MOD)/* + rm -f ../../Makefile.conf + +install: install-bin install-data + +install-bin: + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/sbin + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/sbin/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) install + $(MAKE) -C $(DIR_PLUGINS) install + +install-data: + # Install etc + mkdir -m $(DATA_MODE) -p $(PREFIX)/etc/stargazer + install -m $(DATA_MODE) -o $(OWNER) $(ETC_DIR)/stargazer.conf $(PREFIX)/etc/stargazer/stargazer.conf + install -m $(DATA_MODE) -o $(OWNER) $(ETC_DIR)/rules $(PREFIX)/etc/stargazer/rules + install -m $(BIN_MODE) -o $(OWNER) $(ETC_DIR)/On* $(PREFIX)/etc/stargazer/ + + # Install file db + mkdir -m $(DATA_MODE) -p $(PREFIX)/var/stargazer/admins + mkdir -m $(DATA_MODE) -p $(PREFIX)/var/stargazer/tariffs + mkdir -m $(DATA_MODE) -p $(PREFIX)/var/stargazer/users/test + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/admins/admin.adm $(PREFIX)/var/stargazer/admins/admin.adm + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/tariffs/tariff.tf $(PREFIX)/var/stargazer/tariffs/tariff.tf + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/users/test/conf $(PREFIX)/var/stargazer/users/test/conf + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/users/test/stat $(PREFIX)/var/stargazer/users/test/stat + +ifeq ($(CHECK_FBCLIENT),yes) + # Install firebird db + mkdir -p $(PREFIX)/var/stargazer + chown $(OWNER):$(FIREBIRD_GROUP) $(PREFIX)/var/stargazer + chmod g+rw $(PREFIX)/var/stargazer + echo "connect '$(DB_ADDRESS)' user '$(DB_USER)' password '$(DB_PASSWORD)';" > .db.sql + echo "drop database;" >> .db.sql + echo "create database '$(DB_ADDRESS)' user '$(DB_USER)' password '$(DB_PASSWORD)' default character set win1251;" >> .db.sql + cat $(VAR_DIR)/../00-base-00.sql >> .db.sql + $(FIREBIRD_ISQL) -i .db.sql + rm -f .db.sql +endif + +uninstall: uninstall-bin uninstall-data + +uninstall-bin: + rm -f $(PREFIX)/usr/sbin/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) uninstall + $(MAKE) -C $(DIR_PLUGINS) uninstall + rm -rf $(PREFIX)/usr/lib/stg + +uninstall-data: + # Uninstall etc + rm -rf $(PREFIX)/etc/stargazer + rm -rf $(PREFIX)/var/stargazer + + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf + $(MAKE) -C $(DIR_LIBSRC) includes + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) -MM $$file` Makefile ../../Makefile.conf" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/stargazer/BUGS b/projects/stargazer/BUGS new file mode 100644 index 00000000..139597f9 --- /dev/null +++ b/projects/stargazer/BUGS @@ -0,0 +1,2 @@ + + diff --git a/projects/stargazer/CHANGES b/projects/stargazer/CHANGES new file mode 100644 index 00000000..7f80179b --- /dev/null +++ b/projects/stargazer/CHANGES @@ -0,0 +1,9 @@ +1. éÓÐÒÁ×ÌÅÎÁ ÏÛÉÂËÁ × ÐÌÁÇÉÎÅ ËÏÎÆÉÇÕÒÁÔÏÒÁ ÐÒÉ×ÏÄÉ×ÛÁÑ Ë ÐÁÄÅÎÉÀ ÓÅÒ×ÅÒÁ +2. ðÅÒÅÒÁÂÏÔÁÎ ËÏÄ ÐÌÁÇÉÎÁ Á×ÔÏÒÉÚÁÃÉÉ inetaccess. õ×ÅÌÉÞÅÎÁ ÓËÏÒÏÓÔØ ÅÇÏ + ÒÁÂÏÔÙ +3. éÓÐÒÁ×ÌÅÎÁ ÏÛÉÂËÁ, ÐÒÉ×ÏÄÉ×ÛÁÑ Ë ÓÌÉÛËÏÍ ÞÁÓÔÏÊ ÚÁÐÉÓÉ ÆÁÊÌÏ× stat É conf, + É ÓÏÏÔ×ÅÔÓÔ×ÅÎÎÏ Ë ×ÙÓÏËÏÊ ÚÁÇÒÕÚËÅ ÐÒÏÃÅÓÓÏÒÁ +4. éÓÐÒÁ×ÌÅÎÁ ÏÛÉÂËÁ × ÏÂÒÁÂÏÔËÅ ÐÏÒÏÇÁ ÔÁÒÉÆÁ. ðÒÉ ÔÒÁÆÉËÅ ÂÏÌÅÅ 2 ç ÐÏÒÏÇ + ÐÅÒÅÓÔÁ×ÁÌ ÒÁÂÏÔÁÔØ +5. äÏÂÁ×ÌÅÎÁ ×ÏÚÍÏÖÎÏÓÔØ ×ÙÐÏÌÎÑÔØ ÓËÒÉÐÔÙ OnConnect É OnDisconnect ÕÄÁÌÅÎÎÏ + (ôÏÌØËÏ ÄÌÑ ÔÅÓÔÏ×) diff --git a/projects/stargazer/Makefile b/projects/stargazer/Makefile new file mode 100644 index 00000000..02757ad1 --- /dev/null +++ b/projects/stargazer/Makefile @@ -0,0 +1,140 @@ +############################################################################### +# $Id: Makefile,v 1.50 2010/10/07 18:27:27 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +PROG = stargazer + +SRCS = ./admin.cpp \ + ./admins.cpp \ + ./main.cpp \ + ./settings.cpp \ + ./stg_timer.cpp \ + ./tariff.cpp \ + ./tariffs.cpp \ + ./traffcounter.cpp \ + ./user.cpp \ + ./user_property.cpp \ + ./users.cpp \ + ./plugin_runner.cpp \ + ./store_loader.cpp \ + ./pidfile.cpp \ + ./eventloop.cpp + +STGLIBS = -lstg_logger \ + -lstg_locker \ + -lstg_common \ + -lscript_executer \ + -ldotconfpp + +LIBS += -lexpat + +ifeq ($(OS),linux) +LIBS += $(LIB_THREAD) \ + -ldl +else +LIBS += $(LIB_THREAD) \ + -lc +endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +LDFLAGS += -Wl,-E -L$(DIR_LIB) -Wl,-rpath,$(PREFIX)/usr/lib/stg -Wl,-rpath-link,$(DIR_LIB) +vpath %.so $(DIR_LIB) + +.PHONY: all clean distclean libs plugins install uninstall install-bin install-data +all: libs plugins $(PROG) + +libs: + $(MAKE) -C $(DIR_LIBSRC) + +plugins: libs + $(MAKE) -C $(DIR_PLUGINS) + +$(PROG): $(OBJS) libs + $(CC) $(OBJS) $(LDFLAGS) $(LIBS) $(STGLIBS) -o $(PROG) + +clean: + rm -f deps $(PROG) *.o tags *.*~ .OS + rm -f .OS + rm -f .store + rm -f .db.sql + rm -f core* + $(MAKE) -C $(DIR_LIBSRC) clean + $(MAKE) -C $(DIR_PLUGINS) clean + +distclean: clean + rm -f $(DIR_MOD)/* + rm -f ../../Makefile.conf + +install: install-bin install-data + +install-bin: + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/sbin + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/sbin/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) install + $(MAKE) -C $(DIR_PLUGINS) install + +install-data: + # Install etc + mkdir -m $(DATA_MODE) -p $(PREFIX)/etc/stargazer + install -m $(DATA_MODE) -o $(OWNER) $(ETC_DIR)/stargazer.conf $(PREFIX)/etc/stargazer/stargazer.conf + install -m $(DATA_MODE) -o $(OWNER) $(ETC_DIR)/rules $(PREFIX)/etc/stargazer/rules + install -m $(BIN_MODE) -o $(OWNER) $(ETC_DIR)/On* $(PREFIX)/etc/stargazer/ + + # Install file db + mkdir -m $(DATA_MODE) -p $(PREFIX)/var/stargazer/admins + mkdir -m $(DATA_MODE) -p $(PREFIX)/var/stargazer/tariffs + mkdir -m $(DATA_MODE) -p $(PREFIX)/var/stargazer/users/test + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/admins/admin.adm $(PREFIX)/var/stargazer/admins/admin.adm + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/tariffs/tariff.tf $(PREFIX)/var/stargazer/tariffs/tariff.tf + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/users/test/conf $(PREFIX)/var/stargazer/users/test/conf + install -m $(DATA_MODE) -o $(OWNER) $(VAR_DIR)/users/test/stat $(PREFIX)/var/stargazer/users/test/stat + +ifeq ($(CHECK_FBCLIENT),yes) + # Install firebird db + mkdir -p $(PREFIX)/var/stargazer + chown $(OWNER):$(FIREBIRD_GROUP) $(PREFIX)/var/stargazer + chmod g+rw $(PREFIX)/var/stargazer + echo "connect '$(DB_ADDRESS)' user '$(DB_USER)' password '$(DB_PASSWORD)';" > .db.sql + echo "drop database;" >> .db.sql + echo "create database '$(DB_ADDRESS)' user '$(DB_USER)' password '$(DB_PASSWORD)' default character set win1251;" >> .db.sql + cat $(VAR_DIR)/../00-base-00.sql >> .db.sql + $(FIREBIRD_ISQL) -i .db.sql + rm -f .db.sql +endif + +uninstall: uninstall-bin uninstall-data + +uninstall-bin: + rm -f $(PREFIX)/usr/sbin/$(PROG) + $(MAKE) -C $(DIR_LIBSRC) uninstall + $(MAKE) -C $(DIR_PLUGINS) uninstall + rm -rf $(PREFIX)/usr/lib/stg + +uninstall-data: + # Uninstall etc + rm -rf $(PREFIX)/etc/stargazer + rm -rf $(PREFIX)/var/stargazer + + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf + $(MAKE) -C $(DIR_LIBSRC) includes + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) -MM $$file` Makefile ../../Makefile.conf" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/stargazer/README b/projects/stargazer/README new file mode 100644 index 00000000..db298b28 --- /dev/null +++ b/projects/stargazer/README @@ -0,0 +1,6 @@ +éÎÓÔÁÌÑÃÉÑ É ÚÁÐÕÓË. +1. > ./build +2. > make install +3. ðÒÁ×ËÁ ËÏÎÆÉÇÕÒÁÃÉÏÎÎÙÈ ÆÁÊÌÏ× +4. > stargazer + diff --git a/projects/stargazer/TODO b/projects/stargazer/TODO new file mode 100644 index 00000000..6a56eb37 --- /dev/null +++ b/projects/stargazer/TODO @@ -0,0 +1,7 @@ +1. äÏÂÁ×ÉÔØ ÐÁÒÁÍÅÔÒÙ × Á×ÔÏÒÉÚÁÔÏÒ É ËÏÎÆÉÇÕÒÁÔÏÒ HostAllow, ... +2. äÏÂÁ×ÉÔØ ÐÁÒÁÍÅÔÒÙ × Á×ÔÏÒÉÚÁÔÏÒ FloodControl +3. äÏÐÉÓÁÎÉÅ ÍÏÄÕÌÅÊ + - VPN +4. óÅÒ×ÉÓÙ +5. ëÏÒÐÏÒÁÃÉÉ +6. óÔÁÒÔÏ×ÙÅ ÓËÒÉÐÔÙ ÄÌÑ ÒÁÚÎÙÈ ïó diff --git a/projects/stargazer/actions.h b/projects/stargazer/actions.h new file mode 100644 index 00000000..53dde92b --- /dev/null +++ b/projects/stargazer/actions.h @@ -0,0 +1,87 @@ +#ifndef __ACTIONS_H__ +#define __ACTIONS_H__ + +// Usage: +// +// ACTIONS_LIST actionsList; +// CLASS myClass; +// DATA1 myData1; +// DATA2 myData2; +// +// actionsList.Enqueue(myClass, &CLASS::myMethod1, myData1); +// actionsList.Enqueue(myClass, &CLASS::myMethod2, myData2); +// +// actionsList.InvokeAll(); + +#include +#include +#include + +// Generalized actor type - a method of some class with one argument +template +struct ACTOR +{ +typedef void (ACTIVE_CLASS::*TYPE)(DATA_TYPE); +}; + +// Abstract base action class for polymorphic action invocation +class BASE_ACTION +{ +public: + virtual ~BASE_ACTION() {}; + virtual void Invoke() = 0; +}; + +// Concrete generalized action type - an actor with it's data and owner +template +class ACTION : public BASE_ACTION, + public std::unary_function +{ +public: + ACTION(ACTIVE_CLASS & ac, + typename ACTOR::TYPE a, + DATA_TYPE d) + : activeClass(ac), actor(a), data(d) {}; + void Invoke(); +private: + ACTIVE_CLASS & activeClass; + typename ACTOR::TYPE actor; + DATA_TYPE data; +}; + +// A list of an actions +// All methods are thread-safe +class ACTIONS_LIST : private std::list +{ +public: + // Just a typedef for parent class + typedef std::list parent; + + // Initialize mutex + ACTIONS_LIST(); + // Delete actions and destroy mutex + ~ACTIONS_LIST(); + + parent::iterator begin(); + parent::iterator end(); + parent::const_iterator begin() const; + parent::const_iterator end() const; + + bool empty() const; + size_t size() const; + void swap(ACTIONS_LIST & list); + + // Add an action to list + template + void Enqueue(ACTIVE_CLASS & ac, + typename ACTOR::TYPE a, + DATA_TYPE d); + // Invoke all actions in the list + void InvokeAll(); +private: + mutable pthread_mutex_t mutex; +}; + +#include "actions.inl.h" + +#endif diff --git a/projects/stargazer/actions.inl.h b/projects/stargazer/actions.inl.h new file mode 100644 index 00000000..c29a63cd --- /dev/null +++ b/projects/stargazer/actions.inl.h @@ -0,0 +1,111 @@ +#ifndef __ACTIONS_INL_H__ +#define __ACTIONS_INL_H__ + +#include + +#include "stg_locker.h" + +// Polymorphick action invocation +template +inline +void ACTION::Invoke() +{ +(activeClass.*actor)(data); +} + +inline +ACTIONS_LIST::ACTIONS_LIST() + : mutex() +{ +pthread_mutex_init(&mutex, NULL); +}; + +// Delete all actions before deleting list +inline +ACTIONS_LIST::~ACTIONS_LIST() +{ + + { + STG_LOCKER(&mutex, __FILE__, __LINE__); + + parent::iterator it(parent::begin()); + while (it != parent::end()) + { + delete *it++; + } + } + +pthread_mutex_destroy(&mutex); +}; + +inline +ACTIONS_LIST::parent::iterator ACTIONS_LIST::begin() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return parent::begin(); +}; + +inline +ACTIONS_LIST::parent::iterator ACTIONS_LIST::end() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return parent::end(); +}; + +inline +ACTIONS_LIST::parent::const_iterator ACTIONS_LIST::begin() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return parent::begin(); +}; + +inline +ACTIONS_LIST::parent::const_iterator ACTIONS_LIST::end() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return parent::end(); +}; + +inline +bool ACTIONS_LIST::empty() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return parent::empty(); +}; + +inline +size_t ACTIONS_LIST::size() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return parent::size(); +}; + +inline +void ACTIONS_LIST::swap(ACTIONS_LIST & list) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +parent::swap(list); +}; + +template +inline +void ACTIONS_LIST::Enqueue(ACTIVE_CLASS & ac, + typename ACTOR::TYPE a, + DATA_TYPE d) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +push_back(new ACTION(ac, a, d)); +} + +inline +void ACTIONS_LIST::InvokeAll() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +std::for_each( + parent::begin(), + parent::end(), + std::mem_fun(&BASE_ACTION::Invoke) +); +}; + +#endif diff --git a/projects/stargazer/admin.cpp b/projects/stargazer/admin.cpp new file mode 100644 index 00000000..867717aa --- /dev/null +++ b/projects/stargazer/admin.cpp @@ -0,0 +1,115 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.13 $ + $Date: 2010/10/04 20:16:09 $ + $Author: faust $ + */ + +#include "admin.h" +#include "common.h" + +//----------------------------------------------------------------------------- +ADMIN::ADMIN() + : conf(), + ip(0), + WriteServLog(GetStgLogger()) +{ +} +//----------------------------------------------------------------------------- +ADMIN::ADMIN(const ADMIN_CONF & ac) + : conf(ac), + ip(0), + WriteServLog(GetStgLogger()) +{ +} +//----------------------------------------------------------------------------- +ADMIN::ADMIN(const PRIV & priv, const std::string & login, const std::string & password) + : conf(priv, login, password), + ip(0), + WriteServLog(GetStgLogger()) +{ +} +//----------------------------------------------------------------------------- +ADMIN & ADMIN::operator=(const ADMIN & adm) +{ +if (&adm == this) + return *this; + +conf = adm.conf; +ip = adm.ip; +return *this; +} +//----------------------------------------------------------------------------- +ADMIN & ADMIN::operator=(const ADMIN_CONF & ac) +{ +conf = ac; +return *this; +} +//----------------------------------------------------------------------------- +bool ADMIN::operator==(const ADMIN & rhs) const +{ +return conf.login == rhs.GetLogin(); +} +//----------------------------------------------------------------------------- +bool ADMIN::operator!=(const ADMIN & rhs) const +{ +return conf.login != rhs.GetLogin(); +} +//----------------------------------------------------------------------------- +bool ADMIN::operator<(const ADMIN & rhs) const +{ +return conf.login < rhs.GetLogin(); +} +//----------------------------------------------------------------------------- +bool ADMIN::operator<=(const ADMIN & rhs) const +{ +return conf.login <= rhs.GetLogin(); +} +//----------------------------------------------------------------------------- +string ADMIN::GetAdminIPStr() const +{ +return inet_ntostring(ip); +} +//----------------------------------------------------------------------------- +void ADMIN::PrintAdmin() const +{ +printfd(__FILE__, "=======================================\n"); +printfd(__FILE__, "login %s\n", conf.login.c_str()); +printfd(__FILE__, "password %s\n", conf.password.c_str()); +printfd(__FILE__, "ChgConf %d\n", conf.priv.userConf); +printfd(__FILE__, "ChgStat %d\n", conf.priv.userStat); +printfd(__FILE__, "ChgCash %d\n", conf.priv.userCash); +printfd(__FILE__, "UsrAddDel %d\n", conf.priv.userAddDel); +printfd(__FILE__, "ChgAdmin %d\n", conf.priv.adminChg); +printfd(__FILE__, "ChgTariff %d\n", conf.priv.tariffChg); +printfd(__FILE__, "=======================================\n"); +} +//----------------------------------------------------------------------------- +const string ADMIN::GetLogStr() const +{ +return "Admin \'" + conf.login + "\', " + GetAdminIPStr() + ":"; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/admin.h b/projects/stargazer/admin.h new file mode 100644 index 00000000..45979ab7 --- /dev/null +++ b/projects/stargazer/admin.h @@ -0,0 +1,78 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.14 $ + $Date: 2010/10/04 20:15:43 $ + $Author: faust $ + */ + +#ifndef ADMIN_H +#define ADMIN_H + +#include + +#include "os_int.h" +#include "admin_conf.h" +#include "stg_logger.h" + +using namespace std; + +//----------------------------------------------------------------------------- +class ADMIN +{ +public: + ADMIN(); + ADMIN(const ADMIN_CONF & ac); + ADMIN(const PRIV & priv, + const std::string & login, + const std::string & password); + ~ADMIN() {}; + + ADMIN & operator=(const ADMIN &); + ADMIN & operator=(const ADMIN_CONF &); + bool operator==(const ADMIN & rhs) const; + bool operator!=(const ADMIN & rhs) const; + bool operator<(const ADMIN & rhs) const; + bool operator<=(const ADMIN & rhs) const; + + const string & GetPassword() const { return conf.password; }; + const string & GetLogin() const { return conf.login; }; + PRIV const * GetPriv() const { return &conf.priv; }; + uint16_t GetPrivAsInt() const { return conf.priv.ToInt(); }; + const ADMIN_CONF & GetConf() const { return conf; }; + void PrintAdmin() const; + uint32_t GetAdminIP() const { return ip; }; + string GetAdminIPStr() const; + void SetAdminIP(uint32_t ip) { ADMIN::ip = ip; }; + const string GetLogStr() const; + +private: + ADMIN_CONF conf; + uint32_t ip; + STG_LOGGER & WriteServLog; +}; +//----------------------------------------------------------------------------- + +#endif diff --git a/projects/stargazer/admins.cpp b/projects/stargazer/admins.cpp new file mode 100644 index 00000000..4e89c9fd --- /dev/null +++ b/projects/stargazer/admins.cpp @@ -0,0 +1,321 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.15 $ + $Date: 2010/10/04 20:17:12 $ + $Author: faust $ + */ + +#include +#include +#include + +#include "admins.h" +#include "admin.h" +#include "common.h" + +using namespace std; + +//----------------------------------------------------------------------------- +ADMINS::ADMINS(BASE_STORE * st) + : stg(0xFFFF, "@stargazer", ""), + noAdmin(0xFFFF, "NO-ADMIN", ""), + data(), + store(st), + WriteServLog(GetStgLogger()), + searchDescriptors(), + handle(0) +{ +pthread_mutex_init(&mutex, NULL); +ReadAdmins(); +} +//----------------------------------------------------------------------------- +int ADMINS::Add(const string & login, const ADMIN & admin) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +const PRIV * priv = admin.GetPriv(); + +if (!priv->adminChg) + { + string s = admin.GetLogStr() + " Add administrator \'" + login + "\'. Access denied."; + strError = "Access denied."; + WriteServLog(s.c_str()); + return -1; + } + +ADMIN adm(0, login, ""); +admin_iter ai(find(data.begin(), data.end(), adm)); + +if (ai != data.end()) + { + strError = "Administrator \'" + login + "\' cannot not be added. Administrator alredy exist."; + WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + + return -1; + } + +data.push_back(adm); +/*ADMIN_CONF ac; +ac.login = login;*/ +if (store->AddAdmin(login) == 0 /*&& store->SaveAdmin(ac) == 0*/) + { + WriteServLog("%s Administrator \'%s\' added.", + admin.GetLogStr().c_str(), login.c_str()); + return 0; + } + +strError = "Administrator \'" + login + "\' was not added. Error: " + store->GetStrError(); +WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + +return -1; +} +//----------------------------------------------------------------------------- +int ADMINS::Del(const string & login, const ADMIN & admin) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +ADMIN adm(0, login, ""); +const PRIV * priv = admin.GetPriv(); + +if (!priv->adminChg) + { + string s = admin.GetLogStr() + " Delete administrator \'" + login + "\'. Access denied."; + strError = "Access denied."; + WriteServLog(s.c_str()); + return -1; + } + +admin_iter ai(find(data.begin(), data.end(), adm)); + +if (ai == data.end()) + { + strError = "Administrator \'" + login + "\' cannot be deleted. Administrator does not exist."; + WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + return -1; + } + +map::iterator si; +si = searchDescriptors.begin(); +while (si != searchDescriptors.end()) + { + if (si->second == ai) + (si->second)++; + si++; + } + +data.remove(*ai); +if (store->DelAdmin(login) < 0) + { + strError = "Administrator \'" + login + "\' was not deleted. Error: " + store->GetStrError(); + WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + + return -1; + } + +WriteServLog("%s Administrator \'%s\' deleted.", admin.GetLogStr().c_str(), login.c_str()); +return 0; +} +//----------------------------------------------------------------------------- +int ADMINS::Change(const ADMIN_CONF & ac, const ADMIN & admin) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +const PRIV * priv = admin.GetPriv(); + +if (!priv->adminChg) + { + string s = admin.GetLogStr() + " Change administrator \'" + ac.login + "\'. Access denied."; + strError = "Access denied."; + WriteServLog(s.c_str()); + return -1; + } + +ADMIN adm(0, ac.login, ""); +admin_iter ai(find(data.begin(), data.end(), adm)); + +if (ai == data.end()) + { + strError = "Administrator \'" + ac.login + "\' cannot be changed " + ". Administrator does not exist."; + WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + return -1; + } + +*ai = ac; +if (store->SaveAdmin(ac)) + { + WriteServLog("Cannot write admin %s.", ac.login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + return -1; + } + +WriteServLog("%s Administrator \'%s\' changed.", + admin.GetLogStr().c_str(), ac.login.c_str()); + +return 0; +} +//----------------------------------------------------------------------------- +int ADMINS::ReadAdmins() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +vector adminsList; +if (store->GetAdminsList(&adminsList) < 0) + { + WriteServLog(store->GetStrError().c_str()); + return -1; + } + +for (unsigned int i = 0; i < adminsList.size(); i++) + { + ADMIN_CONF ac(0, adminsList[i], ""); + + if (store->RestoreAdmin(&ac, adminsList[i])) + { + WriteServLog(store->GetStrError().c_str()); + return -1; + } + + data.push_back(ADMIN(ac)); + } +return 0; +} +//----------------------------------------------------------------------------- +void ADMINS::PrintAdmins() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +const_admin_iter ai(data.begin()); +while (ai != data.end()) + { + ai->PrintAdmin(); + ai++; + } +} +//----------------------------------------------------------------------------- +bool ADMINS::FindAdmin(const string & l, ADMIN * admin) const +{ +assert(admin != NULL && "Pointer to admin is not null"); + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +if (data.empty()) + { + printfd(__FILE__, "no admin in system!\n"); + *admin = noAdmin; + return false; + } + +ADMIN adm(0, l, ""); +const_admin_iter ai(find(data.begin(), data.end(), adm)); + +if (ai != data.end()) + { + *admin = *ai; + return false; + } + +return true; +} +//----------------------------------------------------------------------------- +bool ADMINS::AdminExists(const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +if (data.empty()) + { + printfd(__FILE__, "no admin in system!\n"); + return true; + } + +ADMIN adm(0, login, ""); +const_admin_iter ai(find(data.begin(), data.end(), adm)); + +if (ai != data.end()) + return true; + +return false; +} +//----------------------------------------------------------------------------- +bool ADMINS::AdminCorrect(const string & login, const std::string & password, ADMIN * admin) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +if (data.empty()) + { + printfd(__FILE__, "no admin in system!\n"); + return true; + } + +ADMIN adm(0, login, ""); +const_admin_iter ai(find(data.begin(), data.end(), adm)); + +if (ai == data.end()) + { + return false; + } + +if (ai->GetPassword() != password) + { + return false; + } + +*admin = *ai; + +return true; +} +//----------------------------------------------------------------------------- +int ADMINS::OpenSearch() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +handle++; +searchDescriptors[handle] = data.begin(); +return handle; +} +//----------------------------------------------------------------------------- +int ADMINS::SearchNext(int h, ADMIN_CONF * ac) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +if (searchDescriptors.find(h) == searchDescriptors.end()) + { + WriteServLog("ADMINS. Incorrect search handle."); + return -1; + } + +if (searchDescriptors[h] == data.end()) + return -1; + +ADMIN a = *searchDescriptors[h]++; + +*ac = a.GetConf(); + +return 0; +} +//----------------------------------------------------------------------------- +int ADMINS::CloseSearch(int h) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +if (searchDescriptors.find(h) != searchDescriptors.end()) + { + searchDescriptors.erase(searchDescriptors.find(h)); + return 0; + } + +WriteServLog("ADMINS. Incorrect search handle."); +return -1; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/admins.h b/projects/stargazer/admins.h new file mode 100644 index 00000000..3e04ebdf --- /dev/null +++ b/projects/stargazer/admins.h @@ -0,0 +1,88 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.10 $ + $Date: 2010/10/04 20:17:12 $ + $Author: faust $ + */ + +#ifndef ADMINS_H +#define ADMINS_H + +#include +#include +#include + +#include "admin.h" +#include "stg_locker.h" +#include "base_store.h" +#include "noncopyable.h" + +using namespace std; + +//----------------------------------------------------------------------------- +class ADMINS : private NONCOPYABLE +{ +public: + ADMINS(BASE_STORE * st); + ~ADMINS() {}; + + int Add(const string & login, const ADMIN & admin); + int Del(const string & login, const ADMIN & admin); + int Change(const ADMIN_CONF & ac, const ADMIN & admin); + void PrintAdmins() const; + const ADMIN GetSysAdmin() const { return stg; }; + const ADMIN GetNoAdmin() const { return noAdmin; }; + bool FindAdmin(const string & l, ADMIN * admin) const; + bool AdminExists(const std::string & login) const; + bool AdminCorrect(const std::string & login, + const std::string & password, + ADMIN * admin) const; + const string & GetStrError() { return strError; }; + + int OpenSearch() const; + int SearchNext(int, ADMIN_CONF * ac) const; + int CloseSearch(int) const; + +private: + typedef list::iterator admin_iter; + typedef list::const_iterator const_admin_iter; + + int ReadAdmins(); + + ADMIN stg; + ADMIN noAdmin; + list data; + BASE_STORE * store; + STG_LOGGER & WriteServLog; + mutable map searchDescriptors; + mutable unsigned int handle; + mutable pthread_mutex_t mutex; + string strError; +}; +//----------------------------------------------------------------------------- +#endif + + diff --git a/projects/stargazer/build b/projects/stargazer/build new file mode 100755 index 00000000..3d4b1f92 --- /dev/null +++ b/projects/stargazer/build @@ -0,0 +1,413 @@ + #!/bin/sh + +# $Revision: 1.57 $ +# $Author: faust $ +# $Date: 2010/05/09 12:39:01 $ +###################################################### + +# Installation path prefix + +PREFIX="" + +# Binaries access bits + +BIN_MODE=0755 + +# Data files access bits + +DATA_MODE=0644 + +# Binaries and data files owner + +OWNER=root + +# Name of the firebird's group +# Need for chown directory with firebird db + +FIREBIRD_GROUP=firebird + +# Database address +# Firebird must have priviledges to read/write for specified path + +DB_ADDRESS="localhost:/var/stargazer/stargazer.fdb" + +# Database user +# This user must have priviledges to create database + +DB_USER="stg" + +# Database password + +DB_PASSWORD="123456" + +# Full path to isql utility +# Note: Debian users have to specify path to isql-fb utility + +FIREBIRD_ISQL="/opt/firebird/bin/isql" + +OS=unknown +sys=`uname -s` +release=`uname -r | cut -b1` +BUILD_DIR=`pwd` +CONFFILE="../../Makefile.conf" +VAR_DIR="./inst/var/stargazer" +MIN_XMLRPCC_VERSION="1.06.27" +XMLRPC_FEATURES="c++2 abyss-server" + + +if [ -z $1 ] +then + DEFS="$DEFS -DNDEBUG" + MAKEOPTS="-j1" +else + if [ "$1" = "debug" ] + then + DEFS="$DEFS -DDEBUG" + MAKEOPTS="-j1" + CXXFLAGS="$CXXFLAGS -g3 -W -Wall" + else + DEFS="$DEFS -DNDEBUG" + MAKEOPTS="-j1" + fi +fi + +CXXFLAGS="$CXXFLAGS -I/usr/local/include" +LDFLAGS="$LDFLAGS -L/usr/local/lib" + +if [ "$sys" = "Linux" ] +then + OS=linux + release="" + ETC_DIR="./inst/linux/etc/stargazer" + MAKE="make" +fi + +if [ "$sys" = "FreeBSD" ] +then + case $release in + 4) OS=bsd;; + 5) OS=bsd5;; + 6) OS=bsd5;; + 7) OS=bsd7;; + 8) OS=bsd7;; + *) OS=unknown;; + esac + ETC_DIR="./inst/freebsd/etc/stargazer" + MAKE="gmake" +fi + +if [ "$OS" = "unknown" ] +then + echo "#############################################################################" + echo "# Sorry, but stargazer currently supported by Linux, FreeBSD 4.x, 5.x, 6.x #" + echo "#############################################################################" + exit 1 +fi + +echo "#############################################################################" +echo " Building STG 2.4 for $sys $release" +echo "#############################################################################" + +STG_LIBS="stg_logger.lib + stg_locker.lib + crypto.lib + common.lib + script_executer.lib + conffiles.lib + hostallow.lib + pinger.lib + dotconfpp.lib" + +PLUGINS="authorization/ao + authorization/inetaccess + configuration/sgconfig + other/ping + other/rscript + other/radius + store/files + capture/cap_nf" + +if [ "$OS" = "linux" ] +then + DEFS="$DEFS -DLINUX" + PLUGINS="$PLUGINS + capture/ether_linux + capture/ipq_linux" + SHELL="/bin/bash" + LIB_THREAD=-lpthread +else + if [ "$OS" = "bsd" ] + then + DEFS="$DEFS -DFREE_BSD" + LIB_THREAD=-lc_r + else + DEFS="$DEFS -DFREE_BSD5" + if [ "$OS" = "bsd7" ] + then + LIB_THREAD=-lpthread + else + LIB_THREAD=-lc_r + fi + fi + PLUGINS="$PLUGINS + capture/ether_freebsd + capture/divert_freebsd" + SHELL="/usr/local/bin/bash" +fi + +echo -n "Checking endianess... " +echo "int main() { int probe = 0x00000001; return *(char *)&probe; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS build_check.c -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + echo "FAIL!" + echo "Endianess checking failed" + exit; +else + ./fake + if [ $? = 1 ] + then + ARCH=le + CXXFLAGS="$CXXFLAGS -DARCH_LE" + echo "Little Endian" + else + ARCH=be + CXXFLAGS="$CXXFLAGS -DARCH_BE" + echo "Big Endian" + fi +fi +rm -f fake + +echo -n "Checking for -lexpat... " +echo "int main() { return 0; }" > build_check.c +gcc $CXXFLAGS $LDFLAGS build_check.c -lexpat -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + CHECK_EXPAT=no + echo "no" +else + CHECK_EXPAT=yes + echo "yes" +fi +rm -f fake + +echo -n "Checking for -lfbclient... " +gcc $CXXFLAGS $LDFLAGS build_check.c -lfbclient $LIB_THREAD -o fake > /dev/null 2> /dev/null +if [ $? != 0 ] +then + CHECK_FBCLIENT=no + echo "no" +else + CHECK_FBCLIENT=yes + echo "yes" +fi +rm -f fake + +echo -n "Checking for mysql_config... " +MYSQL_VERSION=`mysql_config --version 2> /dev/null` +if [ $? != 0 ] +then + echo "no"; + echo -n "Checking for -lmysqlclient... " + gcc $CXXFLAGS $LDFLAGS build_check.c -lmysqlclient_r $LIB_THREAD -o fake > /dev/null 2> /dev/null + if [ $? != 0 ] + then + CHECK_MYSQLCLIENT=no + echo "no" + else + CHECK_MYSQLCLIENT=yes + echo "yes" + fi + rm -f fake +else + echo "yes" + echo -n "Checking for mysql_config --cflags... " + MYSQL_CFLAGS=`mysql_config --cflags 2> /dev/null` + if [ $? != 0 ] + then + CHECK_MYSQLCLIENT=no + echo "no" + else + #CXXFLAGS="$CXXFLAGS $MYSQL_CFLAGS" + echo "[$MYSQL_CFLAGS]" + echo -n "Checking for mysql_config --libs_r... " + MYSQL_LDFLAGS=`mysql_config --libs_r 2> /dev/null` + if [ $? != 0 ] + then + CHECK_MYSQLCLIENT=no + echo "no" + else + CHECK_MYSQLCLIENT=yes + #LDFLAGS="$LDFLAGS $MYSQL_LDFLAGS" + echo "[$MYSQL_LDFLAGS]" + fi + fi +fi + +echo -n "Checking for pg_config... " +PG_VERSION=`pg_config --version 2> /dev/null` +if [ $? != 0 ] +then + echo "no"; + echo -n "Checking for -lpq... " + gcc $CXXFLAGS $LDFLAGS build_check.c -lpq $LIB_THREAD -o fake > /dev/null 2> /dev/null + if [ $? != 0 ] + then + CHECK_PQ=no + echo "no" + else + CHECK_PQ=yes + echo "yes" + fi + rm -f fake +else + echo "yes"; + echo -n "Checking for pg_config --includedir... " + PG_CFLAGS=`pg_config --includedir 2> /dev/null` + if [ $? != 0 ] + then + CHECK_PQ=no + echo "no" + else + echo "[$PG_CFLAGS]" + echo -n "Checking for pg_config --libdir... " + PG_LDFLAGS=`pg_config --libdir 2> /dev/null` + if [ $? != 0 ] + then + CHECK_PQ=no + echo "no" + else + CHECK_PQ=yes + echo "[$PG_LDFLAGS]" + fi + fi +fi + +echo -n "Checking for xmlrpc-c-config... " +XMLRPCC_VERSION=`xmlrpc-c-config $XMLRPC_FEATURES --version 2> /dev/null` +if [ $? != 0 ] +then + echo "no"; + echo -n "Checking for -lxmlrpc... " + gcc $CXXFLAGS $LDFLAGS build_check.c -lxmlrpc $LIB_THREAD -o fake > /dev/null 2> /dev/null + if [ $? != 0 ] + then + CHECK_XMLRPC=no + echo "no" + else + CHECK_XMLRPC=yes + echo "yes" + fi + rm -f fake +elif [ "$XMLRPCC_VERSION" \< "$MIN_XMLRPCC_VERSION" ] +then + echo "no (need at least $MIN_XMLRPCC_VERSION, actual $XMLRPCC_VERSION)"; + CHECK_XMLRPC=no +else + echo "yes (version $XMLRPCC_VERSION)"; + echo -n "Checking for xmlrpc-c-config --cflags... " + XMLRPC_CFLAGS=`xmlrpc-c-config $XMLRPC_FEATURES --cflags 2> /dev/null` + if [ $? != 0 ] + then + CHECK_XMLRPC=no + echo "no" + else + echo "[$XMLRPC_CFLAGS]" + echo -n "Checking for xmlrpc-c-config --libs... " + XMLRPC_LDFLAGS=`xmlrpc-c-config $XMLRPC_FEATURES --libs 2> /dev/null` + if [ $? != 0 ] + then + CHECK_XMLRPC=no + echo "no" + else + CHECK_XMLRPC=yes + echo "[$XMLRPC_LDFLAGS]" + fi + fi +fi + +rm -f build_check.c + +if [ "$CHECK_EXPAT" != "yes" ] +then + echo "-lexpat not found!" + exit 1 +fi + +if [ "$CHECK_FBCLIENT" = "yes" ] +then + STG_LIBS="$STG_LIBS + ibpp.lib" + PLUGINS="$PLUGINS + store/firebird" +fi + +if [ "$CHECK_PQ" = "yes" ] +then + PLUGINS="$PLUGINS + store/postgresql" +fi + +if [ "$CHECK_MYSQLCLIENT" = "yes" ] +then + PLUGINS="$PLUGINS + store/mysql" +fi + +if [ "$CHECK_XMLRPC" = "yes" ] +then + PLUGINS="$PLUGINS + configuration/rpcconfig" +fi + +echo "OS=$OS" > $CONFFILE +echo "STG_TIME=yes" >> $CONFFILE +echo "DIR_BUILD=$BUILD_DIR" >> $CONFFILE +echo "DIR_LIB=\$(DIR_BUILD)/../../lib" >> $CONFFILE +echo "DIR_LIBSRC=\$(DIR_BUILD)/../../stglibs" >> $CONFFILE +echo "DIR_INCLUDE=\$(DIR_BUILD)/../../include" >> $CONFFILE +echo "DIR_MOD=\$(DIR_BUILD)/modules" >> $CONFFILE +echo "DIR_PLUGINS=\$(DIR_BUILD)/plugins" >> $CONFFILE +echo "ARCH=$ARCH" >> $CONFFILE +echo "CHECK_EXPAT=$CHECK_EXPAT" >> $CONFFILE +echo "CHECK_FBCLIENT=$CHECK_FBCLIENT" >> $CONFFILE +echo "CHECK_MYSQLCLIENT=$CHECK_MYSQLCLIENT" >> $CONFFILE +echo "CHECK_PQ=$CHECK_PQ" >> $CONFFILE +echo "CHECK_XMLRPC=$CHECK_XMLRPC" >> $CONFFILE +echo "DEFS=$DEFS" >> $CONFFILE +echo -n "STG_LIBS=" >> $CONFFILE +for lib in $STG_LIBS +do + echo -n "$lib " >> $CONFFILE +done +echo "" >> $CONFFILE +echo -n "PLUGINS=" >> $CONFFILE +for plugin in $PLUGINS +do + echo -n "$plugin " >> $CONFFILE +done +echo "" >> $CONFFILE +echo "SHELL=$SHELL" >> $CONFFILE +echo "CXXFLAGS=$CXXFLAGS" >> $CONFFILE +echo "LDFLAGS=$LDFLAGS" >> $CONFFILE +echo "LIB_THREAD=$LIB_THREAD" >> $CONFFILE +echo "PREFIX=$PREFIX" >> $CONFFILE +echo "BIN_MODE=$BIN_MODE" >> $CONFFILE +echo "DATA_MODE=$DATA_MODE" >> $CONFFILE +echo "OWNER=$OWNER" >> $CONFFILE +echo "VAR_DIR=$VAR_DIR" >> $CONFFILE +echo "ETC_DIR=$ETC_DIR" >> $CONFFILE +echo "DB_ADDRESS=$DB_ADDRESS" >> $CONFFILE +echo "DB_USER=$DB_USER" >> $CONFFILE +echo "DB_PASSWORD=$DB_PASSWORD" >> $CONFFILE +echo "FIREBIRD_ISQL=$FIREBIRD_ISQL" >> $CONFFILE +echo "FIREBIRD_GROUP=$FIREBIRD_GROUP" >> $CONFFILE + +mkdir -p modules + +if [ "$1" != "debug" ] +then + $MAKE $MAKEOPTS +else + echo -e "\n\n\nDebug build. Type $MAKE explicitly" +fi diff --git a/projects/stargazer/devbuild b/projects/stargazer/devbuild new file mode 100755 index 00000000..05856b20 --- /dev/null +++ b/projects/stargazer/devbuild @@ -0,0 +1,5 @@ +#!/bin/bash + +export CFLAGS=-DTRAFF_STAT_WITH_PORTS +./build debug + diff --git a/projects/stargazer/eventloop.cpp b/projects/stargazer/eventloop.cpp new file mode 100644 index 00000000..4cd6074b --- /dev/null +++ b/projects/stargazer/eventloop.cpp @@ -0,0 +1,109 @@ +#include +#include + +#include "eventloop.h" +#include "stg_locker.h" +#include "common.h" + +EVENT_LOOP::EVENT_LOOP() + : ACTIONS_LIST() +{ +pthread_mutex_init(&_mutex, NULL); +pthread_cond_init(&_condition, NULL); +} + +EVENT_LOOP::~EVENT_LOOP() +{ +pthread_cond_destroy(&_condition); +pthread_mutex_destroy(&_mutex); +} + +bool EVENT_LOOP::Start() +{ +_running = true; +if (pthread_create(&_tid, NULL, Run, this)) + { + printfd(__FILE__, "EVENT_LOOP::Start - Failed to create thread: '%s'\n", strerror(errno)); + return true; + } +return false; +} + +bool EVENT_LOOP::Stop() +{ +_running = false; +// Wake up thread +pthread_cond_signal(&_condition); +// Wait until thread exit +pthread_join(_tid, NULL); +return false; +} + +void * EVENT_LOOP::Run(void * self) +{ +EVENT_LOOP * ev = static_cast(self); +ev->Runner(); +return NULL; +} + +void EVENT_LOOP::Runner() +{ +_stopped = false; +printfd(__FILE__, "EVENT_LOOP::Runner - Before start\n"); +while (_running) + { + { + STG_LOCKER lock(&_mutex, __FILE__, __LINE__); + // Check for any actions... + if (empty()) + { + // ... and sleep until new actions added + printfd(__FILE__, "EVENT_LOOP::Runner - Sleeping until new actions arrived\n"); + pthread_cond_wait(&_condition, &_mutex); + } + // Check for running after wake up + if (!_running) + { + // Don't process any actions if stopping + break; + } + } + // Create new empty actions list + ACTIONS_LIST local; + // Fast swap with current + swap(local); + // Invoke all current actions + printfd(__FILE__, "EVENT_LOOP::Runner - Invoke %d actions\n", local.size()); + local.InvokeAll(); + } +printfd(__FILE__, "EVENT_LOOP::Runner - Before stop\n"); +_stopped = true; +} + +namespace { + +pthread_mutex_t singletonMutex; + +} + +EVENT_LOOP & EVENT_LOOP_SINGLETON::GetInstance() +{ +// Double-checking technique +if (!_instance) + { + STG_LOCKER lock(&singletonMutex, __FILE__, __LINE__); + if (!_instance) + { + CreateInstance(); + } + } +return *_instance; +} + +void EVENT_LOOP_SINGLETON::CreateInstance() +{ +static EVENT_LOOP loop; +_instance = &loop; +} + +EVENT_LOOP * EVENT_LOOP_SINGLETON::_instance = NULL; diff --git a/projects/stargazer/eventloop.h b/projects/stargazer/eventloop.h new file mode 100644 index 00000000..e1b7f6aa --- /dev/null +++ b/projects/stargazer/eventloop.h @@ -0,0 +1,63 @@ +#ifndef __EVENT_LOOP_H__ +#define __EVENT_LOOP_H__ + +#include + +#include "noncopyable.h" +#include "actions.h" + +class EVENT_LOOP : private NONCOPYABLE, + private ACTIONS_LIST +{ + public: + bool Start(); + bool Stop(); + bool IsRunning() const { return _running; }; + + template + void Enqueue(ACTIVE_CLASS & ac, + typename ACTOR::TYPE a, + DATA_TYPE d); + + private: + bool _running; + bool _stopped; + pthread_t _tid; + pthread_mutex_t _mutex; + pthread_cond_t _condition; + + EVENT_LOOP(); + virtual ~EVENT_LOOP(); + + static void * Run(void *); + void Runner(); + + friend class EVENT_LOOP_SINGLETON; +}; + +class EVENT_LOOP_SINGLETON : private NONCOPYABLE +{ + public: + static EVENT_LOOP & GetInstance(); + + private: + static EVENT_LOOP * _instance; + static void CreateInstance(); + + EVENT_LOOP_SINGLETON() {}; + ~EVENT_LOOP_SINGLETON() {}; +}; + +template +void EVENT_LOOP::Enqueue(ACTIVE_CLASS & ac, + typename ACTOR::TYPE a, + DATA_TYPE d) +{ +STG_LOCKER lock(&_mutex, __FILE__, __LINE__); +// Add new action +ACTIONS_LIST::Enqueue(ac, a, d); +// Signal about new action +pthread_cond_signal(&_condition); +} + +#endif diff --git a/projects/stargazer/inst/freebsd/etc/stargazer/OnChange b/projects/stargazer/inst/freebsd/etc/stargazer/OnChange new file mode 100755 index 00000000..a9272287 --- /dev/null +++ b/projects/stargazer/inst/freebsd/etc/stargazer/OnChange @@ -0,0 +1,6 @@ +login=$1 +param=$2 +oldValue=$3 +newValue=$4 + +#echo "User: '$login'. Parameter $param changed from '$oldValue' to '$newValue'" >> /var/stargazer/users.chg.log \ No newline at end of file diff --git a/projects/stargazer/inst/freebsd/etc/stargazer/OnConnect b/projects/stargazer/inst/freebsd/etc/stargazer/OnConnect new file mode 100755 index 00000000..cf4abfb4 --- /dev/null +++ b/projects/stargazer/inst/freebsd/etc/stargazer/OnConnect @@ -0,0 +1,22 @@ +#üÔÏÔ ÓËÒÉÐÔ ×ÙÚÙ×ÁÅÔÓÑ × ÍÏÍÅÎÔ, ËÏÇÄÁ ÐÏÌØÚÏ×ÁÔÅÌØ +#ÕÓÐÅÛÎÏ ÐÒÏÛÅÌ Á×ÔÏÒÉÚÁÃÉÀ ÎÁ ÓÅÒ×ÅÒÅ. úÁÄÁÞÁ ÓËÒÉÐÔÁ - ÐÅÒÅÓÔÒÏÉÔØ +#ÆÁÊÒ×ÏÌ ÔÁË, ÞÔÏ ÂÙ ÐÏÌØÚÏ×ÁÔÅÌØ ÐÏÌÕÞÉÌ ÄÏÓÔÕÐ × ÉÎÔÅÒÎÅÔ + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to connect +DIRS=$5 + + +#echo "C `date +%Y.%m.%d-%H.%M.%S` $IP $CASH" >> /var/stargazer/users/$LOGIN/connect.log + diff --git a/projects/stargazer/inst/freebsd/etc/stargazer/OnDisconnect b/projects/stargazer/inst/freebsd/etc/stargazer/OnDisconnect new file mode 100755 index 00000000..6f5eeee0 --- /dev/null +++ b/projects/stargazer/inst/freebsd/etc/stargazer/OnDisconnect @@ -0,0 +1,27 @@ +# üÔÏÔ ÓËÒÉÐÔ ×ÙÚÙ×ÁÅÔÓÑ × ÍÏÍÅÎÔ, ËÏÇÄÁ ÐÏÌØÚÏ×ÁÔÅÌØ +# ÖÅÌÁÅÔ ÏÔËÌÀÞÉÔÓÑ ÏÔ ÉÎÔÅÒÎÅÔÁ ÉÌÉ ×ÙÛÅÌ ÔÁÊÍÁÕÔ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ +# É ÓÅÒ×ÅÒ ÓÁÍ ÏÔËÌÀÞÁÅÔ ÐÏÌØÚÏ×ÁÔÅÌÑ +# úÁÄÁÞÁ ÓËÒÉÐÔÁ ÐÏÄÏÂÎÁ ÚÁÄÁÞÅ ÓËÒÉÐÔÁ OnConnect - ÐÅÒÅÓÔÒÏÉÔØ +# ÆÁÊÒ×ÏÌ ÔÁË, ÞÔÏ ÂÙ ÐÏÌØÚÏ×ÁÔÅÌÀ ÚÁËÒÙÔØ ÄÏÓÔÕÐ × ÉÎÔÅÒÎÅÔ + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to disconnect +DIRS=$4 + +#echo "D `date +%Y.%m.%d-%H.%M.%S` $IP $CASH" >> /var/stargazer/users/$LOGIN/connect.log + + + + + diff --git a/projects/stargazer/inst/freebsd/etc/stargazer/OnUserAdd b/projects/stargazer/inst/freebsd/etc/stargazer/OnUserAdd new file mode 100755 index 00000000..655b0eef --- /dev/null +++ b/projects/stargazer/inst/freebsd/etc/stargazer/OnUserAdd @@ -0,0 +1,12 @@ +# éÓÐÏÌØÚÏ×ÁÎÉÅ (ÎÅÉÓÐÏÌØÚÏ×ÁÎÉÅ) ÜÔÏÇÏ ÓËÒÉÐÔÁ ÄÅÌÏ ×ËÕÓÁ. +# ïÎ ÎÅ ×ÙÐÏÌÎÑÅÔ ËÒÉÔÉÞÅÓËÉÈ ÆÕÎËÃÉÊ. åÇÏ ÚÁÄÁÞÁ Á×ÔÍÁÔÉÚÉÒÏ×ÁÔØ +# ÄÅÊÓÔ×ÉÑ ÈÁÒÁËÔÅÒÎÙÅ ÐÒÉ ÄÏÂÁ×ÌÅÎÉÉ ÐÏÌØÚÏ×ÁÔÅÌÑ ÓÅÔÉ, ÎÁÐÒÉÍÅÒ ÄÏÂÁ×ÌÅËÎÉÅ +# ÐÏÌØÚÏ×ÁÔÅÌÀ ÐÏÞÔÙ + +# Login +login=$1 + +#echo "added user $login" >> /var/stargazer/add_del.log + + + diff --git a/projects/stargazer/inst/freebsd/etc/stargazer/OnUserDel b/projects/stargazer/inst/freebsd/etc/stargazer/OnUserDel new file mode 100755 index 00000000..3be6046e --- /dev/null +++ b/projects/stargazer/inst/freebsd/etc/stargazer/OnUserDel @@ -0,0 +1,5 @@ +# Login +login=$1 + +#echo "deleted user $login" >> /var/stargazer/add_del.log + diff --git a/projects/stargazer/inst/freebsd/etc/stargazer/rules b/projects/stargazer/inst/freebsd/etc/stargazer/rules new file mode 100644 index 00000000..1042010f --- /dev/null +++ b/projects/stargazer/inst/freebsd/etc/stargazer/rules @@ -0,0 +1,3 @@ +ALL 192.168.0.0/16 DIR1 +ALL 10.0.0.0/8 DIR2 +ALL 0.0.0.0/0 DIR0 \ No newline at end of file diff --git a/projects/stargazer/inst/freebsd/etc/stargazer/stargazer.conf b/projects/stargazer/inst/freebsd/etc/stargazer/stargazer.conf new file mode 100644 index 00000000..5276071d --- /dev/null +++ b/projects/stargazer/inst/freebsd/etc/stargazer/stargazer.conf @@ -0,0 +1,396 @@ +################################################################################ +# æÁÊÌ ÎÁÓÔÒÏÅË ÓÅÒ×ÅÒÁ stargazer # +################################################################################ + + + +# éÍÑ ÌÏÇ-ÆÁÊÌÁ ËÕÄÁ ÐÉÛÕÔÓÑ ÓÏÂÙÔÉÑ +LogFile = /var/log/stargazer.log + + + +# éÍÑ PID-ÆÁÊÌÁ ËÕÄÁ ÐÉÛÅÔÓÑ ÉÄÅÎÔÉÆÉËÁÔÏÒ ÐÒÏÃÅÓÓÁ +# ðÏ ÕÍÏÌÞÁÎÉÀ /var/run/pid +# PIDFile = /var/run/stargazer.pid + + + +# éÍÑ ÆÁÊÌÁ × ËÏÔÏÒÏÍ ÏÐÒÅÄÅÌÑÀÔÓÑ ÐÒÁ×ÉÌÁ ÐÏÄÓÞÅÔÁ ÔÒÁÆÉËÁ +Rules = /etc/stargazer/rules + + + +# ÷ÒÅÍÑ ÞÅÒÅÚ ËÏÔÏÒÏÅ ÐÉÛÅÔÓÑ d âä ÄÅÔÁÌØÎÁÑ ÓÔÁÔÉÓÔÉËÁ ÐÏÌØÚÏ×ÁÔÅÌÑ +# úÎÁÞÅÎÉÑ: 1, 1/2, 1/4, 1/6. +# 1 - ÒÁÚ × ÞÁc, 1/2 - ÒÁÚ × ÐÏÌ ÞÁÓÁ, 1/4 - ÒÁÚ × 15 ÍÉÎ, 1/6 - ÒÁÚ × 10 ÍÉÎ +DetailStatWritePeriod=1/6 + + + +# ðÅÒÉÏÄÉÞÎÏÓÔØ ÚÁÐÉÓÉ ÚÁÐÉÓÉ × âä ÉÎÆÏÒÍÁÃÉÉ Ï ÓÔÁÔÉÓÔÉËÅ ÐÏÌØÚÏ×ÁÔÅÌÑ (ÍÉÎÕÔÙ) +# ðÒÉ ÂÏÌØÛÏÍ ËÏÌ-×Å ÐÏÌØÚÏ×ÁÔÅÌÅÊ ÜÔÕ ×ÅÌÉÞÉÎÕ ÓÔÏÉÔ Õ×ÅÌÉÞÉÔØ, Ô.Ë. +# ÚÁÐÉÓØ × âä ÍÏÖÅÔ ÚÁÎÉÍÁÔØ ÄÌÉÔÅÌØÎÏÅ ×ÒÅÍÑ. +# úÎÁÞÅÎÉÑ: 1...1440 (ÍÉÎÕÔÙ) +StatWritePeriod = 10 + + + +# äÅÎØ ÓÎÑÔÉÑ ÁÂÏÎÐÌÁÔÙ +# úÎÁÞÅÎÉÑ: 0...31. 0 - ðÏÓÌÅÄÎÉÊ ÄÅÎØ ÍÅÓÑÃÁ +DayFee = 1 + + + +# áÂÏÎÐÌÁÔÁ ÓÎÉÍÁÅÔÓÑ × ÐÏÓÌÅÄÎÉÊ (yes) ÉÌÉ ÐÅÒ×ÙÊ (no) ÄÅÎØ ÕÞÅÔÎÏÇÏ ÐÅÒÉÏÄÁ. +# üÔÏ ×ÌÉÑÅÔ ÎÁ ÔÏ, ËÁË ÂÕÄÅÔ ÓÎÑÔÁ ÁÂÏÎÐÌÁÔÁ (áð) ÐÒÉ ÐÅÒÅÈÏÄÅ ÎÁ ÎÏ×ÙÊ ÔÁÒÉÆ. +# åÓÌÉ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÂÙÌ ÔÁÒÉÆ A Ó áð=100 É ÏÎ ÈÏÞÅÔ ÐÅÒÅÊÔÉ ÎÁ ÔÁÒÉÆ B Ó áð=200, +# ÔÏ ÐÒÉ ÐÅÒÅÈÏÄÅ ÎÁ ÎÏ×ÙÊ ÔÁÒÉÆ ÓÏ ÓÞÅÔÁ ÐÏÌØÚÏ×ÁÔÅÌÑ ÓÎÉÍÅÔÓÑ 100, ÅÓÌÉ +# DayFeeIsLastDay = yes É 200, ÅÓÌÉ DayFeeIsLastDay = no +DayFeeIsLastDay = yes + + + +# äÅÎØ ÓÂÒÏÓÁ ÄÁÎÎÙÈ Ï ÔÒÁÆÉËÅ ÚÁ ÍÅÓÑÃ É ÄÅÎØ ÐÅÒÅÈÏÄÁ ÐÏÌØÚÏ×ÁÔÅÌÅÊ ÎÁ ÎÏ×ÙÅ ÔÁÒÉÆÙ +# úÎÁÞÅÎÉÑ: 0...31. 0 - ðÏÓÌÅÄÎÉÊ ÄÅÎØ ÍÅÓÑÃÁ +DayResetTraff = 1 + + + +# "òÁÚÍÁÚÁÎÎÏÅ" ÓÎÑÔÉÅ ÁÂÏÎÐÌÁÔÙ. óÎÑÔÉÅ áð ÎÅ ÒÁÚ × ÍÅÓÑÃ, Á ËÁÖÄÙÊ +# ÄÅÎØ 1/30 ÉÌÉ 1/31 ÞÁÓÔÉ áð +# úÎÁÞÅÎÉÑ: yes, no +SpreadFee = no + + + +# äÁÎÎÁÑ ÏÐÃÉÑ ÏÐÒÅÄÅÌÑÅÔ ÍÏÖÅÔ ÌÉ ÐÏÌØÚÏ×ÁÔÅÌØ ÐÏÌÕÞÉÔØ ÄÏÓÔÕÐ × ÉÎÔÅÒÅÎÔ +# ÅÓÌÉ Õ ÎÅÇÏ ÎÁ ÓÞÅÔÕ ÎÅÔ ÄÅÎÅÇ, ÎÏ ÏÓÔÁÌÓÑ ÐÒÅÄÏÐÌÁÞÅÎÎÙÊ ÔÒÁÆÉË +# úÎÁÞÅÎÉÑ: yes, no +FreeMbAllowInet = no + + + +# üÔÁ ÏÐÃÉÑ ÏÐÒÅÄÅÌÑÅÔ ÞÔÏ ÂÕÄÅÔ ÐÉÓÁÔØÓÑ × ÓÔÏÉÍÏÓÔØ ÔÒÁÆÉËÁ × detail_stat. +# åÓÌÉ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÅÝÅ ÅÓÔØ ÐÒÅÄÏÐÌÁÞÅÎÎÙÊ ÔÒÁÆÉË É WriteFreeMbTraffCost = no, +# ÔÏ × detail_stat ÓÔÏÉÍÏÓÔØ ÂÕÄÅÔ 0. åÓÌÉ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÕÖÅ ÎÅÔ +# ÐÒÅÄÏÐÌÁÞÅÎÎÏÇÏ ÔÒÁÆÉËÁ É WriteFreeMbTraffCost = no, ÔÏ × detail_stat +# ÂÕÄÅÔ ÚÁÐÉÓÁÎÁ ÓÔÏÉÏÓÔØ ÔÒÁÆÉËÁ. ðÒÉ WriteFreeMbTraffCost = yes ÓÔÏÉÍÏÓÔØ +# ÔÒÁÆÉËÁ ÂÕÄÅÔ ÚÁÐÉÓÁÎÁ × ÌÀÂÏÍ ÓÌÕÞÁÅ. +WriteFreeMbTraffCost = no + + + +# îÅÏÂÑÚÁÔÅÌØÎÙÊ ÐÁÒÁÍÅÔÒ. õËÁÚÙ×ÁÅÔ ÓÎÉÍÁÔØ ÐÏÌÎÕÀ ÁÂÏÎÐÌÁÔÕ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÄÁÖÅ +# ÅÓÌÉ ÏÎ ÂÙÚ ÚÁÍÏÒÏÖÅÎ ÔÏÌØËÏ ÞÁÓÔØ ÕÞÅÔÎÏÇÏ ÐÅÒÉÏÄÁ. +# ðÏ ÕÍÏÌÞÁÎÉÀ ÕÓÔÁÎÏ×ÌÅÎ × no +# FullFee=no + +# îÅÏÂÑÚÁÔÅÌØÎÙÊ ÐÁÒÁÍÅÔÒ ÕËÁÚÙ×ÁÀÝÉÊ ÐÏËÁÚÙ×ÁÔØ ÎÁ ÓÞÅÔÕ É ÐÏÚ×ÏÌÑÔØ +# ÉÓÐÏÌØÚÏ×ÁÔØ ÐÏÌØÚÏ×ÁÔÅÌÀ ÁÂÏÎÐÌÁÔÕ. ðÏ ÕÍÏÌÞÁÎÉÀ ÕÓÔÁÎÏ×ÌÅÎ × yes +# ShowFeeInCash=yes + + + +# îÁÚ×ÁÎÉÑ ÎÁÐÒÁ×ÌÅÎÉÊ. îÁÐÒÁ×ÌÅÎÉÑ ÂÅÚ ÎÁÚ×ÁÎÉÊ ÎÅ ÂÕÄÕÔ ÏÔÏÂÒÁÖÁÔØÓÑ × +# Á×ÔÏÒÉÚÁÔÏÒÅ É ËÏÎÆÉÇÕÒÁÔÏÒÅ. îÁÚ×ÁÎÉÑ ÓÏÓÔÏÑÝÉÅ ÉÚ ÎÅÓËÏÌØËÉÈ ÓÌÏ× ÄÏÌÖÎÙ +# ÂÙÔØ ×ÚÑÔÙ × ËÁ×ÙÞËÉ + + DirName0 = ìÏËÁÌØ + DirName1 = çÏÒÏÄ + DirName2 = íÉÒ + DirName3 = + DirName4 = + DirName5 = "ìÏËÁÌØÎÙÅ ÉÇÒÙ" + DirName6 = + DirName7 = + DirName8 = + DirName9 = + + + + +# ëÏÌ-×Ï ÚÁÐÕÓËÁÅÍÙÈ ÐÒÏÃÅÓÓÏ× stg-exec. +# üÔÉ ÐÒÏÃÅÓÓÙ ÏÔ×ÅÞÁÀÔ ÚÁ ×ÙÐÏÌÎÅÎÉÅ ÓËÒÉÐÔÏ× OnConnect, OnDisconnect, ... +# ëÏÌ-×Ï ÐÒÏÃÅÓÓÏ× ÏÚÎÁÞÁÅÔ ÓËÏÌØËÏ ÓËÒÉÐÔÏ× ÍÏÇÕÔ ×ÙÐÏÌÎÑÔÓÑ ÏÄÎÏ×ÒÅÍÅÎÎÏ. +# úÎÁÞÅÎÉÑ: 1...1024 +ExecutersNum = 1 + + + +# Message Key ÄÌÑ stg-exec. +# éÄÅÎÔÉÆÉËÁÔÏÒ ÏÞÅÒÅÄÉ ÓÏÏÂÝÅÎÉÊ ÄÌÑ ×ÙÐÏÌÎÑÔÅÌÑ ÓËÒÉÐÔÏ×. +# åÇÏ ÉÚÍÅÎÅÎÉÅ ÍÏÖÅÔ ÐÏÎÁÄÏÂÉÔÓÑ ÅÓÌÉ ÅÓÔØ ÎÅÏÂÈÏÄÉÍÏÓÔØ ÚÁÐÕÓÔÉÔØ ÎÅÓËÏÌØËÏ +# ÜËÚÅÍÐÌÑÒÏ× stg. åÓÌÉ ×Ù ÎÅ ÐÏÎÉÍÁÅÔÅ, ÞÔÏ ÜÔÏ, ÎÅ ÔÒÏÇÁÊÔÅ ÜÔÏÔ ÐÁÒÁÍÅÔÒ! +# úÎÁÞÅÎÉÑ: 0...2^32 +# úÎÁÞÅÎÉÅ ÐÏ ÕÍÏÌÞÁÎÉÀ: 5555 +# ExecMsgKey = 5555 + + + +# ðÕÔØ Ë ÄÉÒÅËÔÏÒÉÉ, × ËÏÔÏÒÏÊ ÎÁÈÏÄÑÔÓÑ ÍÏÄÕÌÉ ÓÅÒ×ÅÒÁ +ModulesPath = /usr/lib/stg + +# ïÐÒÅÄÅÌÑÅÔ ÄÉÒÅËÔÏÒÉÀ, × ËÏÔÏÒÏÊ ÂÕÄÕÔ ÎÁÈÏÄÉÔÓÑ ÆÁÊÌÙ "ÍÏÎÉÔÏÒÁ" +# ÒÁÂÏÔÙ ÓÅÒ×ÅÒÁ. ÷ ÜÔÏÊ ÄÉÒÅËÔÏÒÉÉ ÂÕÄÕÔ ÓÏÚÄÁÎÙ ÐÕÓÔÙÅ ÆÁÊÌÙ, ×ÒÅÍÑ +# ÍÏÄÉÆÉËÁÃÉÉ ËÏÔÏÒÙÈ ÂÕÄÅÔ ÍÅÎÑÔØÓÑ ÐÒÉÍÅÒÎÏ ÒÁÚ × ÍÉÎÕÔÕ. åÓÌÉ ËÁËÏÊ-ÔÏ +# ËÏÍÐÏÎÅÎÔ ÓÅÒ×ÅÒÁ ÚÁ×ÉÓÎÅÔ, ÆÁÊÌ(Ù) ÐÅÒÅÓÔÁÎÅÔ ÏÂÎÏ×ÌÑÔÓÑ, É ÐÏ ÜÔÏÍÕ +# ÐÒÉÚÎÁËÕ ÍÏÖÎÏ ÏÐÒÅÄÅÌÉÔØ ÓÂÏÊ × ÒÁÂÏÔÅ ÓÅÒ×ÅÒÁ É ÐÒÉ ÎÁÄÏÂÎÏÓÔÉ +# ÐÅÒÅÚÁÐÕÓÔÉÔØ. åÓÌÉ ÐÁÒÁÍÅÔÒ ÎÅ ÕËÁÚÁÎ ÉÌÉ ÐÕÓÔÏÊ, ÍÏÎÉÔÏÒÉÎÇ ÐÒÏÉÚ×ÏÄÉÔÓÑ +# ÎÅ ÂÕÄÅÔ. ðÁÒÁÍÅÔÒ ÎÅ Ñ×ÌÑÅÔÓÑ ÏÂÑÚÁÔÅÌØÎÙÍ, ÐÏ ÕÍÏÌÞÁÎÉÀ ÐÕÓÔÏÊ. +# MonitorDir=/var/stargazer/monitor + +################################################################################ +# Store module +# îÁÓÔÒÏÊËÉ ÐÌÁÇÉÎÁ ÒÁÂÏÔÁÀÝÅÇÏ Ó âä ÓÅÒ×ÅÒÁ + +# ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ +# ô.Å. ÐÏÌÎÏÅ ÉÍÑ ÍÏÄÕÌÑ mod_store_files.so + + + # òÁÂÏÞÁÑ ÄÉÒÅËÔÏÒÉÑ ÓÅÒ×ÅÒÁ, ÔÕÔ ÓÏÄÅÒÖÁÔÓÑ ÄÁÎÎÙÅ Ï ÔÁÒÉÆÁÈ, ÐÏÌØÚÏ×ÁÔÅÌÑÈ, + # ÁÄÍÉÎÉÓÔÒÁÔÏÒÁÈ É Ô.Ä. + WorkDir = /var/stargazer + + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÆÁÊÌÙ ÓÔÁÔÉÓÔÉËÉ (stat) ÐÏÌØÚÏ×ÁÔÅÌÑ + ConfOwner = root + ConfGroup = wheel + ConfMode = 600 + + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÆÁÊÌÙ ËÏÎÆÉÇÕÒÁÃÉÉ (conf) ÐÏÌØÚÏ×ÁÔÅÌÑ + StatOwner = root + StatGroup = wheel + StatMode = 640 + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÌÏÇ-ÆÁÊÌÙ (log) ÐÏÌØÚÏ×ÁÔÅÌÑ + UserLogOwner = root + UserLogGroup = wheel + UserLogMode = 640 + + # õÄÁÌÑÔØ ÒÅÚÅÒ×ÎÙÅ ËÏÐÉÉ ÐÏÓÌÅ ÕÓÐÅÛÎÏÊ ÚÁÐÉÓÉ conf/stat + # úÎÁÞÅÎÉÑ: yes, no + # ðÏ ÕÍÏÌÞÁÎÉÀ: yes + # RemoveBak = yes + + # ÷ÏÓÓÔÁÎÁ×ÌÉ×ÁÔØ ÆÁÊÌÙ conf/stat ÉÚ ÒÅÚÅÒ×ÎÙÈ ËÏÐÉÊ ÐÒÉ ÏÛÉÂËÅ ÞÔÅÎÉÑ + # úÎÁÞÅÎÉÑ: yes, no + # ðÏ ÕÍÏÌÞÁÎÉÀ: no + # ReadBak = no + + + +# +# # áÄÒÅÓ ÓÅÒ×ÅÒÁ âä +# server=localhost +# +# # ðÕÔØ Ë âä ÎÁ ÓÅÒ×ÅÒÅ ÉÌÉ ÅÅ ÁÌÉÁÓ +# database=/var/stg/stargazer.fdb +# +# # éÍÑ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# user=stg +# +# # ðÁÒÏÌØ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# password=123456 +# +# # õÒÏ×ÅÎØ ÉÚÏÌÑÃÉÉ ÔÒÁÎÚÁÃÉÊ (ÎÅ ÏÂÑÚÁÔÅÌØÎÏ, ÐÏ ÕÍÏÌÞÁÎÉÀ oncurrency): +# # concurrency +# # dirtyRead +# # readCommitted +# # consistency +# isolationLevel=concurrency +# +# # äÅÊÓÔ×ÉÑ ÐÒÉ ÂÌÏËÉÒÏ×ËÁÈ (ÎÅ ÏÂÑÚÁÔÅÌØÎÏ, ÐÏ ÕÍÏÌÞÁÎÉÀ wait): +# # wait +# # noWait +# lockResolution=wait +# + +# +# # áÄÒÅÓ ÓÅÒ×ÅÒÁ âä +# server=localhost +# +# # éÍÑ âä +# database=stargazer +# +# # éÍÑ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# user=stg +# +# # ðÁÒÏÌØ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# password=123456 +# + +# +# # éÍÑ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# dbuser = stg +# +# # ðÁÒÏÌØ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# rootdbpass = 123456 +# +# # éÍÑ âä ÎÁ ÓÅÒ×ÅÒÅ +# dbname = stg +# +# # áÄÒÅÓ ÓÅÒ×ÅÒÁ âä +# dbhost = localhost +# + +################################################################################ +# ðÒÏÞÉÅ ÍÏÄÕÌÉ + + + + # îÁÓÔÒÏÊËÉ ÐÌÁÇÉÎÁ Á×ÔÏÒÉÚÁÃÉÉ Always Online "mod_auth_ao.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + # ô.Å. ÐÏÌÎÏÅ ÉÍÑ ÍÏÄÕÌÑ mod_auth_ao.so + + + + + + # îÁÓÔÒÏÊËÉ ÐÌÁÇÉÎÁ Á×ÔÏÒÉÚÁÃÉÉ InetAccess "mod_auth_ia.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + # ô.Å. ÐÏÌÎÏÅ ÉÍÑ ÍÏÄÕÌÑ mod_auth_ia.so + + + # ðÏÒÔ ÎÁ ËÏÔÏÒÏÍ ÐÒÉÎÉÍÁÀÔÓÑ ÏÂÒÁÝÅÎÉÑ ÏÔ Á×ÔÏÒÉÚÁÔÏÒÁ + # úÎÁÞÅÎÉÑ: 1...65534 + Port = 5555 + + + # ÷ÒÅÍÑ ÍÅÖÄÕ ÐÏÓÙÌËÁÍÉ ÚÁÐÒÏÓÁ ÐÏÌØÚÏ×ÁÔÅÌÀ ÖÉ× ÌÉ ÏÎ + # É ÏÂÎÏ×ÌÅÎÉÅÍ ÄÁÎÎÙÈ ÓÔÁÔÉÓÔÉËÉ (ÓÅËÕÎÄÙ) + # úÎÁÞÅÎÉÑ: 5...600 + UserDelay = 15 + + + #ôÁÊÍÁÕÔ ÄÌÑ ÐÏÌØÚÏ×ÁÔÅÌÑ. åÓÌÉ × ÔÅÞÅÎÉÅ ÜÔÏÇÏ ×ÒÅÍÅÎÉ Á×ÔÏÒÉÚÁÔÏÒ + #ÎÅ ÏÔ×ÅÞÁÅÔ, ÐÏÌØÚÏ×ÁÔÅÌØ ÂÕÄÅÔ ÏÔËÌÀÞÅÎ + # úÎÁÞÅÎÉÑ: 15...1200 + UserTimeout = 65 + + + # üÔÏÔ ÐÁÒÁÍÅÔÒ ÏÐÒÅÄÅÌÑÅÔ ÞÔÏ ÂÕÄÅÔ ÐÅÒÅÄÁ×ÁÔØÓÑ ÐÒÏÇÒÁÍÍÅ InetAccess ÏÔ ÓÅÒ×ÅÒÁ + # ËÁË ÏÔÓÔÁÔÏË ÐÒÅÄÏÐÌÁÞÅÎÎÏÇÏ ÔÒÁÆÉËÁ + # úÎÁÞÅÎÉÑ: + # FreeMb = 0 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÎÕÌÅ×ÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = 1 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÐÅÒ×ÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = 2 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ×ÔÏÒÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = 3 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÔÒÅÔØÅÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # ........................ + # FreeMb = 9 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÄÅ×ÑÔÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = cash - ËÏÌ-×Ï ÄÅÎÅÇ ÎÁ ËÏÔÏÒÙÅ ÀÚÅÒ ÍÏÖÅÔ ÂÅÓÐÌÁÔÎÏ ËÁÞÁÔØ + # FreeMb = none - ÎÉÞÅÇÏ ÎÅ ÐÅÒÅÄÁ×ÁÔØ + FreeMb = cash + + + + + + # íÏÄÕÌÉ ÍÏÖÎÏ ÉÓÐÏÌØÚÏ×ÁÔØ ÎÅÓËÏÌØËÏ ÒÁÚ Ó ÒÁÚÎÙÍÉ ÐÁÒÁÍÅÔÒÁÍÉ + # + # Port = 7777 + # UserDelay = 15 + # UserTimeout = 65 + # FreeMb = 0 + # + + + + # îÁÓÔÒÏÊËÉ ÍÏÄÕÌÑ ËÏÎÆÉÇÕÒÁÃÉÉ SgConfig "mod_conf_sg.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + + + # ðÏÒÔ ÐÏ ËÏÔÏÒÏÍÕ ÓÅÒ×ÅÒ ×ÚÁÉÍÏÄÅÊÓÔ×ÕÅÔ Ó ËÏÎÆÉÇÕÒÁÔÏÒÏÍ + # úÎÁÞÅÎÉÑ: 1...65535 + Port = 5555 + + + + + + # íÏÄÕÌØ ÚÁÈ×ÁÔÁ ÔÒÁÆÉËÁ "mod_cap_bpf.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + # âÅÚ ÐÁÒÁÍÅÔÒÏ×. ôÏÌØËÏ ÉÍÑ ÍÏÄÕÌÑ. + + # éÎÔÅÒÆÅÊÓ(Ù) ÎÁ ËÏÔÏÒÏÍ ÎÕÖÎÏ ÐÒÏÉÚ×ÏÄÉÔØ ÐÏÄÓÞÅÔ ÔÒÁÆÉËÁ + iface = rl0 + iface = rl1 + iface = dc0 + + + # íÏÄÕÌØ ÚÁÈ×ÁÔÁ ÔÒÁÆÉËÁ "mod_cap_nf.so" + # ðÒÉÎÉÍÁÅÔ ÉÎÆÏÒÍÁÃÉÀ Ï ÔÒÁÆÉËÅ ÐÏ ÐÒÏÔÏËÏÌÕ NetFlow + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÅÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + + # TCPPort - ÐÏÒÔ ÄÌÑ TCP-ÓÏÅÄÉÎÅÎÉÊ + TCPPort = 42111 + + # UDPPort - ÐÏÒÔ ÄÌÑ UDP-ÓÏÅÄÉÎÅÎÉÊ + UDPPort = 42111 + + # íÏÇÕÔ ÉÍÅÔØ ÓÏ×ÐÁÄÁÀÝÉÅ ÚÎÁÞÅÎÉÑ. + # åÓÌÉ ÐÁÒÁÍÅÔÒ ÎÅ ÕËÁÚÁÎ - ÓÏÏÔ×ÅÔÓÔ×ÕÀÝÉÊ ÐÏÒÔ ÎÅ "ÐÒÏÓÌÕÛÉ×ÁÅÔÓÑ". + + + + + # îÁÓÔÒÏÊËÉ ÍÏÄÕÌÑ ÐÉÎÇÕÀÝÅÇÏ ÐÏÌØÚÏ×ÁÔÅÌÅÊ "mod_ping.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + + + # ÷ÒÅÍÑ, × ÓÅËÕÎÄÁÈ, ÍÅÖÄÕ ÐÉÎÇÁÍÉ ÏÄÎÏÇÏ É ÔÏÇÏ ÖÅ ÐÏÌØÚÏ×ÁÔÅÌÑ + # úÎÁÞÅÎÉÑ: 10...3600 + PingDelay = 15 + + + +# # îÁÓÔÒÏÊËÉ ÍÏÄÕÌÑ ÄÌÑ ÕÄÁÌÅÎÎÏÇÏ ×ÙÐÏÌÎÅÎÉÑ ÓËÒÉÐÔÏ× OnCOnnect É +# # OnDisconnect "mod_remote_script.so" +# # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ +# +# +# # ÷ÒÅÍÑ, × ÓÅËÕÎÄÁÈ, ÍÅÖÄÕ ÐÏÓÙÌËÁÍÉ ÐÏÄÔ×ÅÒÖÄÅÎÉÊ, ÔÏÇÏ, ÞÔÏ ÐÏÌØÚÏ×ÁÔÅÌØ +# # ×Ó£ ÅÝÅ ÏÎÌÁÊÎ +# # úÎÁÞÅÎÉÑ: 10...600 +# SendPeriod = 15 +# +# # óÏÏÔ×ÅÔÓÔ×ÉÅ ÐÏÄÓÅÔÅÊ, × ËÏÔÏÒÏÊ ÎÁÈÏÄÉÔÓÑ ÐÏÌØÚÏ×ÁÔÅÌØ É +# # ÓÏÏÔ×ÅÔÓÔ×ÕÀÝÅÇÏ ÒÏÕÔÅÒÁ. ðÅÒ×ÁÑ ÞÁÓÔØ ÓÔÒÏËÉ - ÐÏÄÓÌÅÔØ, ÚÁÄÁÎÎÁÑ +# # ËÁË IP-ÁÄÒÅÓ É ÍÁÓËÁ, ÞÅÒÅÚ ÐÒÏÂÅÌ - IP-ÁÄÒÅÓ ÒÏÕÔÅÒÁ ÎÁ ËÏÔÏÒÏÍ +# # ÄÏÌÖÎÙ ×ÙÐÏÌÎÑÔØÓÑ ÓËÒÉÐÔÙ +# # îÁÐÒÉÍÅÒ ÜÔÁ ÚÁÐÉÓØ "192.168.1.0/24 192.168.1.1" ÏÚÎÁÞÁÅÔ, ÞÔÏ ÄÌÑ +# # ×ÓÅÈ ÐÏÌØÚÏ×ÁÔÅÌÅÊ ÉÚ ÐÏÄÓÅÔÉ 192.168.1.0/24, ÓËÒÉÐÔÙ ÂÕÄÕÔ +# # ×ÙÐÏÌÎÑÔØÓÑ ÎÁ ÒÏÕÔÅÒÅ Ó ÁÄÒÅÓÏÍ 192.168.1.1 +# # Subnet0...Subnet100 +# Subnet0 = 192.168.1.0/24 192.168.1.7 +# Subnet1 = 192.168.2.0/24 192.168.2.5 +# Subnet2 = 192.168.3.0/24 192.168.2.5 +# Subnet3 = 192.168.4.0/24 192.168.2.5 +# +# # ðÁÒÏÌØ ÄÌÑ ÛÉÆÒÏ×ÁÎÉÑ ÐÁËÅÔÏ× ÍÅÖÄÕ stg-ÓÅÒ×ÅÒÏÍ É ÓÅÒ×ÅÒÏÍ, +# # ×ÙÐÏÌÎÑÀÝÉÍ ÓËÒÉÐÔÙ +# Password = 123456 +# +# # üÔÏÔ ÐÁÒÁÍÅÔÒ ÏÐÒÅÄÅÌÑÅÔ ËÁËÉÅ ÐÁÒÁÍÅÔÒÙ ÐÏÌØÚÏ×ÁÔÅÌÑ ÐÅÒÅÄÁÀÔÓÑ +# # ÎÁ ÕÄÁÌÅÎÎÙÊ ÓÅÒ×ÅÒ +# # Cash, FreeMb, Passive, Disabled, AlwaysOnline, TariffName, NextTariff, Address, +# # Note, Group, Email, RealName, Credit, EnabledDirs, Userdata0...Userdata9 +# UserParams=Cash Tariff EnabledDirs +# +# # ðÏÒÔ ÐÏ ËÏÔÏÒÏÍÕ ÓÅÒ×ÅÒ ÏÔÓÙÌÁÅÔ ÓÏÏÂÝÅÎÉÑ ÎÁ ÒÏÕÔÅÒ +# # úÎÁÞÅÎÉÑ: 1...65535 +# Port = 9999 +# +# + +# +# Password = 123456 +# ServerIP = 127.0.0.1 +# Port = 6666 +# AuthServices = Login-User +# AcctServices = Framed-User +# + + +################################################################################ + diff --git a/projects/stargazer/inst/linux/etc/init.d/stargazer.gentoo.2007 b/projects/stargazer/inst/linux/etc/init.d/stargazer.gentoo.2007 new file mode 100755 index 00000000..daef60b5 --- /dev/null +++ b/projects/stargazer/inst/linux/etc/init.d/stargazer.gentoo.2007 @@ -0,0 +1,31 @@ +#!/sbin/runscript + +opts="reload" + +DAEMON=/usr/sbin/stargazer +STARGAZER_OPTS="" +PIDFILE=/var/run/stargazer.pid + +depend() { + need net +} + +start() { + ebegin "Starting stargazer" + start-stop-daemon --start --quiet --exec ${DAEMON} -- ${STARGAZER_OPTS} + eend $? +} + +stop() { + ebegin "Stopping stargazer" + start-stop-daemon --stop --quiet --pidfile ${PIDFILE} --retry=INT/60/KILL/5 + rm -f ${PIDFILE} + eend $? +} + +reload() { + ebegin "Reloading stargazer rules" + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE + return 0 + eend $? +} diff --git a/projects/stargazer/inst/linux/etc/init.d/stargazer.suse.9.3 b/projects/stargazer/inst/linux/etc/init.d/stargazer.suse.9.3 new file mode 100755 index 00000000..49a50da4 --- /dev/null +++ b/projects/stargazer/inst/linux/etc/init.d/stargazer.suse.9.3 @@ -0,0 +1,70 @@ +#!/bin/bash +# +# processname: stargazer +# config: /etc/stargazer/stargazer.conf +# pidfile: /var/run/stargazer.pid + +# Source function library. +. /etc/init.d/functions + +# Source networking configuration. +. /etc/sysconfig/network + +# Source stargazer configureation. +DAEMON=yes +QUEUE=1h + +# Check that networking is up. +[ ${NETWORKING} = "no" ] && exit 0 + +[ -f /sbin/stargazer ] || exit 0 + +RETVAL=0 +prog="stargazer" + +start() { + # Start daemons. + + echo -n $"Starting $prog: " + /etc/stargazer/first 2> /dev/null + daemon /sbin/stargazer + RETVAL=$? + /etc/stargazer/last 2> /dev/null + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/stargazer + return $RETVAL +} + +stop() { + # Stop daemons. + echo -n $"Shutting down $prog: " + killproc stargazer + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/stargazer + return $RETVAL +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart|reload) + stop + start + RETVAL=$? + ;; + status) + status stargazer + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/projects/stargazer/inst/linux/etc/init.d/stargazer.ubuntu.7.10 b/projects/stargazer/inst/linux/etc/init.d/stargazer.ubuntu.7.10 new file mode 100755 index 00000000..a5ed971f --- /dev/null +++ b/projects/stargazer/inst/linux/etc/init.d/stargazer.ubuntu.7.10 @@ -0,0 +1,152 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: stargazer +# Required-Start: $local_fs $remote_fs +# Required-Stop: $local_fs $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: S 0 1 6 +# Short-Description: Stargazer initscript +# Description: This file should be used to start and stop stargazer daemon +### END INIT INFO + +# Author: Boris Mikhailenko + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/usr/sbin:/usr/bin:/sbin:/bin +DESC="Billing system" +NAME=stargazer +DAEMON=/usr/sbin/$NAME +DAEMON_ARGS="" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +[ -f /etc/default/rcS ] && . /etc/default/rcS + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + + # ps x | grep $DAEMON | grep -v grep | cut -f1 -d" " > $PIDFILE + + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + + # ps x | grep $DAEMON | grep -v grep | cut -f1 -d" " > $PIDFILE + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=INT/60/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/60/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + reload) + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + log_daemon_msg "Reloading $DESC" "$NAME" + do_reload + log_end_msg $? + ;; + restart) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|restart|reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/projects/stargazer/inst/linux/etc/stargazer/OnChange b/projects/stargazer/inst/linux/etc/stargazer/OnChange new file mode 100755 index 00000000..a9272287 --- /dev/null +++ b/projects/stargazer/inst/linux/etc/stargazer/OnChange @@ -0,0 +1,6 @@ +login=$1 +param=$2 +oldValue=$3 +newValue=$4 + +#echo "User: '$login'. Parameter $param changed from '$oldValue' to '$newValue'" >> /var/stargazer/users.chg.log \ No newline at end of file diff --git a/projects/stargazer/inst/linux/etc/stargazer/OnConnect b/projects/stargazer/inst/linux/etc/stargazer/OnConnect new file mode 100755 index 00000000..f3899d63 --- /dev/null +++ b/projects/stargazer/inst/linux/etc/stargazer/OnConnect @@ -0,0 +1,22 @@ +#Этот скрипт вызывается в момент, когда пользователь +#успешно прошел авторизацию на сервере. Задача скрипта - перестроить +#файрвол так, что бы пользователь получил доступ в интернет + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to connect +DIRS=$5 + + +#echo "C `date +%Y.%m.%d-%H.%M.%S` $IP $CASH" >> /var/stargazer/users/$LOGIN/connect.log + diff --git a/projects/stargazer/inst/linux/etc/stargazer/OnDisconnect b/projects/stargazer/inst/linux/etc/stargazer/OnDisconnect new file mode 100755 index 00000000..c29a011e --- /dev/null +++ b/projects/stargazer/inst/linux/etc/stargazer/OnDisconnect @@ -0,0 +1,27 @@ +# Этот скрипт вызывается в момент, когда пользователь +# желает отключится от интернета или вышел таймаут у пользователя +# и сервер сам отключает пользователя +# Задача скрипта подобна задаче скрипта OnConnect - перестроить +# файрвол так, что бы пользователю закрыть доступ в интернет + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to disconnect +DIRS=$4 + +#echo "D `date +%Y.%m.%d-%H.%M.%S` $IP $CASH" >> /var/stargazer/users/$LOGIN/connect.log + + + + + diff --git a/projects/stargazer/inst/linux/etc/stargazer/OnUserAdd b/projects/stargazer/inst/linux/etc/stargazer/OnUserAdd new file mode 100755 index 00000000..a3ee3a9f --- /dev/null +++ b/projects/stargazer/inst/linux/etc/stargazer/OnUserAdd @@ -0,0 +1,12 @@ +# Использование (неиспользование) этого скрипта дело вкуса. +# Он не выполняет критических функций. Его задача автматизировать +# действия характерные при добавлении пользователя сети, например добавлекние +# пользователю почты + +# Login +login=$1 + +#echo "added user $login" >> /var/stargazer/add_del.log + + + diff --git a/projects/stargazer/inst/linux/etc/stargazer/OnUserDel b/projects/stargazer/inst/linux/etc/stargazer/OnUserDel new file mode 100755 index 00000000..3be6046e --- /dev/null +++ b/projects/stargazer/inst/linux/etc/stargazer/OnUserDel @@ -0,0 +1,5 @@ +# Login +login=$1 + +#echo "deleted user $login" >> /var/stargazer/add_del.log + diff --git a/projects/stargazer/inst/linux/etc/stargazer/rules b/projects/stargazer/inst/linux/etc/stargazer/rules new file mode 100644 index 00000000..3fb48284 --- /dev/null +++ b/projects/stargazer/inst/linux/etc/stargazer/rules @@ -0,0 +1,3 @@ +ALL 192.168.0.0/16 DIR1 +ALL 10.0.0.0/8 DIR2 +ALL 0.0.0.0/0 DIR0 \ No newline at end of file diff --git a/projects/stargazer/inst/linux/etc/stargazer/stargazer.conf b/projects/stargazer/inst/linux/etc/stargazer/stargazer.conf new file mode 100644 index 00000000..52e70e91 --- /dev/null +++ b/projects/stargazer/inst/linux/etc/stargazer/stargazer.conf @@ -0,0 +1,394 @@ +################################################################################ +# æÁÊÌ ÎÁÓÔÒÏÅË ÓÅÒ×ÅÒÁ stargazer # +################################################################################ + + + +# éÍÑ ÌÏÇ-ÆÁÊÌÁ ËÕÄÁ ÐÉÛÕÔÓÑ ÓÏÂÙÔÉÑ +LogFile = /var/log/stargazer.log + + + +# éÍÑ PID-ÆÁÊÌÁ ËÕÄÁ ÐÉÛÅÔÓÑ ÉÄÅÎÔÉÆÉËÁÔÏÒ ÐÒÏÃÅÓÓÁ +# ðÏ ÕÍÏÌÞÁÎÉÀ /var/run/pid +# PIDFile = /var/run/stargazer.pid + + + +# éÍÑ ÆÁÊÌÁ × ËÏÔÏÒÏÍ ÏÐÒÅÄÅÌÑÀÔÓÑ ÐÒÁ×ÉÌÁ ÐÏÄÓÞÅÔÁ ÔÒÁÆÉËÁ +Rules = /etc/stargazer/rules + + + +# ÷ÒÅÍÑ ÞÅÒÅÚ ËÏÔÏÒÏÅ ÐÉÛÅÔÓÑ d âä ÄÅÔÁÌØÎÁÑ ÓÔÁÔÉÓÔÉËÁ ÐÏÌØÚÏ×ÁÔÅÌÑ +# úÎÁÞÅÎÉÑ: 1, 1/2, 1/4, 1/6. +# 1 - ÒÁÚ × ÞÁc, 1/2 - ÒÁÚ × ÐÏÌ ÞÁÓÁ, 1/4 - ÒÁÚ × 15 ÍÉÎ, 1/6 - ÒÁÚ × 10 ÍÉÎ +DetailStatWritePeriod=1/6 + + + +# ðÅÒÉÏÄÉÞÎÏÓÔØ ÚÁÐÉÓÉ ÚÁÐÉÓÉ × âä ÉÎÆÏÒÍÁÃÉÉ Ï ÓÔÁÔÉÓÔÉËÅ ÐÏÌØÚÏ×ÁÔÅÌÑ (ÍÉÎÕÔÙ) +# ðÒÉ ÂÏÌØÛÏÍ ËÏÌ-×Å ÐÏÌØÚÏ×ÁÔÅÌÅÊ ÜÔÕ ×ÅÌÉÞÉÎÕ ÓÔÏÉÔ Õ×ÅÌÉÞÉÔØ, Ô.Ë. +# ÚÁÐÉÓØ × âä ÍÏÖÅÔ ÚÁÎÉÍÁÔØ ÄÌÉÔÅÌØÎÏÅ ×ÒÅÍÑ. +# úÎÁÞÅÎÉÑ: 1...1440 (ÍÉÎÕÔÙ) +StatWritePeriod = 10 + + + +# äÅÎØ ÓÎÑÔÉÑ ÁÂÏÎÐÌÁÔÙ +# úÎÁÞÅÎÉÑ: 0...31. 0 - ðÏÓÌÅÄÎÉÊ ÄÅÎØ ÍÅÓÑÃÁ +DayFee = 1 + + + +# áÂÏÎÐÌÁÔÁ ÓÎÉÍÁÅÔÓÑ × ÐÏÓÌÅÄÎÉÊ (yes) ÉÌÉ ÐÅÒ×ÙÊ (no) ÄÅÎØ ÕÞÅÔÎÏÇÏ ÐÅÒÉÏÄÁ. +# üÔÏ ×ÌÉÑÅÔ ÎÁ ÔÏ, ËÁË ÂÕÄÅÔ ÓÎÑÔÁ ÁÂÏÎÐÌÁÔÁ (áð) ÐÒÉ ÐÅÒÅÈÏÄÅ ÎÁ ÎÏ×ÙÊ ÔÁÒÉÆ. +# åÓÌÉ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÂÙÌ ÔÁÒÉÆ A Ó áð=100 É ÏÎ ÈÏÞÅÔ ÐÅÒÅÊÔÉ ÎÁ ÔÁÒÉÆ B Ó áð=200, +# ÔÏ ÐÒÉ ÐÅÒÅÈÏÄÅ ÎÁ ÎÏ×ÙÊ ÔÁÒÉÆ ÓÏ ÓÞÅÔÁ ÐÏÌØÚÏ×ÁÔÅÌÑ ÓÎÉÍÅÔÓÑ 100, ÅÓÌÉ +# DayFeeIsLastDay = yes É 200, ÅÓÌÉ DayFeeIsLastDay = no +DayFeeIsLastDay = yes + + + +# äÅÎØ ÓÂÒÏÓÁ ÄÁÎÎÙÈ Ï ÔÒÁÆÉËÅ ÚÁ ÍÅÓÑÃ É ÄÅÎØ ÐÅÒÅÈÏÄÁ ÐÏÌØÚÏ×ÁÔÅÌÅÊ ÎÁ ÎÏ×ÙÅ ÔÁÒÉÆÙ +# úÎÁÞÅÎÉÑ: 0...31. 0 - ðÏÓÌÅÄÎÉÊ ÄÅÎØ ÍÅÓÑÃÁ +DayResetTraff = 1 + + + +# "òÁÚÍÁÚÁÎÎÏÅ" ÓÎÑÔÉÅ ÁÂÏÎÐÌÁÔÙ. óÎÑÔÉÅ áð ÎÅ ÒÁÚ × ÍÅÓÑÃ, Á ËÁÖÄÙÊ +# ÄÅÎØ 1/30 ÉÌÉ 1/31 ÞÁÓÔÉ áð +# úÎÁÞÅÎÉÑ: yes, no +SpreadFee = no + + + +# äÁÎÎÁÑ ÏÐÃÉÑ ÏÐÒÅÄÅÌÑÅÔ ÍÏÖÅÔ ÌÉ ÐÏÌØÚÏ×ÁÔÅÌØ ÐÏÌÕÞÉÔØ ÄÏÓÔÕÐ × ÉÎÔÅÒÅÎÔ +# ÅÓÌÉ Õ ÎÅÇÏ ÎÁ ÓÞÅÔÕ ÎÅÔ ÄÅÎÅÇ, ÎÏ ÏÓÔÁÌÓÑ ÐÒÅÄÏÐÌÁÞÅÎÎÙÊ ÔÒÁÆÉË +# úÎÁÞÅÎÉÑ: yes, no +FreeMbAllowInet = no + + + +# üÔÁ ÏÐÃÉÑ ÏÐÒÅÄÅÌÑÅÔ ÞÔÏ ÂÕÄÅÔ ÐÉÓÁÔØÓÑ × ÓÔÏÉÍÏÓÔØ ÔÒÁÆÉËÁ × detail_stat. +# åÓÌÉ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÅÝÅ ÅÓÔØ ÐÒÅÄÏÐÌÁÞÅÎÎÙÊ ÔÒÁÆÉË É WriteFreeMbTraffCost = no, +# ÔÏ × detail_stat ÓÔÏÉÍÏÓÔØ ÂÕÄÅÔ 0. åÓÌÉ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÕÖÅ ÎÅÔ +# ÐÒÅÄÏÐÌÁÞÅÎÎÏÇÏ ÔÒÁÆÉËÁ É WriteFreeMbTraffCost = no, ÔÏ × detail_stat +# ÂÕÄÅÔ ÚÁÐÉÓÁÎÁ ÓÔÏÉÏÓÔØ ÔÒÁÆÉËÁ. ðÒÉ WriteFreeMbTraffCost = yes ÓÔÏÉÍÏÓÔØ +# ÔÒÁÆÉËÁ ÂÕÄÅÔ ÚÁÐÉÓÁÎÁ × ÌÀÂÏÍ ÓÌÕÞÁÅ. +WriteFreeMbTraffCost = no + + + +# îÅÏÂÑÚÁÔÅÌØÎÙÊ ÐÁÒÁÍÅÔÒ. õËÁÚÙ×ÁÅÔ ÓÎÉÍÁÔØ ÐÏÌÎÕÀ ÁÂÏÎÐÌÁÔÕ Õ ÐÏÌØÚÏ×ÁÔÅÌÑ ÄÁÖÅ +# ÅÓÌÉ ÏÎ ÂÙÚ ÚÁÍÏÒÏÖÅÎ ÔÏÌØËÏ ÞÁÓÔØ ÕÞÅÔÎÏÇÏ ÐÅÒÉÏÄÁ. +# ðÏ ÕÍÏÌÞÁÎÉÀ ÕÓÔÁÎÏ×ÌÅÎ × no +# FullFee=no + +# îÅÏÂÑÚÁÔÅÌØÎÙÊ ÐÁÒÁÍÅÔÒ ÕËÁÚÙ×ÁÀÝÉÊ ÐÏËÁÚÙ×ÁÔØ ÎÁ ÓÞÅÔÕ É ÐÏÚ×ÏÌÑÔØ +# ÉÓÐÏÌØÚÏ×ÁÔØ ÐÏÌØÚÏ×ÁÔÅÌÀ ÁÂÏÎÐÌÁÔÕ. ðÏ ÕÍÏÌÞÁÎÉÀ ÕÓÔÁÎÏ×ÌÅÎ × yes +# ShowFeeInCash=yes + + + +# îÁÚ×ÁÎÉÑ ÎÁÐÒÁ×ÌÅÎÉÊ. îÁÐÒÁ×ÌÅÎÉÑ ÂÅÚ ÎÁÚ×ÁÎÉÊ ÎÅ ÂÕÄÕÔ ÏÔÏÂÒÁÖÁÔØÓÑ × +# Á×ÔÏÒÉÚÁÔÏÒÅ É ËÏÎÆÉÇÕÒÁÔÏÒÅ. îÁÚ×ÁÎÉÑ ÓÏÓÔÏÑÝÉÅ ÉÚ ÎÅÓËÏÌØËÉÈ ÓÌÏ× ÄÏÌÖÎÙ +# ÂÙÔØ ×ÚÑÔÙ × ËÁ×ÙÞËÉ + + DirName0 = ìÏËÁÌØ + DirName1 = çÏÒÏÄ + DirName2 = íÉÒ + DirName3 = + DirName4 = + DirName5 = "ìÏËÁÌØÎÙÅ ÉÇÒÙ" + DirName6 = + DirName7 = + DirName8 = + DirName9 = + + + + +# ëÏÌ-×Ï ÚÁÐÕÓËÁÅÍÙÈ ÐÒÏÃÅÓÓÏ× stg-exec. +# üÔÉ ÐÒÏÃÅÓÓÙ ÏÔ×ÅÞÁÀÔ ÚÁ ×ÙÐÏÌÎÅÎÉÅ ÓËÒÉÐÔÏ× OnConnect, OnDisconnect, ... +# ëÏÌ-×Ï ÐÒÏÃÅÓÓÏ× ÏÚÎÁÞÁÅÔ ÓËÏÌØËÏ ÓËÒÉÐÔÏ× ÍÏÇÕÔ ×ÙÐÏÌÎÑÔÓÑ ÏÄÎÏ×ÒÅÍÅÎÎÏ. +# úÎÁÞÅÎÉÑ: 1...1024 +ExecutersNum = 1 + + + +# Message Key ÄÌÑ stg-exec. +# éÄÅÎÔÉÆÉËÁÔÏÒ ÏÞÅÒÅÄÉ ÓÏÏÂÝÅÎÉÊ ÄÌÑ ×ÙÐÏÌÎÑÔÅÌÑ ÓËÒÉÐÔÏ×. +# åÇÏ ÉÚÍÅÎÅÎÉÅ ÍÏÖÅÔ ÐÏÎÁÄÏÂÉÔÓÑ ÅÓÌÉ ÅÓÔØ ÎÅÏÂÈÏÄÉÍÏÓÔØ ÚÁÐÕÓÔÉÔØ ÎÅÓËÏÌØËÏ +# ÜËÚÅÍÐÌÑÒÏ× stg. åÓÌÉ ×Ù ÎÅ ÐÏÎÉÍÁÅÔÅ, ÞÔÏ ÜÔÏ, ÎÅ ÔÒÏÇÁÊÔÅ ÜÔÏÔ ÐÁÒÁÍÅÔÒ! +# úÎÁÞÅÎÉÑ: 0...2^32 +# úÎÁÞÅÎÉÅ ÐÏ ÕÍÏÌÞÁÎÉÀ: 5555 +# ExecMsgKey = 5555 + + + +# ðÕÔØ Ë ÄÉÒÅËÔÏÒÉÉ, × ËÏÔÏÒÏÊ ÎÁÈÏÄÑÔÓÑ ÍÏÄÕÌÉ ÓÅÒ×ÅÒÁ +ModulesPath = /usr/lib/stg + +# ïÐÒÅÄÅÌÑÅÔ ÄÉÒÅËÔÏÒÉÀ, × ËÏÔÏÒÏÊ ÂÕÄÕÔ ÎÁÈÏÄÉÔÓÑ ÆÁÊÌÙ "ÍÏÎÉÔÏÒÁ" +# ÒÁÂÏÔÙ ÓÅÒ×ÅÒÁ. ÷ ÜÔÏÊ ÄÉÒÅËÔÏÒÉÉ ÂÕÄÕÔ ÓÏÚÄÁÎÙ ÐÕÓÔÙÅ ÆÁÊÌÙ, ×ÒÅÍÑ +# ÍÏÄÉÆÉËÁÃÉÉ ËÏÔÏÒÙÈ ÂÕÄÅÔ ÍÅÎÑÔØÓÑ ÐÒÉÍÅÒÎÏ ÒÁÚ × ÍÉÎÕÔÕ. åÓÌÉ ËÁËÏÊ-ÔÏ +# ËÏÍÐÏÎÅÎÔ ÓÅÒ×ÅÒÁ ÚÁ×ÉÓÎÅÔ, ÆÁÊÌ(Ù) ÐÅÒÅÓÔÁÎÅÔ ÏÂÎÏ×ÌÑÔÓÑ, É ÐÏ ÜÔÏÍÕ +# ÐÒÉÚÎÁËÕ ÍÏÖÎÏ ÏÐÒÅÄÅÌÉÔØ ÓÂÏÊ × ÒÁÂÏÔÅ ÓÅÒ×ÅÒÁ É ÐÒÉ ÎÁÄÏÂÎÏÓÔÉ +# ÐÅÒÅÚÁÐÕÓÔÉÔØ. åÓÌÉ ÐÁÒÁÍÅÔÒ ÎÅ ÕËÁÚÁÎ ÉÌÉ ÐÕÓÔÏÊ, ÍÏÎÉÔÏÒÉÎÇ ÐÒÏÉÚ×ÏÄÉÔÓÑ +# ÎÅ ÂÕÄÅÔ. ðÁÒÁÍÅÔÒ ÎÅ Ñ×ÌÑÅÔÓÑ ÏÂÑÚÁÔÅÌØÎÙÍ, ÐÏ ÕÍÏÌÞÁÎÉÀ ÐÕÓÔÏÊ. +# MonitorDir=/var/stargazer/monitor + + +################################################################################ +# Store module +# îÁÓÔÒÏÊËÉ ÐÌÁÇÉÎÁ ÒÁÂÏÔÁÀÝÅÇÏ Ó âä ÓÅÒ×ÅÒÁ + +# ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ +# ô.Å. ÐÏÌÎÏÅ ÉÍÑ ÍÏÄÕÌÑ mod_store_files.so + + + # òÁÂÏÞÁÑ ÄÉÒÅËÔÏÒÉÑ ÓÅÒ×ÅÒÁ, ÔÕÔ ÓÏÄÅÒÖÁÔÓÑ ÄÁÎÎÙÅ Ï ÔÁÒÉÆÁÈ, ÐÏÌØÚÏ×ÁÔÅÌÑÈ, + # ÁÄÍÉÎÉÓÔÒÁÔÏÒÁÈ É Ô.Ä. + WorkDir = /var/stargazer + + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÆÁÊÌÙ ÓÔÁÔÉÓÔÉËÉ (stat) ÐÏÌØÚÏ×ÁÔÅÌÑ + ConfOwner = root + ConfGroup = root + ConfMode = 600 + + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÆÁÊÌÙ ËÏÎÆÉÇÕÒÁÃÉÉ (conf) ÐÏÌØÚÏ×ÁÔÅÌÑ + StatOwner = root + StatGroup = root + StatMode = 640 + + # ÷ÌÁÄÅÌÅÃ, ÇÒÕÐÐÁ É ÐÒÁ×Á ÄÏÓÔÕÐÁ ÎÁ ÌÏÇ-ÆÁÊÌÙ (log) ÐÏÌØÚÏ×ÁÔÅÌÑ + UserLogOwner = root + UserLogGroup = root + UserLogMode = 640 + + # õÄÁÌÑÔØ ÒÅÚÅÒ×ÎÙÅ ËÏÐÉÉ ÐÏÓÌÅ ÕÓÐÅÛÎÏÊ ÚÁÐÉÓÉ conf/stat + # úÎÁÞÅÎÉÑ: yes, no + # ðÏ ÕÍÏÌÞÁÎÉÀ: yes + # RemoveBak = yes + + # ÷ÏÓÓÔÁÎÁ×ÌÉ×ÁÔØ ÆÁÊÌÙ conf/stat ÉÚ ÒÅÚÅÒ×ÎÙÈ ËÏÐÉÊ ÐÒÉ ÏÛÉÂËÅ ÞÔÅÎÉÑ + # úÎÁÞÅÎÉÑ: yes, no + # ðÏ ÕÍÏÌÞÁÎÉÀ: no + # ReadBak = no + + + +# +# # áÄÒÅÓ ÓÅÒ×ÅÒÁ âä +# server=localhost +# +# # ðÕÔØ Ë âä ÎÁ ÓÅÒ×ÅÒÅ ÉÌÉ ÅÅ ÁÌÉÁÓ +# database=/var/stg/stargazer.fdb +# +# # éÍÑ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# user=stg +# +# # ðÁÒÏÌØ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# password=123456 +# +# # õÒÏ×ÅÎØ ÉÚÏÌÑÃÉÉ ÔÒÁÎÚÁÃÉÊ (ÎÅ ÏÂÑÚÁÔÅÌØÎÏ, ÐÏ ÕÍÏÌÞÁÎÉÀ oncurrency): +# # concurrency +# # dirtyRead +# # readCommitted +# # consistency +# isolationLevel=concurrency +# +# # äÅÊÓÔ×ÉÑ ÐÒÉ ÂÌÏËÉÒÏ×ËÁÈ (ÎÅ ÏÂÑÚÁÔÅÌØÎÏ, ÐÏ ÕÍÏÌÞÁÎÉÀ wait): +# # wait +# # noWait +# lockResolution=wait +# + +# +# # áÄÒÅÓ ÓÅÒ×ÅÒÁ âä +# server=localhost +# +# # éÍÑ âä +# database=stargazer +# +# # éÍÑ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# user=stg +# +# # ðÁÒÏÌØ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# password=123456 +# + +# +# # éÍÑ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# dbuser = stg +# +# # ðÁÒÏÌØ ÐÏÌØÚÏ×ÁÔÅÌÑ âä +# rootdbpass = 123456 +# +# # éÍÑ âä ÎÁ ÓÅÒ×ÅÒÅ +# dbname = stg +# +# # áÄÒÅÓ ÓÅÒ×ÅÒÁ âä +# dbhost = localhost +# + +################################################################################ +# ðÒÏÞÉÅ ÍÏÄÕÌÉ + + + + # îÁÓÔÒÏÊËÉ ÐÌÁÇÉÎÁ Á×ÔÏÒÉÚÁÃÉÉ Always Online "mod_auth_ao.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + # ô.Å. ÐÏÌÎÏÅ ÉÍÑ ÍÏÄÕÌÑ mod_auth_ao.so + + + + + + # îÁÓÔÒÏÊËÉ ÐÌÁÇÉÎÁ Á×ÔÏÒÉÚÁÃÉÉ InetAccess "mod_auth_ia.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + # ô.Å. ÐÏÌÎÏÅ ÉÍÑ ÍÏÄÕÌÑ mod_auth_ia.so + + + # ðÏÒÔ ÎÁ ËÏÔÏÒÏÍ ÐÒÉÎÉÍÁÀÔÓÑ ÏÂÒÁÝÅÎÉÑ ÏÔ Á×ÔÏÒÉÚÁÔÏÒÁ + # úÎÁÞÅÎÉÑ: 1...65534 + Port = 5555 + + + # ÷ÒÅÍÑ ÍÅÖÄÕ ÐÏÓÙÌËÁÍÉ ÚÁÐÒÏÓÁ ÐÏÌØÚÏ×ÁÔÅÌÀ ÖÉ× ÌÉ ÏÎ + # É ÏÂÎÏ×ÌÅÎÉÅÍ ÄÁÎÎÙÈ ÓÔÁÔÉÓÔÉËÉ (ÓÅËÕÎÄÙ) + # úÎÁÞÅÎÉÑ: 5...600 + UserDelay = 15 + + + #ôÁÊÍÁÕÔ ÄÌÑ ÐÏÌØÚÏ×ÁÔÅÌÑ. åÓÌÉ × ÔÅÞÅÎÉÅ ÜÔÏÇÏ ×ÒÅÍÅÎÉ Á×ÔÏÒÉÚÁÔÏÒ + #ÎÅ ÏÔ×ÅÞÁÅÔ, ÐÏÌØÚÏ×ÁÔÅÌØ ÂÕÄÅÔ ÏÔËÌÀÞÅÎ + # úÎÁÞÅÎÉÑ: 15...1200 + UserTimeout = 65 + + + # üÔÏÔ ÐÁÒÁÍÅÔÒ ÏÐÒÅÄÅÌÑÅÔ ÞÔÏ ÂÕÄÅÔ ÐÅÒÅÄÁ×ÁÔØÓÑ ÐÒÏÇÒÁÍÍÅ InetAccess ÏÔ ÓÅÒ×ÅÒÁ + # ËÁË ÏÔÓÔÁÔÏË ÐÒÅÄÏÐÌÁÞÅÎÎÏÇÏ ÔÒÁÆÉËÁ + # úÎÁÞÅÎÉÑ: + # FreeMb = 0 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÎÕÌÅ×ÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = 1 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÐÅÒ×ÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = 2 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ×ÔÏÒÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = 3 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÔÒÅÔØÅÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # ........................ + # FreeMb = 9 - ËÏÌ-×Ï ÂÅÓÐÌÁÔÎÙÈ ÍÅÇÁÂÁÊÔ × ÐÒÅÓÞÅÔÅ ÎÁ ÃÅÎÕ ÄÅ×ÑÔÏÇÏ ÎÁÐÒÁ×ÌÅÎÉÑ + # FreeMb = cash - ËÏÌ-×Ï ÄÅÎÅÇ ÎÁ ËÏÔÏÒÙÅ ÀÚÅÒ ÍÏÖÅÔ ÂÅÓÐÌÁÔÎÏ ËÁÞÁÔØ + # FreeMb = none - ÎÉÞÅÇÏ ÎÅ ÐÅÒÅÄÁ×ÁÔØ + FreeMb = cash + + + + + + # íÏÄÕÌÉ ÍÏÖÎÏ ÉÓÐÏÌØÚÏ×ÁÔØ ÎÅÓËÏÌØËÏ ÒÁÚ Ó ÒÁÚÎÙÍÉ ÐÁÒÁÍÅÔÒÁÍÉ + # + # Port = 7777 + # UserDelay = 15 + # UserTimeout = 65 + # FreeMb = 0 + # + + + + # îÁÓÔÒÏÊËÉ ÍÏÄÕÌÑ ËÏÎÆÉÇÕÒÁÃÉÉ SgConfig "mod_conf_sg.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + + + # ðÏÒÔ ÐÏ ËÏÔÏÒÏÍÕ ÓÅÒ×ÅÒ ×ÚÁÉÍÏÄÅÊÓÔ×ÕÅÔ Ó ËÏÎÆÉÇÕÒÁÔÏÒÏÍ + # úÎÁÞÅÎÉÑ: 1...65535 + Port = 5555 + + + + + + # íÏÄÕÌØ ÚÁÈ×ÁÔÁ ÔÒÁÆÉËÁ "mod_cap_ether.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÅÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + # âÅÚ ÐÁÒÁÍÅÔÒÏ×. ôÏÌØËÏ ÉÍÑ ÍÏÄÕÌÑ. + + # íÏÄÕÌØ ÂÅÚ ÐÁÒÁÍÅÔÒÏ× + + + # íÏÄÕÌØ ÚÁÈ×ÁÔÁ ÔÒÁÆÉËÁ "mod_cap_nf.so" + # ðÒÉÎÉÍÁÅÔ ÉÎÆÏÒÍÁÃÉÀ Ï ÔÒÁÆÉËÅ ÐÏ ÐÒÏÔÏËÏÌÕ NetFlow + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÅÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + + # TCPPort - ÐÏÒÔ ÄÌÑ TCP-ÓÏÅÄÉÎÅÎÉÊ + TCPPort = 42111 + + # UDPPort - ÐÏÒÔ ÄÌÑ UDP-ÓÏÅÄÉÎÅÎÉÊ + UDPPort = 42111 + + # íÏÇÕÔ ÉÍÅÔØ ÓÏ×ÐÁÄÁÀÝÉÅ ÚÎÁÞÅÎÉÑ. + # åÓÌÉ ÐÁÒÁÍÅÔÒ ÎÅ ÕËÁÚÁÎ - ÓÏÏÔ×ÅÔÓÔ×ÕÀÝÉÊ ÐÏÒÔ ÎÅ "ÐÒÏÓÌÕÛÉ×ÁÅÔÓÑ". + + + + + # îÁÓÔÒÏÊËÉ ÍÏÄÕÌÑ ÐÉÎÇÕÀÝÅÇÏ ÐÏÌØÚÏ×ÁÔÅÌÅÊ "mod_ping.so" + # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ + + + # ÷ÒÅÍÑ, × ÓÅËÕÎÄÁÈ, ÍÅÖÄÕ ÐÉÎÇÁÍÉ ÏÄÎÏÇÏ É ÔÏÇÏ ÖÅ ÐÏÌØÚÏ×ÁÔÅÌÑ + # úÎÁÞÅÎÉÑ: 10...3600 + PingDelay = 15 + + + +# # îÁÓÔÒÏÊËÉ ÍÏÄÕÌÑ ÄÌÑ ÕÄÁÌÅÎÎÏÇÏ ×ÙÐÏÌÎÅÎÉÑ ÓËÒÉÐÔÏ× OnConnect É +# # OnDisconnect "mod_remote_script.so" +# # ÷ÔÏÒÏÊ ÐÁÒÁÍÅÔÒ - ÜÔÏ ÉÍÑ ÍÏÄÕÌÑ ÂÅÚ mod_ × ÎÁÞÁÌÅ É .so × ËÏÎÃÅ +# +# +# # ÷ÒÅÍÑ, × ÓÅËÕÎÄÁÈ, ÍÅÖÄÕ ÐÏÓÙÌËÁÍÉ ÐÏÄÔ×ÅÒÖÄÅÎÉÊ, ÔÏÇÏ, ÞÔÏ ÐÏÌØÚÏ×ÁÔÅÌØ +# # ×Ó£ ÅÝÅ ÏÎÌÁÊÎ +# # úÎÁÞÅÎÉÑ: 10...600 +# SendPeriod = 15 +# +# # óÏÏÔ×ÅÔÓÔ×ÉÅ ÐÏÄÓÅÔÅÊ, × ËÏÔÏÒÏÊ ÎÁÈÏÄÉÔÓÑ ÐÏÌØÚÏ×ÁÔÅÌØ É +# # ÓÏÏÔ×ÅÔÓÔ×ÕÀÝÅÇÏ ÒÏÕÔÅÒÁ. ðÅÒ×ÁÑ ÞÁÓÔØ ÓÔÒÏËÉ - ÐÏÄÓÅÔØ, ÚÁÄÁÎÎÁÑ +# # ËÁË IP-ÁÄÒÅÓ É ÍÁÓËÁ, ÞÅÒÅÚ ÐÒÏÂÅÌ - IP-ÁÄÒÅÓ ÒÏÕÔÅÒÁ ÎÁ ËÏÔÏÒÏÍ +# # ÄÏÌÖÎÙ ×ÙÐÏÌÎÑÔØÓÑ ÓËÒÉÐÔÙ +# # îÁÐÒÉÍÅÒ ÜÔÁ ÚÁÐÉÓØ "192.168.1.0/24 192.168.1.1" ÏÚÎÁÞÁÅÔ, ÞÔÏ ÄÌÑ +# # ×ÓÅÈ ÐÏÌØÚÏ×ÁÔÅÌÅÊ ÉÚ ÐÏÄÓÅÔÉ 192.168.1.0/24, ÓËÒÉÐÔÙ ÂÕÄÕÔ +# # ×ÙÐÏÌÎÑÔØÓÑ ÎÁ ÒÏÕÔÅÒÅ Ó ÁÄÒÅÓÏÍ 192.168.1.1 +# # Subnet0...Subnet100 +# Subnet0 = 192.168.1.0/24 192.168.1.7 +# Subnet1 = 192.168.2.0/24 192.168.2.5 +# Subnet2 = 192.168.3.0/24 192.168.2.5 +# Subnet3 = 192.168.4.0/24 192.168.2.5 +# +# # ðÁÒÏÌØ ÄÌÑ ÛÉÆÒÏ×ÁÎÉÑ ÐÁËÅÔÏ× ÍÅÖÄÕ stg-ÓÅÒ×ÅÒÏÍ É ÓÅÒ×ÅÒÏÍ, +# # ×ÙÐÏÌÎÑÀÝÉÍ ÓËÒÉÐÔÙ +# Password = 123456 +# +# # üÔÏÔ ÐÁÒÁÍÅÔÒ ÏÐÒÅÄÅÌÑÅÔ ËÁËÉÅ ÐÁÒÁÍÅÔÒÙ ÐÏÌØÚÏ×ÁÔÅÌÑ ÐÅÒÅÄÁÀÔÓÑ +# # ÎÁ ÕÄÁÌÅÎÎÙÊ ÓÅÒ×ÅÒ +# # Cash, FreeMb, Passive, Disabled, AlwaysOnline, TariffName, NextTariff, Address, +# # Note, Group, Email, RealName, Credit, EnabledDirs, Userdata0...Userdata9 +# UserParams=Cash Tariff EnabledDirs +# +# # ðÏÒÔ ÐÏ ËÏÔÏÒÏÍÕ ÓÅÒ×ÅÒ ÏÔÓÙÌÁÅÔ ÓÏÏÂÝÅÎÉÑ ÎÁ ÒÏÕÔÅÒ +# # úÎÁÞÅÎÉÑ: 1...65535 +# Port = 9999 +# +# + +# +# Password = 123456 +# ServerIP = 127.0.0.1 +# Port = 6666 +# AuthServices = Login-User +# AcctServices = Framed-User +# + + +################################################################################ + diff --git a/projects/stargazer/inst/var/00-alter-01.postgresql.sql b/projects/stargazer/inst/var/00-alter-01.postgresql.sql new file mode 100644 index 00000000..7fd81117 --- /dev/null +++ b/projects/stargazer/inst/var/00-alter-01.postgresql.sql @@ -0,0 +1,54 @@ +/* + * DB migration from v00 to v01 (postgres) + */ + +ALTER TABLE tb_sessions_log ADD free_mb dm_money; +ALTER TABLE tb_sessions_log ADD reason TEXT; + +DROP FUNCTION sp_add_session_log_entry ( dm_name, timestamp without time zone, dm_session_event_type, inet, dm_money); + +CREATE FUNCTION sp_add_session_log_entry(_login dm_name, + _event_time TIMESTAMP, + _event_type dm_session_event_type, + _ip INET, + _cash dm_money, + _free_mb dm_money, + _reason TEXT) +RETURNS INTEGER +AS $$ +DECLARE + _pk_user INTEGER; + _pk_session_log INTEGER; +BEGIN + SELECT pk_user INTO _pk_user + FROM tb_users + WHERE name = _login; + IF _pk_user IS NULL THEN + RAISE EXCEPTION 'User % not found', _login; + RETURN -1; + END IF; + + INSERT INTO tb_sessions_log + (fk_user, + event_time, + event_type, + ip, + cash, + free_mb, + reason) + VALUES + (_pk_user, + _event_time, + _event_type, + _ip, + _cash, + _free_mb, + _reason); + + SELECT CURRVAL('tb_sessions_log_pk_session_log_seq') INTO _pk_session_log; + + RETURN _pk_session_log; +END; +$$ LANGUAGE plpgsql; + +UPDATE tb_info SET version = 6; diff --git a/projects/stargazer/inst/var/00-alter-01.sql b/projects/stargazer/inst/var/00-alter-01.sql new file mode 100644 index 00000000..f76ae5f8 --- /dev/null +++ b/projects/stargazer/inst/var/00-alter-01.sql @@ -0,0 +1,25 @@ +/* + * DB migration from v00 to v01 (firebird) + */ + +alter table tb_users add disabled_detail_stat dm_bool; + +drop procedure sp_add_user; + +set term !! ; +create procedure sp_add_user(name varchar(32), dirs integer) +as +declare variable pk_user integer; +declare variable pk_stat integer; +begin + pk_user = gen_id(gn_pk_user, 1); + insert into tb_users(pk_user, fk_tariff, fk_tariff_change, fk_corporation, address, always_online, credit, credit_expire, disabled, disabled_detail_stat, email, grp, note, passive, passwd, phone, name, real_name) values (:pk_user, NULL, NULL, NULL, '', 0, 0, 'now', 0, 0, '', '_', '', 0, '', '', :name, ''); + pk_stat = gen_id(gn_pk_stat, 1); + insert into tb_stats values (:pk_stat, :pk_user, 0, 0, 'now', 0, 'now', 0, 'now'); + while (dirs > 0) do + begin + insert into tb_stats_traffic (fk_stat, dir_num, upload, download) values (:pk_stat, :dirs - 1, 0, 0); + dirs = dirs - 1; + end +end!! +set term ; !! diff --git a/projects/stargazer/inst/var/00-base-00.postgresql.sql b/projects/stargazer/inst/var/00-base-00.postgresql.sql new file mode 100644 index 00000000..5ec01be4 --- /dev/null +++ b/projects/stargazer/inst/var/00-base-00.postgresql.sql @@ -0,0 +1,638 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + ***************************************************************************** + * + * Скрипт генерации структуры базы для хранения данных Stargazer-a + * + * Примечание. + * * dm_permission_flag. Представляет собой битовую маску - rw. + * r - чтение, w - изменение параметра. + * 0 - дествие запрещено, 1 - действие разрешено + * + * * dm_traff_type. Число определяющее тип подсчета трафика: + * 0 - up - считается по upload + * 1 - down - считается по download + * 2 - max - считается по максимальному среди upload/download + * 3 - up+down - считается по сумме upload и download + * + * * dm_session_event_type. Указывает тип записи в логе о сессии. + * 'c' - connect, 'd' - disconnect. + * + * * При занесении IP адресса в БД выполнять приведение к + * знаковуму целому!!! + * + ***************************************************************************** + */ + +/* + * $Revision: 1.12 $ + * $Date: 2009/08/20 14:58:43 $ + */ + + +/* + ***************************************************************************** + * -= Создание типов и доменов =- + ***************************************************************************** + */ + +CREATE DOMAIN dm_name AS VARCHAR(32) NOT NULL; +CREATE DOMAIN dm_password AS VARCHAR(64) NOT NULL; +CREATE DOMAIN dm_permission_flag AS SMALLINT NOT NULL + CHECK ( value BETWEEN 0 AND 3 ); +CREATE DOMAIN dm_money AS NUMERIC(12, 4) NOT NULL DEFAULT 0; +CREATE DOMAIN dm_traff_type AS SMALLINT NOT NULL + CHECK ( value BETWEEN 0 AND 3 ); +CREATE DOMAIN dm_day AS SMALLINT NOT NULL + CHECK ( value BETWEEN 0 AND 31 ) + DEFAULT 0; +CREATE DOMAIN dm_session_event_type AS CHAR(1) NOT NULL + CHECK ( value = 'c' OR value = 'd' ); + +/* + ***************************************************************************** + * -= Создание таблиц =- + ***************************************************************************** + */ + +CREATE TABLE tb_info +( + version INTEGER NOT NULL +); + +CREATE TABLE tb_admins +( + pk_admin SERIAL PRIMARY KEY, + login dm_name UNIQUE, + passwd dm_password NOT NULL, + chg_conf dm_permission_flag, + chg_password dm_permission_flag, + chg_stat dm_permission_flag, + chg_cash dm_permission_flag, + usr_add_del dm_permission_flag, + chg_tariff dm_permission_flag, + chg_admin dm_permission_flag, + chg_service dm_permission_flag, + chg_corporation dm_permission_flag +); + +CREATE TABLE tb_tariffs +( + pk_tariff SERIAL PRIMARY KEY, + name dm_name UNIQUE, + fee dm_money, + free dm_money, + passive_cost dm_money, + traff_type dm_traff_type +); + +CREATE TABLE tb_tariffs_params +( + pk_tariff_param SERIAL PRIMARY KEY, + fk_tariff INTEGER NOT NULL, + dir_num SMALLINT NOT NULL, + price_day_a dm_money, + price_day_b dm_money, + price_night_a dm_money, + price_night_b dm_money, + threshold INTEGER NOT NULL, + time_day_begins TIME NOT NULL, + time_day_ends TIME NOT NULL, + + FOREIGN KEY (fk_tariff) + REFERENCES tb_tariffs (pk_tariff) + ON DELETE CASCADE +); + +CREATE TABLE tb_corporations +( + pk_corporation SERIAL PRIMARY KEY, + name dm_name UNIQUE, + cash dm_money +); + +CREATE TABLE tb_users +( + pk_user SERIAL PRIMARY KEY, + fk_tariff INTEGER, + fk_tariff_change INTEGER, + fk_corporation INTEGER, + address VARCHAR(256) NOT NULL, + always_online BOOLEAN NOT NULL, + credit dm_money, + credit_expire TIMESTAMP NOT NULL, + disabled BOOLEAN NOT NULL, + disabled_detail_stat BOOLEAN NOT NULL, + email VARCHAR(256) NOT NULL, + grp dm_name, + note TEXT NOT NULL, + passive BOOLEAN NOT NULL, + passwd dm_password, + phone VARCHAR(256) NOT NULL, + name dm_name UNIQUE, + real_name VARCHAR(256) NOT NULL, + cash dm_money, + free_mb dm_money, + last_activity_time TIMESTAMP NOT NULL, + last_cash_add dm_money, + last_cash_add_time TIMESTAMP NOT NULL, + passive_time INTEGER NOT NULL, + + FOREIGN KEY (fk_tariff) + REFERENCES tb_tariffs (pk_tariff) + ON DELETE CASCADE, + FOREIGN KEY (fk_tariff_change) + REFERENCES tb_tariffs (pk_tariff) + ON DELETE CASCADE, + FOREIGN KEY (fk_corporation) + REFERENCES tb_corporations (pk_corporation) + ON DELETE CASCADE +); + +CREATE TABLE tb_detail_stats +( + pk_detail_stat BIGSERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + dir_num SMALLINT NOT NULL, + ip INET NOT NULL, + download BIGINT NOT NULL, + upload BIGINT NOT NULL, + cost dm_money, + from_time TIMESTAMP NOT NULL, + till_time TIMESTAMP NOT NULL, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE +); + +CREATE TABLE tb_services +( + pk_service SERIAL PRIMARY KEY, + name dm_name UNIQUE, + comment TEXT NOT NULL, + cost dm_money, + pay_day dm_day +); + +CREATE TABLE tb_users_services +( + pk_user_service SERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + fk_service INTEGER NOT NULL, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE, + FOREIGN KEY (fk_service) + REFERENCES tb_services (pk_service) +); + +CREATE TABLE tb_messages +( + pk_message SERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + ver SMALLINT NOT NULL, + msg_type SMALLINT NOT NULL, + last_send_time TIMESTAMP NOT NULL, + creation_time TIMESTAMP NOT NULL, + show_time INTEGER NOT NULL, + repeat SMALLINT NOT NULL, + repeat_period INTEGER NOT NULL, + msg_text TEXT NOT NULL, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE +); + +CREATE TABLE tb_stats_traffic +( + pk_stat_traffic BIGSERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + stats_date DATE NOT NULL, + dir_num SMALLINT NOT NULL, + download BIGINT NOT NULL, + upload BIGINT NOT NULL, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE, + UNIQUE (fk_user, stats_date, dir_num) +); + +CREATE TABLE tb_users_data +( + pk_user_data SERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + num SMALLINT NOT NULL, + data VARCHAR(256) NOT NULL, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE +); + +CREATE TABLE tb_allowed_ip +( + pk_allowed_ip SERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + ip INET NOT NULL, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE +); + +CREATE TABLE tb_sessions_log +( + pk_session_log SERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + event_time TIMESTAMP NOT NULL, + event_type dm_session_event_type, + ip INET NOT NULL, + cash dm_money, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE +); + +CREATE TABLE tb_sessions_data +( + pk_session_data SERIAL PRIMARY KEY, + fk_session_log INTEGER NOT NULL, + dir_num SMALLINT NOT NULL, + session_upload BIGINT NOT NULL, + session_download BIGINT NOT NULL, + month_upload BIGINT NOT NULL, + month_download BIGINT NOT NULL, + + FOREIGN KEY (fk_session_log) + REFERENCES tb_sessions_log (pk_session_log) + ON DELETE CASCADE +); + +CREATE TABLE tb_parameters +( + pk_parameter SERIAL PRIMARY KEY, + name dm_name UNIQUE +); + +CREATE TABLE tb_params_log +( + pk_param_log SERIAL PRIMARY KEY, + fk_user INTEGER NOT NULL, + fk_parameter INTEGER NOT NULL, + fk_admin INTEGER NOT NULL, + ip INET NOT NULL, + event_time TIMESTAMP NOT NULL, + from_val VARCHAR(256), + to_val VARCHAR(256), + comment TEXT, + + FOREIGN KEY (fk_user) + REFERENCES tb_users (pk_user) + ON DELETE CASCADE, + FOREIGN KEY (fk_parameter) + REFERENCES tb_parameters (pk_parameter), + FOREIGN KEY (fk_admin) + REFERENCES tb_admins (pk_admin) + ON DELETE CASCADE +); + +/* + ***************************************************************************** + * -= Создание хранимых процедур =- + ***************************************************************************** + */ + +CREATE FUNCTION sp_add_message(_login dm_name, + _ver SMALLINT, + _msg_type SMALLINT, + _last_send_time TIMESTAMP, + _creation_time TIMESTAMP, + _show_time INTEGER, + _repeat SMALLINT, + _repeat_period INTEGER, + _msg_text TEXT) +RETURNS INTEGER +AS $$ +DECLARE + _pk_user INTEGER; +BEGIN + SELECT pk_user INTO _pk_user + FROM tb_users + WHERE name = _login; + IF _pk_user IS NULL THEN + RAISE EXCEPTION 'User % not found', _login; + RETURN -1; + END IF; + INSERT INTO tb_messages + (fk_user, + ver, + msg_type, + last_send_time, + creation_time, + show_time, + repeat, + repeat_period, + msg_text) + VALUES + (_pk_user, + _ver, + _msg_type, + _last_send_time, + _creation_time, + _show_time, + _repeat, + _repeat_period, + _msg_text); + RETURN CURRVAL('tb_messages_pk_message_seq'); +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION sp_add_tariff(_name dm_name, _dirs INTEGER) +RETURNS INTEGER +AS $$ +DECLARE + pk_tariff INTEGER; +BEGIN + INSERT INTO tb_tariffs + (name, + fee, + free, + passive_cost, + traff_type) + VALUES + (_name, + 0, 0, 0, 0); + SELECT CURRVAL('tb_tariffs_pk_tariff_seq') INTO pk_tariff; + FOR i IN 1.._dirs LOOP + INSERT INTO tb_tariffs_params + (fk_tariff, + dir_num, + price_day_a, + price_day_b, + price_night_a, + price_night_b, + threshold, + time_day_begins, + time_day_ends) + VALUES + (pk_tariff, + i - 1, + 0, 0, 0, 0, 0, + CAST('1970-01-01 00:00:00+00' AS TIMESTAMP), + CAST('1970-01-01 00:00:00+00' AS TIMESTAMP)); + END LOOP; + RETURN pk_tariff; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION sp_add_user(_name dm_name) +RETURNS INTEGER +AS $$ +DECLARE + pk_user INTEGER; +BEGIN + INSERT INTO tb_users + (fk_tariff, + fk_tariff_change, + fk_corporation, + address, + always_online, + credit, + credit_expire, + disabled, + disabled_detail_stat, + email, + grp, + note, + passive, + passwd, + phone, + name, + real_name, + cash, + free_mb, + last_activity_time, + last_cash_add, + last_cash_add_time, + passive_time) + VALUES + (NULL, NULL, NULL, '', FALSE, 0, CAST('now' AS TIMESTAMP), + FALSE, FALSE, '', '', '', FALSE, '', '', _name, '', 0, 0, + CAST('now' AS TIMESTAMP), 0, CAST('now' AS TIMESTAMP), 0); + SELECT CURRVAL('tb_users_pk_user_seq') INTO pk_user; + RETURN pk_user; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION sp_add_stats_traffic (_login dm_name, + _stats_date DATE, + _dir_num SMALLINT, + _upload BIGINT, + _download BIGINT) +RETURNS INTEGER +AS $$ +DECLARE + _pk_user INTEGER; +BEGIN + SELECT pk_user INTO _pk_user + FROM tb_users + WHERE name = _login; + + IF _pk_user IS NULL THEN + RAISE EXCEPTION 'User % not found', _login; + RETURN -1; + END IF; + + UPDATE tb_stats_traffic SET + upload = _upload, + download = _download + WHERE fk_user = _pk_user AND + dir_num = _dir_num AND + stats_date = _stats_date; + + IF NOT FOUND THEN + INSERT INTO tb_stats_traffic + (fk_user, + dir_num, + stats_date, + upload, + download) + VALUES + (_pk_user, + _dir_num, + _stats_date, + _upload, + _download); + END IF; + + RETURN 1; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION sp_set_user_data (_pk_user INTEGER, + _num SMALLINT, + _data VARCHAR(256)) +RETURNS INTEGER +AS $$ +BEGIN + UPDATE tb_users_data SET + data = _data + WHERE fk_user = _pk_user AND num = _num; + + IF NOT FOUND THEN + INSERT INTO tb_users_data + (fk_user, + num, + data) + VALUES + (_pk_user, + _num, + _data); + END IF; + + RETURN 1; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION sp_add_param_log_entry(_login dm_name, + _admin_login dm_name, + _ip INET, + _param_name dm_name, + _event_time TIMESTAMP, + _from VARCHAR(256), + _to VARCHAR(256), + _comment TEXT) +RETURNS INTEGER +AS $$ +DECLARE + _pk_user INTEGER; + _pk_admin INTEGER; + _pk_param INTEGER; +BEGIN + SELECT pk_user INTO _pk_user + FROM tb_users + WHERE name = _login; + IF _pk_user IS NULL THEN + RAISE EXCEPTION 'User % not found', _login; + RETURN -1; + END IF; + + SELECT pk_admin INTO _pk_admin + FROM tb_admins + WHERE login = _admin_login; + IF _pk_admin IS NULL THEN + RAISE EXCEPTION 'Admin % not found', _admin_login; + RETURN -1; + END IF; + + SELECT pk_parameter INTO _pk_param + FROM tb_parameters + WHERE name = _param_name; + + IF NOT FOUND THEN + INSERT INTO tb_parameters (name) VALUES (_param_name); + SELECT CURRVAL('tb_parameters_pk_parameter_seq') INTO _pk_param; + END IF; + + INSERT INTO tb_params_log + (fk_user, + fk_parameter, + fk_admin, + ip, + event_time, + from_val, + to_val, + comment) + VALUES + (_pk_user, + _pk_param, + _pk_admin, + _ip, + _event_time, + _from, + _to, + _comment); + + RETURN 1; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION sp_add_session_log_entry(_login dm_name, + _event_time TIMESTAMP, + _event_type dm_session_event_type, + _ip INET, + _cash dm_money) +RETURNS INTEGER +AS $$ +DECLARE + _pk_user INTEGER; + _pk_session_log INTEGER; +BEGIN + SELECT pk_user INTO _pk_user + FROM tb_users + WHERE name = _login; + IF _pk_user IS NULL THEN + RAISE EXCEPTION 'User % not found', _login; + RETURN -1; + END IF; + + INSERT INTO tb_sessions_log + (fk_user, + event_time, + event_type, + ip, + cash) + VALUES + (_pk_user, + _event_time, + _event_type, + _ip, + _cash); + + SELECT CURRVAL('tb_sessions_log_pk_session_log_seq') INTO _pk_session_log; + + RETURN _pk_session_log; +END; +$$ LANGUAGE plpgsql; + +/* + ***************************************************************************** + * -= Создание администратора =- + * + * Двоичные права доступа пока не поддерживаются, по этому используются флаги + ***************************************************************************** + */ +INSERT INTO tb_admins + (login, passwd, + chg_conf, chg_password, chg_stat, + chg_cash, usr_add_del, chg_tariff, + chg_admin, chg_service, chg_corporation) +VALUES + ('admin', + 'geahonjehjfofnhammefahbbbfbmpkmkmmefahbbbfbmpkmkmmefahbbbfbmpkmk', + 1, 1, 1, 1, 1, 1, 1, 1, 1); + +INSERT INTO tb_info + (version) +VALUES + (5); diff --git a/projects/stargazer/inst/var/00-base-00.sql b/projects/stargazer/inst/var/00-base-00.sql new file mode 100644 index 00000000..4047bc1f --- /dev/null +++ b/projects/stargazer/inst/var/00-base-00.sql @@ -0,0 +1,731 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + ***************************************************************************** + * + * Скрипт генерации структуры базы для хранения данных Stargazer-a + * + * $Id: 00-base-00.sql,v 1.7 2010/01/06 14:41:13 faust Exp $ + * + * Примечание. + * * dm_permission_flag. Представляет собой битовую маску - rw. + * r - чтение, w - изменение параметра. + * 0 - дествие запрещено, 1 - действие разрешено + * + * * dm_traff_type. Число определяющее тип подсчета трафика: + * 0 - up - считается по upload + * 1 - down - считается по download + * 2 - max - считается по максимальному среди upload/download + * 3 - up+down - считается по сумме upload и download + * Как альтернативу этому полю можно сделать еще одну таблицу - типов + * подсчета трафика. И в этом поле хранить ссылку на эту таблицу. + * Вопрос только "А надо ли это?" + * + * * dm_ip. IP адресс в виде четырех байтового целого числа со знаком. + * Выполнять приведение к знаковуму целому при занесении IP в БД!!! + * + * * dm_period. Задает периодичность показа сообщения пользователю. + * Период задается целым числом (int16). Если значение равно 0 то + * сообщение показывается только при подключении пользователя. + * Также этот домен определяет промежуток времени в течении которого + * сообщение показывается пользователю. + * + * * dm_session_event_type. Указывает тип записи в логе о сессии. + * 'c' - connect, 'd' - disconnect. + * + ***************************************************************************** + */ + +/* + * CONNECT 'localhost:/var/stg/stargazer.fdb' USER 'stg' PASSWORD '123456'; + * DROP DATABASE; + * + * CREATE DATABASE 'localhost:/var/stg/stargazer.fdb' USER 'stg' PASSWORD '123456' DEFAULT CHARACTER SET UTF8; + */ + + + +/* + ***************************************************************************** + * -= Создание ДОМЕНОВ =- + ***************************************************************************** + */ + +CREATE DOMAIN dm_id AS INTEGER NOT NULL; +CREATE DOMAIN dm_null_id AS INTEGER; +CREATE DOMAIN dm_login AS VARCHAR(32) NOT NULL; +CREATE DOMAIN dm_tariff_name AS VARCHAR(32) NOT NULL; +CREATE DOMAIN dm_group_name AS VARCHAR(32); +CREATE DOMAIN dm_corporation_name AS VARCHAR(32); +CREATE DOMAIN dm_parameter_name AS VARCHAR(32); + +CREATE DOMAIN dm_password AS VARCHAR(64) NOT NULL; +/* bitmask - rw => Read, Write */ +CREATE DOMAIN dm_permission_flag AS SMALLINT NOT NULL + CHECK ( VALUE BETWEEN 0 AND 3 ); +CREATE DOMAIN dm_money AS NUMERIC(10,6) NOT NULL; +/* (0, 1, 2, 3) => (up, down, max, up+down) */ +CREATE DOMAIN dm_traff_type AS SMALLINT NOT NULL + CHECK ( VALUE BETWEEN 0 AND 3 ); +CREATE DOMAIN dm_dir_num AS SMALLINT NOT NULL; +CREATE DOMAIN dm_num AS SMALLINT NOT NULL; +CREATE DOMAIN dm_traffic_mb AS INTEGER NOT NULL; +CREATE DOMAIN dm_traffic_byte AS BIGINT NOT NULL; +CREATE DOMAIN dm_time AS TIME NOT NULL; +CREATE DOMAIN dm_moment AS TIMESTAMP NOT NULL; +CREATE DOMAIN dm_credit_moment AS TIMESTAMP; +CREATE DOMAIN dm_ip AS INTEGER NOT NULL; +CREATE DOMAIN dm_mask AS INTEGER NOT NULL; +CREATE DOMAIN dm_user_address AS VARCHAR(256) DEFAULT ''; +CREATE DOMAIN dm_bool AS CHAR(1) NOT NULL + CHECK ( VALUE IN ('0', '1', 't', 'f', 'T', 'F') ); +CREATE DOMAIN dm_email AS VARCHAR(256) DEFAULT ''; +CREATE DOMAIN dm_note AS VARCHAR(256) DEFAULT ''; +CREATE DOMAIN dm_phone AS VARCHAR(256) DEFAULT ''; +CREATE DOMAIN dm_user_name AS VARCHAR(256) DEFAULT ''; +CREATE DOMAIN dm_service_comment AS VARCHAR(256) DEFAULT ''; +CREATE DOMAIN dm_service_name AS VARCHAR(32) DEFAULT ''; +/* TODO: why 0-31? Which is default? */ +CREATE DOMAIN dm_pay_day AS SMALLINT NOT NULL + CHECK ( VALUE BETWEEN 0 AND 31 ); +CREATE DOMAIN dm_period AS INTEGER NOT NULL; +CREATE DOMAIN dm_counter AS SMALLINT NOT NULL; +/* Is it needded? */ +CREATE DOMAIN dm_message_ver AS INTEGER NOT NULL; +CREATE DOMAIN dm_message_type AS INTEGER NOT NULL; +/*----------------*/ +CREATE DOMAIN dm_message AS VARCHAR(256) NOT NULL; +CREATE DOMAIN dm_user_data AS VARCHAR(256) NOT NULL; +CREATE DOMAIN dm_session_event_type AS CHAR(1) NOT NULL + CHECK ( VALUE IN ('c', 'd') ); +CREATE DOMAIN dm_char_value AS VARCHAR(64) NOT NULL; +CREATE DOMAIN dm_date AS DATE NOT NULL; + + + +/* + ***************************************************************************** + * -= Создание ТАБЛИЦ =- + ***************************************************************************** + */ + +CREATE TABLE tb_admins +( + pk_admin dm_id PRIMARY KEY, + login dm_login UNIQUE, + passwd dm_password, + chg_conf dm_permission_flag, + chg_password dm_permission_flag, + chg_stat dm_permission_flag, + chg_cash dm_permission_flag, + usr_add_del dm_permission_flag, + chg_tariff dm_permission_flag, + chg_admin dm_permission_flag, + chg_service dm_permission_flag, + chg_corporation dm_permission_flag +); + +CREATE TABLE tb_tariffs +( + pk_tariff dm_id PRIMARY KEY, + name dm_tariff_name UNIQUE, + fee dm_money, + free dm_money, + passive_cost dm_money, + traff_type dm_traff_type +); + +CREATE TABLE tb_tariffs_params +( + pk_tariff_param dm_id PRIMARY KEY, + fk_tariff dm_id, + dir_num dm_dir_num, + price_day_a dm_money, + price_day_b dm_money, + price_night_a dm_money, + price_night_b dm_money, + threshold dm_traffic_mb, + time_day_begins dm_time, + time_day_ends dm_time, + + FOREIGN KEY (fk_tariff) REFERENCES tb_tariffs (pk_tariff) +); + +CREATE TABLE tb_corporations +( + pk_corporation dm_id PRIMARY KEY, + name dm_corporation_name UNIQUE, + cash dm_money +); + +CREATE TABLE tb_users +( + pk_user dm_id PRIMARY KEY, + fk_tariff dm_null_id, + fk_tariff_change dm_null_id, + fk_corporation dm_null_id, + address dm_user_address, + always_online dm_bool, + credit dm_money, + credit_expire dm_credit_moment, + disabled dm_bool, + disabled_detail_stat dm_bool, + email dm_email, + grp dm_group_name, + note dm_note, + passive dm_bool, + passwd dm_password, + phone dm_phone, + name dm_login UNIQUE, + real_name dm_user_name, + + FOREIGN KEY (fk_tariff) REFERENCES tb_tariffs (pk_tariff), + FOREIGN KEY (fk_tariff_change) REFERENCES tb_tariffs (pk_tariff), + FOREIGN KEY (fk_corporation) REFERENCES tb_corporations (pk_corporation) +); + +CREATE TABLE tb_detail_stats +( + pk_detail_stat dm_id PRIMARY KEY, + fk_user dm_id, + dir_num dm_dir_num, + ip dm_ip, + download dm_traffic_byte, + upload dm_traffic_byte, + cost dm_money, + from_time dm_moment, + till_time dm_moment, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user) +); + +CREATE TABLE tb_services +( + pk_service dm_id PRIMARY KEY, + name dm_service_name UNIQUE, + comment dm_service_comment, + cost dm_money, + pay_day dm_pay_day +); + +CREATE TABLE tb_users_services +( + pk_user_service dm_id PRIMARY KEY, + fk_user dm_id, + fk_service dm_id, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user), + FOREIGN KEY (fk_service) REFERENCES tb_services (pk_service) +); + +CREATE TABLE tb_messages +( + pk_message dm_id PRIMARY KEY, + fk_user dm_id, + ver dm_message_ver, + msg_type dm_message_type, + last_send_time dm_period, + creation_time dm_period, + show_time dm_period, + repeat dm_counter, + repeat_period dm_period, + msg_text dm_message, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user) +); + +CREATE TABLE tb_stats +( + pk_stat dm_id PRIMARY KEY, + fk_user dm_id, + cash dm_money, + free_mb dm_money, + last_activity_time dm_moment, + last_cash_add dm_money, + last_cash_add_time dm_moment, + passive_time dm_period, + stats_date dm_date, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user) +); + +CREATE TABLE tb_stats_traffic +( + pk_stat_traffic dm_id PRIMARY KEY, + fk_stat dm_id, + dir_num dm_dir_num, + download dm_traffic_byte, + upload dm_traffic_byte, + + FOREIGN KEY (fk_stat) REFERENCES tb_stats (pk_stat) +); + +CREATE TABLE tb_users_data +( + pk_user_data dm_id PRIMARY KEY, + fk_user dm_id, + num dm_num, /* data_id dm_id renamed */ + data dm_user_data, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user) +); + +CREATE TABLE tb_allowed_ip +( + pk_allowed_ip dm_id PRIMARY KEY, + fk_user dm_id, + ip dm_ip, + mask dm_mask, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user) +); + +CREATE TABLE tb_sessions_log +( + pk_session_log dm_id PRIMARY KEY, + fk_user dm_id, + event_time dm_moment, + event_type dm_session_event_type, + ip dm_ip, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user) +); + +CREATE TABLE tb_sessions_data +( + pk_session_data dm_id PRIMARY KEY, + fk_session_log dm_id, + dir_num dm_dir_num, + session_upload dm_traffic_byte, + session_download dm_traffic_byte, + month_upload dm_traffic_byte, + month_download dm_traffic_byte, + + FOREIGN KEY (fk_session_log) REFERENCES tb_sessions_log (pk_session_log) +); + +CREATE TABLE tb_parameters +( + pk_parameter dm_id PRIMARY KEY, + name dm_parameter_name UNIQUE +); + +CREATE TABLE tb_params_log +( + pk_param_log dm_id PRIMARY KEY, + fk_user dm_id, + fk_parameter dm_id, + event_time dm_moment, + from_val dm_char_value, + to_val dm_char_value, + comment dm_service_comment, + + FOREIGN KEY (fk_user) REFERENCES tb_users (pk_user), + FOREIGN KEY (fk_parameter) REFERENCES tb_parameters (pk_parameter) +); + + +/* + ***************************************************************************** + * -= Создание ИНДЕКСОВ =- + ***************************************************************************** + */ + + + +/* + ***************************************************************************** + * -= Создание ГЕНЕРАТОРОВ =- + ***************************************************************************** + */ + +CREATE GENERATOR gn_pk_admin; +SET GENERATOR gn_pk_admin TO 0; +CREATE GENERATOR gn_pk_tariff; +SET GENERATOR gn_pk_tariff TO 0; +CREATE GENERATOR gn_pk_tariff_param; +SET GENERATOR gn_pk_tariff_param TO 0; +CREATE GENERATOR gn_pk_corporation; +SET GENERATOR gn_pk_corporation TO 0; +CREATE GENERATOR gn_pk_user; +SET GENERATOR gn_pk_user TO 0; +CREATE GENERATOR gn_pk_detail_stat; +SET GENERATOR gn_pk_detail_stat TO 0; +CREATE GENERATOR gn_pk_service; +SET GENERATOR gn_pk_service TO 0; +CREATE GENERATOR gn_pk_user_service; +SET GENERATOR gn_pk_user_service TO 0; +CREATE GENERATOR gn_pk_message; +SET GENERATOR gn_pk_message TO 0; +CREATE GENERATOR gn_pk_stat; +SET GENERATOR gn_pk_stat TO 0; +CREATE GENERATOR gn_pk_stat_traffic; +SET GENERATOR gn_pk_stat_traffic TO 0; +CREATE GENERATOR gn_pk_user_data; +SET GENERATOR gn_pk_user_data TO 0; +CREATE GENERATOR gn_pk_allowed_ip; +SET GENERATOR gn_pk_allowed_ip TO 0; +CREATE GENERATOR gn_pk_session; +SET GENERATOR gn_pk_session TO 0; +CREATE GENERATOR gn_pk_session_log; +SET GENERATOR gn_pk_session_log TO 0; +CREATE GENERATOR gn_pk_session_data; +SET GENERATOR gn_pk_session_data TO 0; +CREATE GENERATOR gn_pk_parameter; +SET GENERATOR gn_pk_parameter TO 0; +CREATE GENERATOR gn_pk_param_log; +SET GENERATOR gn_pk_param_log TO 0; + + +/* + ***************************************************************************** + * -= Создание ТРИГГЕРОВ =- + ***************************************************************************** + */ + +SET TERM !! ; +CREATE TRIGGER tr_admin_bi FOR tb_admins +ACTIVE BEFORE INSERT POSITION 0 +AS +BEGIN + IF (new.pk_admin IS NULL) + THEN new.pk_admin = GEN_ID(gn_pk_admin, 1); +END !! +SET TERM ; !! + +/*set term !! ; +create trigger tr_tariff_bi for tb_tariffs active +before insert position 0 +as +begin + if (new.pk_tariff is null) + then new.pk_tariff = gen_id(gn_pk_tariff, 1); +end !! +set term ; !!*/ + +set term !! ; +create trigger tr_tariff_param_bi for tb_tariffs_params active +before insert position 0 +as +begin + if (new.pk_tariff_param is null) + then new.pk_tariff_param = gen_id(gn_pk_tariff_param, 1); +end !! +set term ; !! + +set term !! ; +create trigger tr_corporation_bi for tb_corporations active +before insert position 0 +as +begin + if (new.pk_corporation is null) + then new.pk_corporation = gen_id(gn_pk_corporation, 1); +end !! +set term ; !! + +/*set term !! ; +create trigger tr_user_bi for tb_users active +before insert position 0 +as +begin + if (new.pk_user is null) + then new.pk_user = gen_id(gn_pk_user, 1); +end !! +set term ; !!*/ + +set term !! ; +create trigger tr_detail_stat_bi for tb_detail_stats active +before insert position 0 +as +begin + if (new.pk_detail_stat is null) + then new.pk_detail_stat = gen_id(gn_pk_detail_stat, 1); +end !! +set term ; !! + +set term !! ; +create trigger tr_service_bi for tb_services active +before insert position 0 +as +begin + if (new.pk_service is null) + then new.pk_service = gen_id(gn_pk_service, 1); +end !! +set term ; !! + +set term !! ; +create trigger tr_user_service_bi for tb_users_services active +before insert position 0 +as +begin + if (new.pk_user_service is null) + then new.pk_user_service = gen_id(gn_pk_user_service, 1); +end !! +set term ; !! + +/*set term !! ; +create trigger tr_message_bi for tb_messages active +before insert position 0 +as +begin + if (new.pk_message is null) + then new.pk_message = gen_id(gn_pk_message, 1); +end !! +set term ; !!*/ + +/*set term !! ; +create trigger tr_stat_bi for tb_stats active +before insert position 0 +as +begin + if (new.pk_stat is null) + then new.pk_stat = gen_id(gn_pk_stat, 1); +end !! +set term ; !!*/ + +set term !! ; +create trigger tr_stat_traffic_bi for tb_stats_traffic active +before insert position 0 +as +begin + if (new.pk_stat_traffic is null) + then new.pk_stat_traffic = gen_id(gn_pk_stat_traffic, 1); +end !! +set term ; !! + +set term !! ; +create trigger tr_user_data_bi for tb_users_data active +before insert position 0 +as +begin + if (new.pk_user_data is null) + then new.pk_user_data = gen_id(gn_pk_user_data, 1); +end !! +set term ; !! + +set term !! ; +create trigger tr_allowed_ip_bi for tb_allowed_ip active +before insert position 0 +as +begin + if (new.pk_allowed_ip is null) + then new.pk_allowed_ip = gen_id(gn_pk_allowed_ip, 1); +end !! +set term ; !! + +/*set term !! ; +create trigger tr_session_log_bi for tb_sessions_log active +before insert position 0 +as +begin + if (new.pk_session_log is null) + then new.pk_session_log = gen_id(gn_pk_session_log, 1); +end !! +set term ; !!*/ + +set term !! ; +create trigger tr_session_data_bi for tb_sessions_data active +before insert position 0 +as +begin + if (new.pk_session_data is null) + then new.pk_session_data = gen_id(gn_pk_session_data, 1); +end !! +set term ; !! + +set term !! ; +create trigger tr_parameter_bi for tb_parameters active +before insert position 0 +as +begin + if (new.pk_parameter is null) + then new.pk_parameter = gen_id(gn_pk_parameter, 1); +end !! +set term ; !! + +set term !! ; +create trigger tr_param_log_bi for tb_params_log active +before insert position 0 +as +begin + if (new.pk_param_log is null) + then new.pk_param_log = gen_id(gn_pk_param_log, 1); +end !! +set term ; !! + +/* + ***************************************************************************** + * -= Создание stored procedure =- + ***************************************************************************** + */ + +/* + * Add a message returning it's ID + */ +set term !! ; +create procedure sp_add_message(pk_message integer, login varchar(32), ver integer, msg_type integer, last_send_time integer, creation_time integer, show_time integer, repeat integer, repeat_period integer, msg_text varchar(256)) +returns(res integer) +as +begin + if (:pk_message is null) then + begin + pk_message = gen_id(gn_pk_message, 1); + insert into tb_messages values (:pk_message, + (select pk_user from tb_users where name = :login), + :ver, + :msg_type, + :last_send_time, + :creation_time, + :show_time, + :repeat, + :repeat_period, + :msg_text); + end + else + begin + update tb_messages set fk_user = (select pk_user from tb_users where name = :login), + ver = :ver, + msg_type = :msg_type, + last_send_time = :last_send_time, + creation_time = :creation_time, + show_time = :show_time, + repeat = :repeat_period, + repeat_period = :repeat_period, + msg_text = :msg_text + where pk_message = :pk_message; + end + res = :pk_message; +end !! +set term ; !! + +set term !! ; +create procedure sp_delete_service(name varchar(32)) +as +declare variable pk_service integer; +begin + select pk_service from tb_services where name = :name into pk_service; + if (pk_service is not null) then + begin + delete from tb_users_services where fk_service = :pk_service; + delete from tb_services where pk_service = :pk_service; + end +end !! +set term ; !! + +set term !! ; +create procedure sp_add_tariff(name varchar(32), dirs integer) +as +declare variable pk_tariff integer; +begin + pk_tariff = gen_id(gn_pk_tariff, 1); + insert into tb_tariffs (pk_tariff, name, fee, free, passive_cost, traff_type) values (:pk_tariff, :name, 0, 0, 0, 0); + while (dirs > 0) do + begin + insert into tb_tariffs_params (fk_tariff, dir_num, price_day_a, + price_day_b, price_night_a, price_night_b, + threshold, time_day_begins, time_day_ends) + values (:pk_tariff, :dirs - 1, 0, 0, 0, 0, 0, '0:0', '0:0'); + dirs = dirs - 1; + end +end !! +set term ; !! + +set term !! ; +create procedure sp_delete_tariff(name varchar(32)) +as +declare variable pk_tariff integer; +begin + select pk_tariff from tb_tariffs where name = :name into pk_tariff; + if (pk_tariff is not null) then + begin + delete from tb_tariffs_params where fk_tariff = :pk_tariff; + delete from tb_tariffs where pk_tariff = :pk_tariff; + end +end !! +set term ; !! + +set term !! ; +create procedure sp_add_user(name varchar(32), dirs integer) +as +declare variable pk_user integer; +declare variable pk_stat integer; +begin + pk_user = gen_id(gn_pk_user, 1); + insert into tb_users(pk_user, fk_tariff, fk_tariff_change, fk_corporation, address, always_online, credit, credit_expire, disabled, disabled_detail_stat, email, grp, note, passive, passwd, phone, name, real_name) values (:pk_user, NULL, NULL, NULL, '', 0, 0, 'now', 0, 0, '', '_', '', 0, '', '', :name, ''); + pk_stat = gen_id(gn_pk_stat, 1); + insert into tb_stats values (:pk_stat, :pk_user, 0, 0, 'now', 0, 'now', 0, 'now'); + while (dirs > 0) do + begin + insert into tb_stats_traffic (fk_stat, dir_num, upload, download) values (:pk_stat, :dirs - 1, 0, 0); + dirs = dirs - 1; + end +end!! +set term ; !! + +set term !! ; +create procedure sp_delete_user(name varchar(32)) +as +declare variable pk_user integer; +begin + select pk_user from tb_users where name = :name into pk_user; + if (pk_user is not null) then + begin + delete from tb_users_services where fk_user = :pk_user; + delete from tb_params_log where fk_user = :pk_user; + delete from tb_detail_stats where fk_user = :pk_user; + delete from tb_stats_traffic where fk_stat in (select pk_stat from tb_stats where fk_user = :pk_user); + delete from tb_stats where fk_user = :pk_user; + delete from tb_sessions_data where fk_session_log in (select pk_session_log from tb_sessions_log where fk_user = :pk_user); + delete from tb_sessions_log where fk_user = :pk_user; + delete from tb_allowed_ip where fk_user = :pk_user; + delete from tb_users_data where fk_user = :pk_user; + delete from tb_messages where fk_user = :pk_user; + delete from tb_users where pk_user = :pk_user; + end +end !! +set term ; !! + +set term !! ; +create procedure sp_append_session_log(name varchar(32), event_time timestamp, event_type char(1), ip integer) +returns(pk_session_log integer) +as +begin + pk_session_log = gen_id(gn_pk_session_log, 1); + insert into tb_sessions_log (pk_session_log, fk_user, event_time, event_type, ip) values (:pk_session_log, (select pk_user from tb_users where name = :name), :event_time, :event_type, :ip); +end !! +set term ; !! + +set term !! ; +create procedure sp_add_stat(name varchar(32), cash numeric(10,6), free_mb numeric(10,6), last_activity_time timestamp, last_cash_add numeric(10,6), last_cash_add_time timestamp, passive_time integer, stats_date date) +returns(pk_stat integer) +as +begin + pk_stat = gen_id(gn_pk_stat, 1); + insert into tb_stats (pk_stat, fk_user, cash, free_mb, last_activity_time, last_cash_add, last_cash_add_time, passive_time, stats_date) values (:pk_stat, (select pk_user from tb_users where name = :name), :cash, :free_mb, :last_activity_time, :last_cash_add, :last_cash_add_time, :passive_time, :stats_date); +end !! +set term ; !! + +/* + ***************************************************************************** + * -= Создание администратора =- + ***************************************************************************** + */ + +insert into tb_admins values(0, 'admin', 'geahonjehjfofnhammefahbbbfbmpkmkmmefahbbbfbmpkmkmmefahbbbfbmpkmk', 1, 1, 1, 1, 1, 1, 1, 1, 1); + +/* EOF */ + diff --git a/projects/stargazer/inst/var/00-mysql-01.sql b/projects/stargazer/inst/var/00-mysql-01.sql new file mode 100644 index 00000000..253738ab --- /dev/null +++ b/projects/stargazer/inst/var/00-mysql-01.sql @@ -0,0 +1,5 @@ +/* + * DB migration from v00 to v01 (mysql) + */ + +ALTER TABLE users ADD DisabledDetailStat INT(3) DEFAULT 0; diff --git a/projects/stargazer/inst/var/base.dia b/projects/stargazer/inst/var/base.dia new file mode 100644 index 0000000000000000000000000000000000000000..a3d09fab5500547c579b26e29e454fddddabf5ca GIT binary patch literal 4546 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 gi2Uz9S + */ + + /* + $Revision: 1.124 $ + $Date: 2010/10/04 20:19:12 $ + $Author: faust $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "settings.h" +#include "user.h" +#include "users.h" +#include "admins.h" +#include "common.h" +#include "traffcounter.h" +#include "base_plugin.h" +#include "stg_logger.h" +#include "stg_timer.h" +#include "plugin_runner.h" +#include "script_executer.h" +#include "conffiles.h" +#include "version.h" +#include "store_loader.h" +#include "pidfile.h" +#include "eventloop.h" + +using namespace std; +uint32_t eip; + +#ifdef DEBUG + #define MAIN_DEBUG (1) + #define NO_DAEMON (1) +#endif + +#define START_FILE "/._ST_ART_ED_" + +static bool needRulesReloading = false; +static bool childExited = false; +//static pid_t executerPid; +set executersPid; +static pid_t stgChildPid; + +#include "pinger.h" + +//----------------------------------------------------------------------------- +bool StartModCmp(const PLUGIN_RUNNER & lhs, const PLUGIN_RUNNER & rhs) +{ +return lhs.GetStartPosition() < rhs.GetStartPosition(); +} +//----------------------------------------------------------------------------- +bool StopModCmp(const PLUGIN_RUNNER & lhs, const PLUGIN_RUNNER & rhs) +{ +return lhs.GetStopPosition() > rhs.GetStopPosition(); +} +//----------------------------------------------------------------------------- +class STG_STOPPER +{ +public: + STG_STOPPER() { nonstop = true; } + bool GetStatus() { return nonstop; }; + #ifdef NO_DAEMON + void Stop(const char * __file__, int __line__) + #else + void Stop(const char *, int) + #endif + { + #ifdef NO_DAEMON + printfd(__FILE__, "Stg stopped at %s:%d\n", __file__, __line__); + #endif + nonstop = false; + } +private: + bool nonstop; +}; +//----------------------------------------------------------------------------- +STG_STOPPER nonstop; +//----------------------------------------------------------------------------- +static void StartTimer() +{ +STG_LOGGER & WriteServLog = GetStgLogger(); + +if (RunStgTimer()) + { + WriteServLog("Cannot start timer. Fatal."); + //printfd(__FILE__, "Cannot start timer. Fatal.\n"); + exit(1); + } +else + { + WriteServLog("Timer thread started successfully."); + //printfd(__FILE__, "Timer thread started successfully.\n"); + } +} +//----------------------------------------------------------------------------- +void CatchPROF(int) +{ +/*STG_LOGGER & WriteServLog = GetStgLogger(); +WriteServLog("CatchPROF");*/ +} +//----------------------------------------------------------------------------- +void CatchUSR1(int) +{ + +} +//----------------------------------------------------------------------------- +void CatchTERM(int sig) +{ +/* + *Function Name:CatchINT + *Parameters: sig_num - ÎÏÍÅÒ ÓÉÇÎÁÌÁ + *Description: ïÂÒÁÂÏÔÞÉË ÓÉÇÎÁÌÁ INT + *Returns: îÉÞÅÇÏ + */ +STG_LOGGER & WriteServLog = GetStgLogger(); +WriteServLog("Shutting down... %d", sig); + +//nonstop = false; +nonstop.Stop(__FILE__, __LINE__); + +struct sigaction newsa, oldsa; +sigset_t sigmask; + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGTERM); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGTERM, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGINT, &newsa, &oldsa); +} +//----------------------------------------------------------------------------- +void CatchPIPE(int) +{ +STG_LOGGER & WriteServLog = GetStgLogger(); +WriteServLog("Broken pipe!"); +} +//----------------------------------------------------------------------------- +void CatchHUP(int) +{ +needRulesReloading = true; +} +//----------------------------------------------------------------------------- +void CatchCHLD(int) +{ +int status; +pid_t childPid; +childPid = waitpid(-1, &status, WNOHANG); + +set::iterator pid; +pid = executersPid.find(childPid); +if (pid != executersPid.end()) + { + executersPid.erase(pid); + if (executersPid.empty() && nonstop.GetStatus()) + { + nonstop.Stop(__FILE__, __LINE__); + } + } +if (childPid == stgChildPid) + { + childExited = true; + } +} +//----------------------------------------------------------------------------- +void CatchSEGV(int, siginfo_t *, void *) +{ +/*char fileName[50]; +sprintf(fileName, "/tmp/stg_segv.%d", getpid()); +FILE * f = fopen(fileName, "wt"); +if (f) + { + fprintf(f, "\nSignal info:\n~~~~~~~~~~~~\n"); + fprintf(f, "numb:\t %d (%d)\n", sinfo->si_signo, sig); + fprintf(f, "errn:\t %d\n", sinfo->si_errno); + fprintf(f, "code:\t %d ", sinfo->si_code); + + switch (sinfo->si_code) + { + case SEGV_MAPERR: + fprintf(f, "(SEGV_MAPERR - address not mapped to object)\n"); + break; + + case SEGV_ACCERR: + fprintf(f, "(SEGV_ACCERR - invalid permissions for mapped object)\n"); + break; + + default: + fprintf(f, "???\n"); + } + + fprintf(f, "addr:\t 0x%.8X\n", + (unsigned int)sinfo->si_addr); + + Dl_info dlinfo; + //asm("movl %eip, eip"); + if (dladdr((void*)CatchCHLD, &dlinfo)) + { + fprintf(f, "SEGV point: %s %s\n", dlinfo.dli_fname, dlinfo.dli_sname); + } + else + { + fprintf(f, "Cannot find SEGV point\n"); + } + + fclose(f); + } + +struct sigaction segv_action, segv_action_old; + +segv_action.sa_handler = SIG_DFL; +segv_action.sa_sigaction = NULL; +segv_action.sa_flags = SA_SIGINFO; +segv_action.sa_restorer = NULL; + +sigaction(SIGSEGV, &segv_action, &segv_action_old);*/ +} +//----------------------------------------------------------------------------- +static void SetSignalHandlers() +{ +struct sigaction newsa, oldsa; +sigset_t sigmask; +/////// +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGTERM); +newsa.sa_handler = CatchTERM; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGTERM, &newsa, &oldsa); +/////// +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGUSR1); +newsa.sa_handler = CatchUSR1; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGUSR1, &newsa, &oldsa); +/////// +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +newsa.sa_handler = CatchTERM; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGINT, &newsa, &oldsa); +/*/////// +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGPROF); +newsa.sa_handler = CatchPROF; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGPROF, &newsa, &oldsa);*/ +////// +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGPIPE); +newsa.sa_handler = CatchPIPE; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGPIPE, &newsa, &oldsa); +////// +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGHUP); +newsa.sa_handler = CatchHUP; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGHUP, &newsa, &oldsa); +////// +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGCHLD); +newsa.sa_handler = CatchCHLD; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGCHLD, &newsa, &oldsa); + +/*newsa.sa_handler = NULL; +newsa.sa_sigaction = CatchSEGV; +newsa.sa_flags = SA_SIGINFO; +newsa.sa_restorer = NULL; +sigaction(SIGSEGV, &newsa, &oldsa);*/ + +return; +} +//----------------------------------------------------------------------------- +int StartScriptExecuter(char * procName, int msgKey, int * msgID, SETTINGS * settings) +{ +STG_LOGGER & WriteServLog = GetStgLogger(); + +if (*msgID == -11) // If msgID == -11 - first call. Create queue + { + for (int i = 0; i < 2; i++) + { + //WriteServLog("Creating queue with key=%d ...", msgKey); + *msgID = msgget(msgKey, IPC_CREAT | IPC_EXCL | 0600); + + if (*msgID == -1) + { + *msgID = msgget(msgKey, 0); + if (*msgID == -1) + { + WriteServLog("Message queue not created."); + return -1; + } + else + { + msgctl(*msgID, IPC_RMID, NULL); + //printfd(__FILE__, "Queue removed!"); + } + } + else + { + WriteServLog("Message queue created successfully. msgKey=%d msgID=%d", msgKey, *msgID); + break; + } + } + } + +pid_t executerPid = fork(); + +switch (executerPid) + { + case -1: // ìÁÖÁ + WriteServLog("Fork error!"); + return -1; + + case 0: // ðÏÔÏÍÏË + //close(0); + //close(1); + //close(2); + //setsid(); + delete settings; + Executer(msgKey, *msgID, executerPid, procName); + return 1; + + default: // ïÓÎÏ×ÎÏÊ ÐÒÏÃÅÓÓ + if (executersPid.empty()) { + Executer(msgKey, *msgID, executerPid, NULL); + } + executersPid.insert(executerPid); + } +return 0; +} +//----------------------------------------------------------------------------- +#ifndef NO_DAEMON +int ForkAndWait(const string & confDir) +#else +int ForkAndWait(const string &) +#endif +{ +#ifndef NO_DAEMON +stgChildPid = fork(); +string startFile = confDir + START_FILE; +unlink(startFile.c_str()); + +switch (stgChildPid) + { + case -1: // ìÁÖÁ + return -1; + break; + + case 0: // ðÏÔÏÍÏË + //close(0); + close(1); + close(2); + setsid(); + break; + + default: // ïÓÎÏ×ÎÏÊ ÐÒÏÃÅÓÓ + for (int i = 0; i < 120 * 5; i++) + { + if (access(startFile.c_str(), F_OK) == 0) + { + //printf("Fork successfull. Exit.\n"); + unlink(startFile.c_str()); + exit(0); + } + + if (childExited) + { + unlink(startFile.c_str()); + exit(1); + } + usleep(200000); + } + unlink(startFile.c_str()); + exit(1); + break; + } +#endif +return 0; +} +//----------------------------------------------------------------------------- +void KillExecuters() +{ +set::iterator pid; +pid = executersPid.begin(); +while (pid != executersPid.end()) + { + printfd(__FILE__, "KillExecuters pid=%d\n", *pid); + kill(*pid, SIGUSR1); + ++pid; + } +} +//----------------------------------------------------------------------------- +int main(int argc, char * argv[]) +{ + +/* + Initialization order: + - Logger + - Stg timer + - Settings + - Plugins + - Plugins settings + - Read Admins + - Read Tariffs + - Read Users + - Start Users + - Start Traffcounter + - Start Plugins + - Start pinger + - Set signal nandlers + - Fork and exit + * */ + +SETTINGS * settings = NULL; +BASE_STORE * dataStore = NULL; +TARIFFS * tariffs = NULL; +ADMINS * admins = NULL; +USERS * users = NULL; +TRAFFCOUNTER * traffCnt = NULL; +int msgID = -11; + + { + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog.SetLogFileName("/var/log/stargazer.log"); + } + +vector modSettings; +list modules; + +list::iterator modIter; + +if (getuid()) + { + printf("You must be root. Exit.\n"); + exit(1); + } + +if (argc == 2) + settings = new SETTINGS(argv[1]); +else + settings = new SETTINGS(); + +if (settings->ReadSettings()) + { + //printfd(__FILE__, "ReadSettings error.\n"); + STG_LOGGER & WriteServLog = GetStgLogger(); + + if (settings->GetLogFileName() != "") + WriteServLog.SetLogFileName(settings->GetLogFileName()); + + WriteServLog("ReadSettings error. %s", settings->GetStrError().c_str()); + exit(1); + } + +#ifndef NO_DAEMON +string startFile(settings->GetConfDir() + START_FILE); +#endif + +//SetSignalHandlers(); +if (ForkAndWait(settings->GetConfDir()) < 0) + { + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog("Fork error!"); + exit(1); + } + +STG_LOGGER & WriteServLog = GetStgLogger(); +WriteServLog.SetLogFileName(settings->GetLogFileName()); +WriteServLog("Stg v. %s", SERVER_VERSION); + +for (int i = 0; i < settings->GetExecutersNum(); i++) + { + int ret = StartScriptExecuter(argv[0], settings->GetExecMsgKey(), &msgID, settings); + if (ret < 0) + { + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog("Start Script Executer error!"); + //goto exitLbl; + return 1; + } + if (ret == 1) + { + // Stopping child + return 0; + } + } + +PIDFile pidFile(settings->GetPIDFileName()); + +StartTimer(); +WaitTimer(); +if (!IsStgTimerRunning()) + { + printfd(__FILE__, "Timer thread not started in 1 sec!\n"); + WriteServLog("Timer thread not started in 1 sec!"); + } + +EVENT_LOOP & loop(EVENT_LOOP_SINGLETON::GetInstance()); + +STORE_LOADER storeLoader(*settings); +if (storeLoader.Load()) + { + WriteServLog("Storage plugin: '%s'", storeLoader.GetStrError().c_str()); + goto exitLblNotStarted; + } + +if (loop.Start()) + { + WriteServLog("Event loop not started."); + goto exitLblNotStarted; + } + +dataStore = storeLoader.GetStore(); +WriteServLog("Storage plugin: %s. Loading successfull.", dataStore->GetVersion().c_str()); + +tariffs = new TARIFFS(dataStore); +admins = new ADMINS(dataStore); +users = new USERS(settings, dataStore, tariffs, admins->GetSysAdmin()); +traffCnt = new TRAFFCOUNTER(users, tariffs, settings->GetRulesFileName()); +traffCnt->SetMonitorDir(settings->GetMonitorDir()); +//tariffs->SetUsers(users); + +modSettings = settings->GetModulesSettings(); + +for (unsigned i = 0; i < modSettings.size(); i++) + { + string modulePath = settings->GetModulesPath(); + modulePath += "/mod_"; + modulePath += modSettings[i].moduleName; + modulePath += ".so"; + printfd(__FILE__, "Module: %s\n", modulePath.c_str()); + modules.push_back( + PLUGIN_RUNNER(modulePath, + modSettings[i], + admins, + tariffs, + users, + traffCnt, + dataStore, + settings) + ); + } + +modIter = modules.begin(); + +while (modIter != modules.end()) + { + //Loading modules + if (modIter->Load()) + { + WriteServLog("Error: %s", + modIter->GetStrError().c_str()); + goto exitLblNotStarted; + } + ++modIter; + } + +//Start section +if (users->Start()) + { + goto exitLblNotStarted; + } +WriteServLog("Users started successfully."); + +if (traffCnt->Start()) + { + goto exitLblNotStarted; + } +WriteServLog("Traffcounter started successfully."); + +//Sort by start order +modules.sort(StartModCmp); +modIter = modules.begin(); + +while (modIter != modules.end()) + { + if (modIter->Start()) + { + WriteServLog("Error: %s", + modIter->GetStrError().c_str()); + //printfd(__FILE__, "Error: %s\n", capRunner.GetStrError().c_str()); + goto exitLbl; + } + WriteServLog("Module: \'%s\'. Start successfull. %d", modIter->GetPlugin()->GetVersion().c_str(), + modIter->GetPlugin()->GetStartPosition()); + ++modIter; + } +SetSignalHandlers(); + +srandom(stgTime); + +/* + * Note that an implementation in which nice returns the new nice value + * can legitimately return -1. To reliably detect an error, set + * errno to 0 before the call, and check its value when nice returns -1. + * + * + * (c) man 2 nice + */ +errno = 0; +if (nice(-19) && errno) { + printfd(__FILE__, "nice failed: '%s'\n", strerror(errno)); + WriteServLog("nice failed: '%s'", strerror(errno)); +} + +WriteServLog("Stg started successfully."); +WriteServLog("+++++++++++++++++++++++++++++++++++++++++++++"); + +#ifndef NO_DAEMON +creat(startFile.c_str(), S_IRUSR); +#endif + +//*a_kill_it = 0; + +while (nonstop.GetStatus()) + { + if (needRulesReloading) + { + needRulesReloading = false; + traffCnt->Reload(); + + modIter = modules.begin(); + for (; modIter != modules.end(); ++modIter) + { + if (modIter->Reload()) + { + WriteServLog("Error reloading %s ('%s')", modIter->GetPlugin()->GetVersion().c_str(), + modIter->GetStrError().c_str()); + printfd(__FILE__, "Error reloading %s ('%s')\n", modIter->GetPlugin()->GetVersion().c_str(), + modIter->GetStrError().c_str()); + } + } + } + stgUsleep(100000); + } + +exitLbl: + +WriteServLog("+++++++++++++++++++++++++++++++++++++++++++++"); + +//Sort by start order +modules.sort(StopModCmp); +modIter = modules.begin(); +while (modIter != modules.end()) + { + std::string name = modIter->GetFileName(); + printfd(__FILE__, "Stopping module '%s'\n", name.c_str()); + if (modIter->Stop()) + { + WriteServLog("Module \'%s\': Error: %s", + modIter->GetPlugin()->GetVersion().c_str(), + modIter->GetStrError().c_str()); + printfd(__FILE__, "Failed to stop module '%s'\n", name.c_str()); + //printfd(__FILE__, "Error: %s\n", capRunner.GetStrError().c_str()); + //goto exitLbl; + } + WriteServLog("Module: \'%s\'. Stop successfull.", modIter->GetPlugin()->GetVersion().c_str()); + ++modIter; + } + +if (loop.Stop()) + { + WriteServLog("Event loop not stopped."); + } + +exitLblNotStarted: + +/*modIter = modules.begin(); +while (modIter != modules.end()) + { + std::string name = modIter->GetFileName(); + printfd(__FILE__, "Unloading module '%s'\n", name.c_str()); + if (modIter->Unload()) + { + WriteServLog("Module \'%s\': Error: %s", + name.c_str(), + modIter->GetStrError().c_str()); + printfd(__FILE__, "Failed to unload module '%s'\n", name.c_str()); + } + ++modIter; + }*/ + +if (traffCnt) + { + traffCnt->Stop(); + WriteServLog("Traffcounter: Stop successfull."); + } + +if (users) + { + users->Stop(); + WriteServLog("Users: Stop successfull."); + } + +sleep(1); +int res = msgctl(msgID, IPC_RMID, NULL); +if (res) + WriteServLog("Queue was not removed. id=%d", msgID); +else + WriteServLog("Queue removed successfully."); + +/*struct sigaction newsa, oldsa; +sigset_t sigmask; +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGCHLD); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGCHLD, &newsa, &oldsa);*/ + +KillExecuters(); + +StopStgTimer(); +WriteServLog("StgTimer: Stop successfull."); + +WriteServLog("Stg stopped successfully."); +sleep(1); +WriteServLog("---------------------------------------------"); + +delete traffCnt; +delete users; +delete admins; +delete tariffs; +delete settings; + +return 0; +} +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/pidfile.cpp b/projects/stargazer/pidfile.cpp new file mode 100644 index 00000000..0f47a257 --- /dev/null +++ b/projects/stargazer/pidfile.cpp @@ -0,0 +1,53 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.2 $ + $Date: 2009/06/09 09:07:32 $ + $Author: faust $ + */ + +/* + * An implementation of RAII pid-file writer + */ + +#include +#include + +#include "pidfile.h" + +PIDFile::PIDFile(const std::string & fn) + : fileName(fn) +{ +if (fileName != "") + { + std::ofstream pf(fileName.c_str()); + pf << getpid() << std::endl; + pf.close(); + } +} + +PIDFile::~PIDFile() +{ +if (fileName != "") + { + unlink(fileName.c_str()); + } +} diff --git a/projects/stargazer/pidfile.h b/projects/stargazer/pidfile.h new file mode 100644 index 00000000..47fbefd9 --- /dev/null +++ b/projects/stargazer/pidfile.h @@ -0,0 +1,44 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Header file for RAII pid-file writer + */ + +/* + $Revision: 1.2 $ + $Date: 2009/06/09 09:07:32 $ + $Author: faust $ + */ + +#ifndef __PID_FILE_H__ +#define __PID_FILE_H__ + +#include + +class PIDFile { +public: + PIDFile(const std::string & fn); + ~PIDFile(); +private: + std::string fileName; +}; + +#endif diff --git a/projects/stargazer/plugin_runner.cpp b/projects/stargazer/plugin_runner.cpp new file mode 100644 index 00000000..180713c0 --- /dev/null +++ b/projects/stargazer/plugin_runner.cpp @@ -0,0 +1,234 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.17 $ + $Date: 2010/09/13 05:52:46 $ + $Author: faust $ + */ + +#include +#include +#include + +#include "plugin_runner.h" +#include "common.h" +#include "conffiles.h" + +//----------------------------------------------------------------------------- +PLUGIN_RUNNER::PLUGIN_RUNNER(const string & pFileName, + const MODULE_SETTINGS & ms, + ADMINS * a, + TARIFFS * t, + USERS * u, + TRAFFCOUNTER * tc, + BASE_STORE * st, + const SETTINGS * s) + : pluginFileName(pFileName), + pluginSettingFileName(), + plugin(NULL), + isPluginLoaded(false), + errorStr(), + libHandle(NULL), + isRunning(false), + admins(a), + tariffs(t), + users(u), + store(st), + traffCnt(tc), + stgSettings(s), + modSettings(ms) +{ +} +//----------------------------------------------------------------------------- +PLUGIN_RUNNER::PLUGIN_RUNNER(const PLUGIN_RUNNER & rvalue) + : pluginFileName(rvalue.pluginFileName), + pluginSettingFileName(rvalue.pluginSettingFileName), + plugin(rvalue.plugin), + isPluginLoaded(rvalue.isPluginLoaded), + errorStr(rvalue.errorStr), + libHandle(rvalue.libHandle), + isRunning(rvalue.isRunning), + admins(rvalue.admins), + tariffs(rvalue.tariffs), + users(rvalue.users), + store(rvalue.store), + traffCnt(rvalue.traffCnt), + stgSettings(rvalue.stgSettings), + modSettings(rvalue.modSettings) +{ +} +//----------------------------------------------------------------------------- +PLUGIN_RUNNER & PLUGIN_RUNNER::operator=(const PLUGIN_RUNNER & rvalue) +{ +pluginFileName = rvalue.pluginFileName; +pluginSettingFileName = rvalue.pluginSettingFileName; +plugin = rvalue.plugin; +isPluginLoaded = rvalue.isPluginLoaded; +errorStr = rvalue.errorStr; +libHandle = rvalue.libHandle; +isRunning = rvalue.isRunning; +admins = rvalue.admins; +tariffs = rvalue.tariffs; +users = rvalue.users; +store = rvalue.store; +traffCnt = rvalue.traffCnt; +stgSettings = rvalue.stgSettings; +modSettings = rvalue.modSettings; + +return *this; +} +//----------------------------------------------------------------------------- +PLUGIN_RUNNER::~PLUGIN_RUNNER() +{ +if (isPluginLoaded) + { + Unload(); + } + +isPluginLoaded = 0; +} +//----------------------------------------------------------------------------- +BASE_PLUGIN * PLUGIN_RUNNER::GetPlugin() +{ +return plugin; +} +//----------------------------------------------------------------------------- +int PLUGIN_RUNNER::Start() +{ +if (!isPluginLoaded) + if (Load()) + return -1; + +plugin->SetTariffs(tariffs); +plugin->SetAdmins(admins); +plugin->SetUsers(users); +plugin->SetTraffcounter(traffCnt); +plugin->SetStore(store); +plugin->SetStgSettings(stgSettings); + +if (plugin->Start()) + { + errorStr = plugin->GetStrError(); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int PLUGIN_RUNNER::Stop() +{ +plugin->Stop(); + +//if (Unload()) +// return -1; +return 0; +} +//----------------------------------------------------------------------------- +int PLUGIN_RUNNER::Reload() +{ +int res = plugin->Reload(); +errorStr = plugin->GetStrError(); +return res; +} +//----------------------------------------------------------------------------- +bool PLUGIN_RUNNER::IsRunning() +{ +if (!isPluginLoaded) + return false; +return plugin->IsRunning(); +} +//----------------------------------------------------------------------------- +int PLUGIN_RUNNER::Load() +{ +if (!pluginFileName.size()) + { + errorStr = "Plugin loading failed. No plugin"; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +libHandle = dlopen(pluginFileName.c_str(), RTLD_NOW); + +if (!libHandle) + { + errorStr = string("Plugin loading failed. ") + dlerror(); + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +BASE_PLUGIN * (*GetPlugin)(); +GetPlugin = (BASE_PLUGIN * (*)())dlsym(libHandle, "GetPlugin"); +if (!GetPlugin) + { + errorStr = string("GetPlugin() not found. ") + dlerror(); + return -1; + } +plugin = GetPlugin(); +isPluginLoaded++; + +if (!plugin) + { + errorStr = "Plugin was not created!"; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +plugin->SetSettings(modSettings); +printfd(__FILE__, "Plugin %s parsesettings\n", plugin->GetVersion().c_str()); +if (plugin->ParseSettings()) + { + errorStr = "Plugin \'" + plugin->GetVersion() + "\' error: " + plugin->GetStrError(); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int PLUGIN_RUNNER::Unload() +{ +if (isPluginLoaded) + { + if (dlclose(libHandle)) + { + errorStr = dlerror(); + printfd(__FILE__, "Error unloading plugin '%s': '%s'", pluginFileName.c_str(), dlerror()); + return -1; + } + isPluginLoaded--; + } +return 0; +} +//----------------------------------------------------------------------------- +const string & PLUGIN_RUNNER::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +uint16_t PLUGIN_RUNNER::GetStartPosition() const +{ +return plugin->GetStartPosition(); +} +//----------------------------------------------------------------------------- +uint16_t PLUGIN_RUNNER::GetStopPosition() const +{ +return plugin->GetStopPosition(); +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugin_runner.h b/projects/stargazer/plugin_runner.h new file mode 100644 index 00000000..2889300d --- /dev/null +++ b/projects/stargazer/plugin_runner.h @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.13 $ + $Date: 2010/03/04 12:22:41 $ + $Author: faust $ + */ + +#ifndef PLUGIN_RUNNER_H +#define PLUGIN_RUNNER_H +#include +#include + +#include "base_plugin.h" +#include "base_settings.h" +#include "traffcounter.h" +#include "tariffs.h" +#include "admins.h" +#include "users.h" + +using namespace std; +//----------------------------------------------------------------------------- +class PLUGIN_RUNNER +{ +public: + PLUGIN_RUNNER(const string & pluginFileName, + const MODULE_SETTINGS & ms, + ADMINS * admins, + TARIFFS * tariffs, + USERS * users, + TRAFFCOUNTER * tc, + BASE_STORE * store, + const SETTINGS * s); + PLUGIN_RUNNER(const PLUGIN_RUNNER & rvalue); + ~PLUGIN_RUNNER(); + + PLUGIN_RUNNER & operator=(const PLUGIN_RUNNER & rvalue); + + int Start(); + int Stop(); + int Reload(); + int Restart(); + bool IsRunning(); + + const string & GetStrError() const; + BASE_PLUGIN * GetPlugin(); + const string & GetFileName() const { return pluginFileName; }; + + int Load(); + int Unload(); + + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + string pluginFileName; + string pluginSettingFileName; + + BASE_PLUGIN * plugin; + int isPluginLoaded; + string errorStr; + + void * libHandle; + bool isRunning; + + ADMINS * admins; + TARIFFS * tariffs; + USERS * users; + BASE_STORE * store; + TRAFFCOUNTER * traffCnt; + const SETTINGS * stgSettings; + MODULE_SETTINGS modSettings; +}; +//----------------------------------------------------------------------------- +#endif //PLUGIN_RUNNER_H + + diff --git a/projects/stargazer/plugins/Makefile b/projects/stargazer/plugins/Makefile new file mode 100644 index 00000000..21639574 --- /dev/null +++ b/projects/stargazer/plugins/Makefile @@ -0,0 +1,20 @@ +############################################################################### +# $Id: Makefile,v 1.1 2007/09/26 13:55:45 faust Exp $ +############################################################################### + +include ../../../Makefile.conf + +.PHONY: clean install uninstall +.PHONY: all $(PLUGINS) + +all: $(PLUGINS) + +$(PLUGINS): + $(MAKE) $(MAKECMDGOALS) -C $@ + +clean: all + +install: all + +uninstall: all + diff --git a/projects/stargazer/plugins/Makefile.in b/projects/stargazer/plugins/Makefile.in new file mode 100644 index 00000000..90411bd4 --- /dev/null +++ b/projects/stargazer/plugins/Makefile.in @@ -0,0 +1,43 @@ +############################################################################### +# $Id: Makefile.in,v 1.11 2009/03/03 15:49:35 faust Exp $ +############################################################################### + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +LN = ln + +CXXFLAGS += -fPIC +LDFLAGS += -shared -L$(DIR_LIB) -Wl,-rpath,$(PREFIX)/usr/lib/stg + +vpath %.so $(DIR_LIB) + +all: $(PROG) + +$(PROG): $(OBJS) $(STGLIBS) + $(CC) $^ $(LDFLAGS) $(LIBS) -o $(PROG) + $(LN) -fs "`pwd`/$(PROG)" $(DIR_MOD)/$(PROG) + +clean: + rm -f deps $(PROG) *.o tags *.*~ + +install: + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/lib/stg + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/lib/stg/$(PROG) + +uninstall: + rm -f $(PREFIX)/usr/lib/stg/$(PROG) + +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif + +deps: $(SRCS) ../../../../../Makefile.conf + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(DEFS) $(SEARCH_DIRS) -MM $$file` Makefile ../../../../../Makefile.conf" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done diff --git a/projects/stargazer/plugins/authorization/ao/Makefile b/projects/stargazer/plugins/authorization/ao/Makefile new file mode 100644 index 00000000..a47cea23 --- /dev/null +++ b/projects/stargazer/plugins/authorization/ao/Makefile @@ -0,0 +1,14 @@ +############################################################################### +# $Id: Makefile,v 1.10 2008/12/04 15:41:03 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_auth_ao.so + +SRCS = ./ao.cpp + +STGLIBS = -lstg_common + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/authorization/ao/ao.cpp b/projects/stargazer/plugins/authorization/ao/ao.cpp new file mode 100644 index 00000000..44631609 --- /dev/null +++ b/projects/stargazer/plugins/authorization/ao/ao.cpp @@ -0,0 +1,348 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* +$Revision: 1.30 $ +$Date: 2010/03/04 12:29:06 $ +$Author: faust $ +*/ + +#include +#include +#include + +#include "ao.h" +#include "../../../user.h" +#include "../../../eventloop.h" + +class AO_CREATOR +{ +private: + AUTH_AO * ao; + +public: + AO_CREATOR() + : ao(new AUTH_AO()) + { + }; + ~AO_CREATOR() + { + delete ao; + }; + + AUTH_AO * GetPlugin() + { + return ao; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +AO_CREATOR aoc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// ëÌÁÓÓ ÄÌÑ ÐÏÉÓËÁ ÀÚÅÒÁ × ÓÐÉÓËÅ ÎÏÔÉÆÉËÁÔÏÒÏ× +template +class IS_CONTAINS_USER: public binary_function +{ +public: + bool operator()(varType notifier, user_iter user) const + { + return notifier.GetUser() == user; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return aoc.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string AUTH_AO::GetVersion() const +{ +return "Always Online authorizator v.1.0"; +} +//----------------------------------------------------------------------------- +AUTH_AO::AUTH_AO() +{ +isRunning = false; +} +//----------------------------------------------------------------------------- +void AUTH_AO::SetUsers(USERS * u) +{ +users = u; +} +//----------------------------------------------------------------------------- +void AUTH_AO::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int AUTH_AO::ParseSettings() +{ +return 0; +} +//----------------------------------------------------------------------------- +const string & AUTH_AO::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int AUTH_AO::Start() +{ +GetUsers(); + +list::iterator users_iter; + +onAddUserNotifier.SetAuthorizator(this); +onDelUserNotifier.SetAuthorizator(this); +users->AddNotifierUserAdd(&onAddUserNotifier); +users->AddNotifierUserDel(&onDelUserNotifier); + +users_iter = usersList.begin(); +while (users_iter != usersList.end()) + { + UpdateUserAuthorization(*users_iter); + ++users_iter; + } +isRunning = true; +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_AO::Stop() +{ +if (!isRunning) + return 0; + +users->DelNotifierUserAdd(&onAddUserNotifier); +users->DelNotifierUserDel(&onDelUserNotifier); + +list::iterator users_iter; +users_iter = usersList.begin(); +while (users_iter != usersList.end()) + { + Unauthorize(*users_iter); + UnSetUserNotifiers(*users_iter); + ++users_iter; + } +isRunning = false; +return 0; +} +//----------------------------------------------------------------------------- +bool AUTH_AO::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +uint16_t AUTH_AO::GetStartPosition() const +{ +return 70; +} +//----------------------------------------------------------------------------- +uint16_t AUTH_AO::GetStopPosition() const +{ +return 70; +} +//----------------------------------------------------------------------------- +void AUTH_AO::SetUserNotifiers(user_iter u) +{ +// ---------- AlwaysOnline ------------------- +CHG_BEFORE_NOTIFIER BeforeChgAONotifier; +CHG_AFTER_NOTIFIER AfterChgAONotifier; + +BeforeChgAONotifier.SetAuthorizator(this); +BeforeChgAONotifier.SetUser(u); +BeforeChgAONotifierList.push_front(BeforeChgAONotifier); + +AfterChgAONotifier.SetAuthorizator(this); +AfterChgAONotifier.SetUser(u); +AfterChgAONotifierList.push_front(AfterChgAONotifier); + +u->property.alwaysOnline.AddBeforeNotifier(&(*BeforeChgAONotifierList.begin())); +u->property.alwaysOnline.AddAfterNotifier(&(*AfterChgAONotifierList.begin())); +// ---------- AlwaysOnline end --------------- + +// ---------- IP ------------------- +CHG_BEFORE_NOTIFIER BeforeChgIPNotifier; +CHG_AFTER_NOTIFIER AfterChgIPNotifier; + +BeforeChgIPNotifier.SetAuthorizator(this); +BeforeChgIPNotifier.SetUser(u); +BeforeChgIPNotifierList.push_front(BeforeChgIPNotifier); + +AfterChgIPNotifier.SetAuthorizator(this); +AfterChgIPNotifier.SetUser(u); +AfterChgIPNotifierList.push_front(AfterChgIPNotifier); + +u->property.ips.AddBeforeNotifier(&(*BeforeChgIPNotifierList.begin())); +u->property.ips.AddAfterNotifier(&(*AfterChgIPNotifierList.begin())); +// ---------- IP end --------------- +} +//----------------------------------------------------------------------------- +void AUTH_AO::UnSetUserNotifiers(user_iter u) +{ +// --- AlwaysOnline --- +IS_CONTAINS_USER > IsContainsUserAOB; +IS_CONTAINS_USER > IsContainsUserAOA; + +list >::iterator aoBIter; +list >::iterator aoAIter; + +aoBIter = find_if(BeforeChgAONotifierList.begin(), + BeforeChgAONotifierList.end(), + bind2nd(IsContainsUserAOB, u)); + +if (aoBIter != BeforeChgAONotifierList.end()) + { + aoBIter->GetUser()->property.alwaysOnline.DelBeforeNotifier(&(*aoBIter)); + BeforeChgAONotifierList.erase(aoBIter); + } + +aoAIter = find_if(AfterChgAONotifierList.begin(), + AfterChgAONotifierList.end(), + bind2nd(IsContainsUserAOA, u)); + +if (aoAIter != AfterChgAONotifierList.end()) + { + aoAIter->GetUser()->property.alwaysOnline.DelAfterNotifier(&(*aoAIter)); + AfterChgAONotifierList.erase(aoAIter); + } +// --- AlwaysOnline end --- + +// --- IP --- +IS_CONTAINS_USER > IsContainsUserIPB; +IS_CONTAINS_USER > IsContainsUserIPA; + +list >::iterator ipBIter; +list >::iterator ipAIter; + +ipBIter = find_if(BeforeChgIPNotifierList.begin(), + BeforeChgIPNotifierList.end(), + bind2nd(IsContainsUserIPB, u)); + +if (ipBIter != BeforeChgIPNotifierList.end()) + { + ipBIter->GetUser()->property.ips.DelBeforeNotifier(&(*ipBIter)); + BeforeChgIPNotifierList.erase(ipBIter); + } + +ipAIter = find_if(AfterChgIPNotifierList.begin(), + AfterChgIPNotifierList.end(), + bind2nd(IsContainsUserIPA, u)); + +if (ipAIter != AfterChgIPNotifierList.end()) + { + ipAIter->GetUser()->property.ips.DelAfterNotifier(&(*ipAIter)); + AfterChgIPNotifierList.erase(ipAIter); + } +// --- IP end --- +} +//----------------------------------------------------------------------------- +void AUTH_AO::GetUsers() +{ +user_iter u; +int h = users->OpenSearch(); +if (!h) + { + printfd(__FILE__, "users->OpenSearch() error\n"); + return; + } + +while (1) + { + if (users->SearchNext(h, &u)) + { + break; + } + usersList.push_back(u); + SetUserNotifiers(u); + } + +users->CloseSearch(h); +} +//----------------------------------------------------------------------------- +void AUTH_AO::Unauthorize(user_iter u) const +{ +u->Unauthorize(this); +} +//----------------------------------------------------------------------------- +void AUTH_AO::UpdateUserAuthorization(user_iter u) const +{ +if (u->property.alwaysOnline) + { + USER_IPS ips = u->property.ips; + if (ips.OnlyOneIP()) + { + if (u->Authorize(ips[0].ip, "", 0xFFffFFff, this) == 0) + { + } + } + } +} +//----------------------------------------------------------------------------- +void AUTH_AO::AddUser(user_iter u) +{ +SetUserNotifiers(u); +usersList.push_back(u); +UpdateUserAuthorization(u); +} +//----------------------------------------------------------------------------- +void AUTH_AO::DelUser(user_iter u) +{ +Unauthorize(u); +UnSetUserNotifiers(u); + +list::iterator users_iter; +users_iter = usersList.begin(); + +while (users_iter != usersList.end()) + { + if (u == *users_iter) + { + usersList.erase(users_iter); + break; + } + ++users_iter; + } +} +//----------------------------------------------------------------------------- +int AUTH_AO::SendMessage(const STG_MSG &, uint32_t) const +{ +errorStr = "Authorization modele \'AlwaysOnline\' does not support sending messages"; +return -1; +} +//----------------------------------------------------------------------------- +template +void CHG_BEFORE_NOTIFIER::Notify(const varParamType &, const varParamType &) +{ +EVENT_LOOP_SINGLETON::GetInstance().Enqueue(*auth, &AUTH_AO::Unauthorize, user); +} +//----------------------------------------------------------------------------- +template +void CHG_AFTER_NOTIFIER::Notify(const varParamType &, const varParamType &) +{ +EVENT_LOOP_SINGLETON::GetInstance().Enqueue(*auth, &AUTH_AO::UpdateUserAuthorization, user); +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/authorization/ao/ao.h b/projects/stargazer/plugins/authorization/ao/ao.h new file mode 100644 index 00000000..f2912d14 --- /dev/null +++ b/projects/stargazer/plugins/authorization/ao/ao.h @@ -0,0 +1,173 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.21 $ + $Date: 2010/09/10 06:38:26 $ + $Author: faust $ +*/ + +#ifndef AO_H +#define AO_H + +#include +#include +#include "base_auth.h" +#include "base_store.h" +#include "notifer.h" +#include "user_ips.h" +#include "../../../users.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +class AUTH_AO; +//----------------------------------------------------------------------------- +template +class CHG_BEFORE_NOTIFIER: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const varParamType & oldValue, const varParamType & newValue); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() {return user; } + void SetAuthorizator(const AUTH_AO * a) { auth = a; } + +private: + user_iter user; + const AUTH_AO * auth; +}; +//----------------------------------------------------------------------------- +template +class CHG_AFTER_NOTIFIER: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const varParamType & oldValue, const varParamType & newValue); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() {return user; } + void SetAuthorizator(const AUTH_AO * a) { auth = a; } + +private: + user_iter user; + const AUTH_AO * auth; +}; +//----------------------------------------------------------------------------- +class AUTH_AO_SETTINGS +{ +public: + const string& GetStrError() const { static string s; return s; } + int ParseSettings(const MODULE_SETTINGS &) { return 0; } +}; +//----------------------------------------------------------------------------- +class AUTH_AO :public BASE_AUTH +{ +public: + AUTH_AO(); + virtual ~AUTH_AO(){}; + + void SetUsers(USERS * u); + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER *){}; + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS *){}; + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + + void AddUser(user_iter u); + void DelUser(user_iter u); + + void UpdateUserAuthorization(user_iter u) const; + void Unauthorize(user_iter u) const; + + int SendMessage(const STG_MSG & msg, uint32_t ip) const; + +private: + void GetUsers(); + void SetUserNotifiers(user_iter u); + void UnSetUserNotifiers(user_iter u); + + mutable string errorStr; + AUTH_AO_SETTINGS aoSettings; + USERS * users; + list usersList; + bool isRunning; + MODULE_SETTINGS settings; + + /* + ÍÙ ÄÏÌÖÎÙ ÐÅÒÅÐÒÏ×ÅÒÉÔØ ×ÏÚÍÏÖÎÏÓÔØ Á×ÔÏÒÉÚÁÃÉÉ ÀÚÅÒÁ ÐÒÉ ÉÚÍÅÎÅÎÉÉ + ÓÌÅÄÕÀÝÉÈ ÅÇÏ ÐÁÒÁÍÅÔÒÏ×: + - alwaysOnline + - ips + */ + + list > BeforeChgAONotifierList; + list > AfterChgAONotifierList; + + list > BeforeChgIPNotifierList; + list > AfterChgIPNotifierList; + + class ADD_USER_NONIFIER: public NOTIFIER_BASE + { + public: + ADD_USER_NONIFIER(){}; + virtual ~ADD_USER_NONIFIER(){}; + + void SetAuthorizator(AUTH_AO * a) { auth = a; } + void Notify(const user_iter & user) + { + auth->AddUser(user); + } + + private: + AUTH_AO * auth; + } onAddUserNotifier; + + class DEL_USER_NONIFIER: public NOTIFIER_BASE + { + public: + DEL_USER_NONIFIER(){}; + virtual ~DEL_USER_NONIFIER(){}; + + void SetAuthorizator(AUTH_AO * a) { auth = a; } + void Notify(const user_iter & user) + { + auth->DelUser(user); + } + + private: + AUTH_AO * auth; + } onDelUserNotifier; + +}; +//----------------------------------------------------------------------------- + +#endif + + diff --git a/projects/stargazer/plugins/authorization/inetaccess/Makefile b/projects/stargazer/plugins/authorization/inetaccess/Makefile new file mode 100644 index 00000000..62997715 --- /dev/null +++ b/projects/stargazer/plugins/authorization/inetaccess/Makefile @@ -0,0 +1,16 @@ +############################################################################### +# $Id: Makefile,v 1.10 2010/02/16 11:41:00 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +LIBS += $(LIB_THREAD) + +PROG = mod_auth_ia.so + +SRCS = ./inetaccess.cpp + +STGLIBS = -lstg_common -lstg_crypto + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/authorization/inetaccess/antiflood.cpp b/projects/stargazer/plugins/authorization/inetaccess/antiflood.cpp new file mode 100644 index 00000000..86790e0f --- /dev/null +++ b/projects/stargazer/plugins/authorization/inetaccess/antiflood.cpp @@ -0,0 +1,190 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + +#include + +#include "antiflood.h" +#include "common.h" + +//----------------------------------------------------------------------------- +int FloodIPCompare(void * p1, void * p2) +{ +/* +óÒÁ×ÎÉ×ÁÅÍ Ä×Á éð × ÓÔÒÕËÔÒÕ Ó ÁÎÔÉÆÌÕÄÏÍ + * */ + +FLOOD_NODE * n1, *n2; + +n1 = (FLOOD_NODE *)p1; +n2 = (FLOOD_NODE *)p2; +printfd(__FILE__, "FloodIPCompare %X %X\n", n1->ip, n2->ip); +return n1->ip - n2->ip; +} +//----------------------------------------------------------------------------- +ANTIFLOOD::ANTIFLOOD(): +floodTree(FloodIPCompare) +{ +/* +óÒÅÄÎÅÅ ÎÁÉÍÅÎØÛÅÅ ÒÁÚÒÅÛÅÎÎÏÅ ×ÒÅÍÑ ÍÅÖÄÕ ÐÁËÅÔÁÍÉ. ÍÓ. + * */ + +avrgTime = 500; +} +//----------------------------------------------------------------------------- +bool ANTIFLOOD::AllowIP(uint32_t ip, bool * logged) +{ +/* +ðÒÏ×ÅÒËÁ ÔÏÇÏ ÎÅ ÓÌÉÛËÏÍ ÌÉ ÞÁÓÔÏ ÐÒÉÈÏÄÑÔ ÐÁËÅÔÙ Ó ÕËÁÚÁÎÎÏÇÏ ÁÊÐÉÛÎÉËÁ + * */ + +BSPNODE * findNode; +FLOOD_NODE floodNode; + +floodNode.ip = ip; +findNode = floodTree.Find(&floodNode); + +gettimeofday(&tv, NULL); +currentTime = ((uint64_t)tv.tv_sec)*1000 + ((uint64_t)tv.tv_usec)/1000; + +if (findNode == NULL) + { + AddNode(ip); + printfd(__FILE__, "AddNode(%X)\n", ip); + return true; + } +else + { + printfd(__FILE__, "UpdateNodeTime(%X)\n", findNode->record); + UpdateNodeTime((FLOOD_NODE*)(findNode->record)); + } + +if (currentTime - CalcAvrgNodeTime((FLOOD_NODE*)findNode->record) < avrgTime) + { + if (((FLOOD_NODE*)findNode->record)->logged == false) + { + *logged = false; + ((FLOOD_NODE*)findNode->record)->logged = true; + //floodNode.logged = true; + } + else + { + *logged = true; + } + return false; + } + +((FLOOD_NODE*)findNode->record)->logged = false; +return true; +} +//----------------------------------------------------------------------------- +void ANTIFLOOD::UpdateNodeTime(FLOOD_NODE * node) +{ +/* +ïÂÎÏ×ÌÑÅÍ ×ÒÅÍÑ ÐÏÓÌÅÄÎÅÇÏ ÐÒÅÛÅÄÛÅÇÏ ÐÁËÅÔÁ + * */ + +node->timeIP[node->pos] = currentTime; + +node->pos++; +if (node->pos >= FLOOD_LBL_MAX) + node->pos = 0; + +} +//----------------------------------------------------------------------------- +void ANTIFLOOD::Clean() +{ +/* +þÍÓÔËÁ ÄÅÒÅ×Á ÓÏ ÓÌÉÛËÏÍ ÓÔÁÒÙÍÉ ÄÁÎÎÙÍÉ + * */ + +BSPNODE * bspNode = floodTree.Max(floodTree.GetRoot()); +FLOOD_NODE * n; +BSPNODE * delNode; +currentTime = ((uint64_t)tv.tv_sec)*1000 + ((uint64_t)tv.tv_usec)/1000; + +while (bspNode) + { + n = (FLOOD_NODE*)bspNode->record; + if (currentTime - CalcAvrgNodeTime(n) > 2 * 60) + { + delNode = floodTree.Delete(bspNode); + delete (FLOOD_NODE*)delNode->record; + delete delNode; + } + bspNode = floodTree.Max(floodTree.GetRoot()); + } + +printfd(__FILE__, "after clean max=%X\n", floodTree.Max(floodTree.GetRoot())); +printfd(__FILE__, "after clean min=%X\n", floodTree.Min(floodTree.GetRoot())); + +} +//----------------------------------------------------------------------------- +void ANTIFLOOD::AddNode(uint32_t ip) +{ +/* +äÏÂÁ×ÌÑÅÍ ÎÏ×ÙÊ éð × ÄÅÒÅ×Ï + * */ + +FLOOD_NODE * fn; +BSPNODE * node; + +fn = new FLOOD_NODE; + +fn->pos = 0; +fn->ip = ip; +fn->logged = false; + +memset(fn->timeIP, 0, sizeof(uint64_t) * FLOOD_LBL_MAX); +fn->timeIP[0] = currentTime; + +node = new BSPNODE; +node->record = fn; + +floodTree.Add(node); +} +//----------------------------------------------------------------------------- +uint64_t ANTIFLOOD::CalcAvrgNodeTime(FLOOD_NODE * fn) +{ +/* +÷ÙÞÉÓÌÑÅÍ ÓÒÅÄÎÅÅ ×ÒÅÍÑ ÐÏÓÌÅÄÎÉÈ ÐÒÅÛÅÄÛÉÈ ÐÁËÅÔÏ× + * */ + +uint64_t t = 0; +for (int i = 0; i < FLOOD_LBL_MAX; i++) + t += fn->timeIP[i]; + +printfd(__FILE__, "node time %lld\n", t/FLOOD_LBL_MAX); +printfd(__FILE__, "current time %lld\n", currentTime); + +return t/FLOOD_LBL_MAX; +} +//----------------------------------------------------------------------------- +void ANTIFLOOD::SetAvrgTime(uint64_t t) +{ +avrgTime = t; +} +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/plugins/authorization/inetaccess/antiflood.h b/projects/stargazer/plugins/authorization/inetaccess/antiflood.h new file mode 100644 index 00000000..2c0f5965 --- /dev/null +++ b/projects/stargazer/plugins/authorization/inetaccess/antiflood.h @@ -0,0 +1,71 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + +#ifndef ANTIFLOOD_H +#define ANTIFLOOD_H + + +#include + +#include "bsp.h" +#include "os_int.h" + +#define FLOOD_LBL_MAX (10) + +//----------------------------------------------------------------------------- +struct FLOOD_NODE +{ +uint32_t ip; +uint64_t timeIP[FLOOD_LBL_MAX]; +int pos; +bool logged; +}; + +//----------------------------------------------------------------------------- +class ANTIFLOOD +{ +public: + ANTIFLOOD(); + bool AllowIP(uint32_t ip, bool * logged); + void Clean(); + void SetAvrgTime(uint64_t); + +private: + uint64_t CalcAvrgNodeTime(FLOOD_NODE * fn); + void AddNode(uint32_t ip); + void UpdateNodeTime(FLOOD_NODE * node); + + TREE floodTree; + struct timeval tv; + uint64_t avrgTime; + uint64_t currentTime; +}; +//----------------------------------------------------------------------------- + +#endif + + + + diff --git a/projects/stargazer/plugins/authorization/inetaccess/inetaccess.cpp b/projects/stargazer/plugins/authorization/inetaccess/inetaccess.cpp new file mode 100644 index 00000000..0d2dbe21 --- /dev/null +++ b/projects/stargazer/plugins/authorization/inetaccess/inetaccess.cpp @@ -0,0 +1,1840 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.79 $ + $Date: 2010/03/25 15:18:48 $ + $Author: faust $ + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + +#include "inetaccess.h" +#include "common.h" + +extern volatile const time_t stgTime; + +//----------------------------------------------------------------------------- +class IA_CREATOR +{ +private: + AUTH_IA * ia; + +public: + IA_CREATOR() + : ia(new AUTH_IA()) + { + }; + ~IA_CREATOR() + { + delete ia; + }; + + AUTH_IA * GetPlugin() + { + return ia; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +IA_CREATOR iac; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return iac.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +AUTH_IA_SETTINGS::AUTH_IA_SETTINGS() + : userDelay(0), + userTimeout(0), + port(0), + freeMbShowType(freeMbCash) +{ +} +//----------------------------------------------------------------------------- +int AUTH_IA_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +int p; +PARAM_VALUE pv; +vector::const_iterator pvi; +/////////////////////////// +pv.param = "Port"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Port\' not found."; + printfd(__FILE__, "Parameter 'Port' not found\n"); + return -1; + } +if (ParseIntInRange(pvi->value[0], 2, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'Port'\n"); + return -1; + } +port = p; +/////////////////////////// +pv.param = "UserDelay"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'UserDelay\' not found."; + printfd(__FILE__, "Parameter 'UserDelay' not found\n"); + return -1; + } + +if (ParseIntInRange(pvi->value[0], 5, 600, &userDelay)) + { + errorStr = "Cannot parse parameter \'UserDelay\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'UserDelay'\n"); + return -1; + } +/////////////////////////// +pv.param = "UserTimeout"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'UserTimeout\' not found."; + printfd(__FILE__, "Parameter 'UserTimeout' not found\n"); + return -1; + } + +if (ParseIntInRange(pvi->value[0], 15, 1200, &userTimeout)) + { + errorStr = "Cannot parse parameter \'UserTimeout\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'UserTimeout'\n"); + return -1; + } +///////////////////////////////////////////////////////////// +string freeMbType; +int n = 0; +pv.param = "FreeMb"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'FreeMb\' not found."; + printfd(__FILE__, "Parameter 'FreeMb' not found\n"); + return -1; + } +freeMbType = pvi->value[0]; + +if (strcasecmp(freeMbType.c_str(), "cash") == 0) + { + freeMbShowType = freeMbCash; + } +else if (strcasecmp(freeMbType.c_str(), "none") == 0) + { + freeMbShowType = freeMbNone; + } +else if (!str2x(freeMbType.c_str(), n)) + { + if (n < 0 || n >= DIR_NUM) + { + errorStr = "Incorrect parameter \'" + freeMbType + "\'."; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + freeMbShowType = (FREEMB)(freeMb0 + n); + } +else + { + errorStr = "Incorrect parameter \'" + freeMbType + "\'."; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +///////////////////////////////////////////////////////////// +return 0; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#ifdef IA_PHASE_DEBUG +IA_PHASE::IA_PHASE() + : phase(1), + flog(NULL) +{ +gettimeofday(&phaseTime, NULL); +} +#else +IA_PHASE::IA_PHASE() + : phase(1) +{ +gettimeofday(&phaseTime, NULL); +} +#endif +//----------------------------------------------------------------------------- +IA_PHASE::~IA_PHASE() +{ +#ifdef IA_PHASE_DEBUG +flog = fopen(log.c_str(), "at"); +if (flog) + { + fprintf(flog, "IA %s D\n", login.c_str()); + fclose(flog); + } +#endif +} +//----------------------------------------------------------------------------- +#ifdef IA_PHASE_DEBUG +void IA_PHASE::SetLogFileName(const string & logFileName) +{ +log = logFileName + ".ia.log"; +} +//----------------------------------------------------------------------------- +void IA_PHASE::SetUserLogin(const string & login) +{ +IA_PHASE::login = login; +} +//----------------------------------------------------------------------------- +void IA_PHASE::WritePhaseChange(int newPhase) +{ +UTIME newPhaseTime; +gettimeofday(&newPhaseTime, NULL); +flog = fopen(log.c_str(), "at"); +/*int64_t tn = newPhaseTime.GetSec()*1000000 + newPhaseTime.GetUSec(); +int64_t to = phaseTime.GetSec()*1000000 + phaseTime.GetUSec();*/ +if (flog) + { + string action = newPhase == phase ? "U" : "C"; + double delta = newPhaseTime.GetSec() - phaseTime.GetSec(); + delta += (newPhaseTime.GetUSec() - phaseTime.GetUSec()) * 1.0e-6; + fprintf(flog, "IA %s %s oldPhase = %d, newPhase = %d. dt = %.6f\n", + login.c_str(), + action.c_str(), + phase, + newPhase, + delta); + fclose(flog); + } +} +#endif +//----------------------------------------------------------------------------- +void IA_PHASE::SetPhase1() +{ +#ifdef IA_PHASE_DEBUG +WritePhaseChange(1); +#endif +phase = 1; +gettimeofday(&phaseTime, NULL); +} +//----------------------------------------------------------------------------- +void IA_PHASE::SetPhase2() +{ +#ifdef IA_PHASE_DEBUG +WritePhaseChange(2); +#endif +phase = 2; +gettimeofday(&phaseTime, NULL); +} +//----------------------------------------------------------------------------- +void IA_PHASE::SetPhase3() +{ +#ifdef IA_PHASE_DEBUG +WritePhaseChange(3); +#endif +phase = 3; +gettimeofday(&phaseTime, NULL); +} +//----------------------------------------------------------------------------- +void IA_PHASE::SetPhase4() +{ +#ifdef IA_PHASE_DEBUG +WritePhaseChange(4); +#endif +phase = 4; +gettimeofday(&phaseTime, NULL); +} +//----------------------------------------------------------------------------- +void IA_PHASE::SetPhase5() +{ +#ifdef IA_PHASE_DEBUG +WritePhaseChange(5); +#endif +phase = 5; +gettimeofday(&phaseTime, NULL); +} +//----------------------------------------------------------------------------- +int IA_PHASE::GetPhase() const +{ +return phase; +} +//----------------------------------------------------------------------------- +void IA_PHASE::UpdateTime() +{ +#ifdef IA_PHASE_DEBUG +WritePhaseChange(phase); +#endif +gettimeofday(&phaseTime, NULL); +} +//----------------------------------------------------------------------------- +const UTIME & IA_PHASE::GetTime() const +{ +return phaseTime; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +AUTH_IA::AUTH_IA() + : nonstop(false), + isRunningRun(false), + isRunningRunTimeouter(false), + WriteServLog(GetStgLogger()), + enabledDirs(0xFFffFFff), + onDelUserNotifier(*this) +{ +InitEncrypt(&ctxS, "pr7Hhen"); + +pthread_mutexattr_t attr; +pthread_mutexattr_init(&attr); +pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +pthread_mutex_init(&mutex, &attr); + +memset(&connSynAck6, 0, sizeof(CONN_SYN_ACK_6)); +memset(&connSynAck8, 0, sizeof(CONN_SYN_ACK_8)); +memset(&disconnSynAck6, 0, sizeof(DISCONN_SYN_ACK_6)); +memset(&disconnSynAck8, 0, sizeof(DISCONN_SYN_ACK_8)); +memset(&aliveSyn6, 0, sizeof(ALIVE_SYN_6)); +memset(&aliveSyn8, 0, sizeof(ALIVE_SYN_8)); +memset(&fin6, 0, sizeof(FIN_6)); +memset(&fin8, 0, sizeof(FIN_8)); + +printfd(__FILE__, "sizeof(CONN_SYN_6) = %d %d\n", sizeof(CONN_SYN_6), Min8(sizeof(CONN_SYN_6))); +printfd(__FILE__, "sizeof(CONN_SYN_8) = %d %d\n", sizeof(CONN_SYN_8), Min8(sizeof(CONN_SYN_8))); +printfd(__FILE__, "sizeof(CONN_SYN_ACK_6) = %d %d\n", sizeof(CONN_SYN_ACK_6), Min8(sizeof(CONN_SYN_ACK_6))); +printfd(__FILE__, "sizeof(CONN_SYN_ACK_8) = %d %d\n", sizeof(CONN_SYN_ACK_8), Min8(sizeof(CONN_SYN_ACK_8))); +printfd(__FILE__, "sizeof(CONN_ACK_6) = %d %d\n", sizeof(CONN_ACK_6), Min8(sizeof(CONN_ACK_6))); +printfd(__FILE__, "sizeof(ALIVE_SYN_6) = %d %d\n", sizeof(ALIVE_SYN_6), Min8(sizeof(ALIVE_SYN_6))); +printfd(__FILE__, "sizeof(ALIVE_SYN_8) = %d %d\n", sizeof(ALIVE_SYN_8), Min8(sizeof(ALIVE_SYN_8))); +printfd(__FILE__, "sizeof(ALIVE_ACK_6) = %d %d\n", sizeof(ALIVE_ACK_6), Min8(sizeof(ALIVE_ACK_6))); +printfd(__FILE__, "sizeof(DISCONN_SYN_6) = %d %d\n", sizeof(DISCONN_SYN_6), Min8(sizeof(DISCONN_SYN_6))); +printfd(__FILE__, "sizeof(DISCONN_SYN_ACK_6) = %d %d\n", sizeof(DISCONN_SYN_ACK_6), Min8(sizeof(DISCONN_SYN_ACK_6))); +printfd(__FILE__, "sizeof(DISCONN_SYN_ACK_8) = %d %d\n", sizeof(DISCONN_SYN_ACK_8), Min8(sizeof(DISCONN_SYN_ACK_8))); +printfd(__FILE__, "sizeof(DISCONN_ACK_6) = %d %d\n", sizeof(DISCONN_ACK_6), Min8(sizeof(DISCONN_ACK_6))); +printfd(__FILE__, "sizeof(FIN_6) = %d %d\n", sizeof(FIN_6), Min8(sizeof(FIN_6))); +printfd(__FILE__, "sizeof(FIN_8) = %d %d\n", sizeof(FIN_8), Min8(sizeof(FIN_8))); +printfd(__FILE__, "sizeof(ERR) = %d %d\n", sizeof(ERR), Min8(sizeof(ERR))); +printfd(__FILE__, "sizeof(INFO_6) = %d %d\n", sizeof(INFO_6), Min8(sizeof(INFO_6))); +printfd(__FILE__, "sizeof(INFO_7) = %d %d\n", sizeof(INFO_7), Min8(sizeof(INFO_7))); +printfd(__FILE__, "sizeof(INFO_8) = %d %d\n", sizeof(INFO_8), Min8(sizeof(INFO_8))); + +packetTypes["CONN_SYN"] = CONN_SYN_N; +packetTypes["CONN_SYN_ACK"] = CONN_SYN_ACK_N; +packetTypes["CONN_ACK"] = CONN_ACK_N; +packetTypes["ALIVE_SYN"] = ALIVE_SYN_N; +packetTypes["ALIVE_ACK"] = ALIVE_ACK_N; +packetTypes["DISCONN_SYN"] = DISCONN_SYN_N; +packetTypes["DISCONN_SYN_ACK"] = DISCONN_SYN_ACK_N; +packetTypes["DISCONN_ACK"] = DISCONN_ACK_N; +packetTypes["FIN"] = FIN_N; +packetTypes["ERR"] = ERROR_N; +} +//----------------------------------------------------------------------------- +AUTH_IA::~AUTH_IA() +{ +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Start() +{ +users->AddNotifierUserDel(&onDelUserNotifier); +nonstop = true; + +if (PrepareNet()) + { + return -1; + } + +if (!isRunningRun) + { + if (pthread_create(&recvThread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + printfd(__FILE__, "Cannot create recv thread\n"); + return -1; + } + } + +if (!isRunningRunTimeouter) + { + if (pthread_create(&timeouterThread, NULL, RunTimeouter, this)) + { + errorStr = "Cannot create thread."; + printfd(__FILE__, "Cannot create timeouter thread\n"); + return -1; + } + } +errorStr = ""; +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Stop() +{ +if (!IsRunning()) + return 0; + +nonstop = false; + +std::for_each( + ip2user.begin(), + ip2user.end(), + UnauthorizeUser(this) + ); + +if (isRunningRun) + { + //5 seconds to thread stops itself + for (int i = 0; i < 25 && isRunningRun; i++) + { + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (isRunningRun) + { + //TODO pthread_cancel() + if (pthread_kill(recvThread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return -1; + } + for (int i = 0; i < 25 && isRunningRun; ++i) + usleep(200000); + if (isRunningRun) + { + printfd(__FILE__, "Failed to stop recv thread\n"); + } + else + { + pthread_join(recvThread, NULL); + } + printfd(__FILE__, "AUTH_IA killed Run\n"); + } + } + +FinalizeNet(); + +if (isRunningRunTimeouter) + { + //5 seconds to thread stops itself + for (int i = 0; i < 25 && isRunningRunTimeouter; i++) + { + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (isRunningRunTimeouter) + { + //TODO pthread_cancel() + if (pthread_kill(timeouterThread, SIGINT)) + { + errorStr = "Cannot kill thread."; + return -1; + } + for (int i = 0; i < 25 && isRunningRunTimeouter; ++i) + usleep(200000); + if (isRunningRunTimeouter) + { + printfd(__FILE__, "Failed to stop timeouter thread\n"); + } + else + { + pthread_join(timeouterThread, NULL); + } + printfd(__FILE__, "AUTH_IA killed Timeouter\n"); + } + } +printfd(__FILE__, "AUTH_IA::Stoped successfully.\n"); +users->DelNotifierUserDel(&onDelUserNotifier); +return 0; +} +//----------------------------------------------------------------------------- +void * AUTH_IA::Run(void * d) +{ +AUTH_IA * ia = static_cast(d); + +ia->isRunningRun = true; + +char buffer[512]; + +time_t touchTime = stgTime - MONITOR_TIME_DELAY_SEC; + +while (ia->nonstop) + { + ia->RecvData(buffer, sizeof(buffer)); + if ((touchTime + MONITOR_TIME_DELAY_SEC <= stgTime) && ia->stgSettings->GetMonitoring()) + { + touchTime = stgTime; + string monFile = ia->stgSettings->GetMonitorDir() + "/inetaccess_r"; + TouchFile(monFile.c_str()); + } + } + +ia->isRunningRun = false; +return NULL; +} +//----------------------------------------------------------------------------- +void * AUTH_IA::RunTimeouter(void * d) +{ +AUTH_IA * ia = static_cast(d); + +ia->isRunningRunTimeouter = true; + +int a = -1; +string monFile = ia->stgSettings->GetMonitorDir() + "/inetaccess_t"; +while (ia->nonstop) + { + usleep(20000); + ia->Timeouter(); + // TODO cahange counter to timer and MONITOR_TIME_DELAY_SEC + if (++a % (50*60) == 0 && ia->stgSettings->GetMonitoring()) + { + TouchFile(monFile.c_str()); + } + } + +ia->isRunningRunTimeouter = false; +return NULL; +} +//----------------------------------------------------------------------------- +int AUTH_IA::ParseSettings() +{ +int ret = iaSettings.ParseSettings(settings); +if (ret) + errorStr = iaSettings.GetStrError(); +return ret; +} +//----------------------------------------------------------------------------- +int AUTH_IA::PrepareNet() +{ +struct sockaddr_in listenAddr; + +listenSocket = socket(AF_INET, SOCK_DGRAM, 0); + +if (listenSocket < 0) + { + errorStr = "Cannot create socket."; + return -1; + } + +listenAddr.sin_family = AF_INET; +listenAddr.sin_port = htons(iaSettings.GetUserPort()); +listenAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); + +if (bind(listenSocket, (struct sockaddr*)&listenAddr, sizeof(listenAddr)) < 0) + { + errorStr = "AUTH_IA: Bind failed."; + return -1; + } + +/*int buffLen; +if (getsockopt(listenSocket, SOL_SOCKET, SO_SNDBUF, &buffLen, sizeof(buffLen)) < 0) + { + + errorStr = "Getsockopt failed. " + string(strerror(errno)); + return -1; + }*/ + +//WriteServLog("buffLen = %d", buffLen); + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::FinalizeNet() +{ +close(listenSocket); +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::RecvData(char * buffer, int bufferSize) +{ +if (!WaitPackets(listenSocket)) // Timeout + { + return 0; + } + +struct sockaddr_in outerAddr; +socklen_t outerAddrLen(sizeof(outerAddr)); +int dataLen = recvfrom(listenSocket, buffer, bufferSize, 0, (struct sockaddr *)&outerAddr, &outerAddrLen); + +if (!dataLen) // EOF + { + return 0; + } + +if (dataLen <= 0) // Error + { + if (errno != EINTR) + { + printfd(__FILE__, "recvfrom res=%d, error: '%s'\n", dataLen, strerror(errno)); + return -1; + } + return 0; + } + +if (dataLen > 256) + return -1; + +int protoVer; +if (CheckHeader(buffer, &protoVer)) + return -1; + +char login[PASSWD_LEN]; //TODO why PASSWD_LEN ? +memset(login, 0, PASSWD_LEN); + +Decrypt(&ctxS, login, buffer + 8, PASSWD_LEN / 8); + +uint32_t sip = *((uint32_t*)&outerAddr.sin_addr); +uint16_t sport = htons(outerAddr.sin_port); + +user_iter user; +if (users->FindByName(login, &user) == 0) + { + printfd(__FILE__, "User %s FOUND!\n", user->GetLogin().c_str()); + PacketProcessor(buffer, dataLen, sip, sport, protoVer, &user); + } +else + { + WriteServLog("User\'s connect failed:: user \'%s\' not found. IP \'%s\'", + login, + inet_ntostring(sip).c_str()); + printfd(__FILE__, "User %s NOT found!\n", login); + SendError(sip, sport, protoVer, "îÅÐÒÁ×ÉÌØÎÙÊ ÌÏÇÉÎ ÉÌÉ ÐÁÒÏÌØ!"); + } + +return 0; + +} +//----------------------------------------------------------------------------- +int AUTH_IA::CheckHeader(const char * buffer, int * protoVer) +{ +if (strncmp(IA_ID, buffer, strlen(IA_ID)) != 0) + { + //SendError(userIP, updateMsg); + printfd(__FILE__, "update needed - IA_ID\n"); + //SendError(userIP, "Incorrect header!"); + return -1; + } + +if (buffer[6] != 0) //proto[0] shoud be 0 + { + printfd(__FILE__, "update needed - PROTO major: %d\n", buffer[6]); + //SendError(userIP, updateMsg); + return -1; + } + +if (buffer[7] < 6) + { + // need update + //SendError(userIP, updateMsg); + printfd(__FILE__, "update needed - PROTO minor: %d\n", buffer[7]); + return -1; + } +else + { + *protoVer = buffer[7]; + } +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Timeouter() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +map::iterator it; +it = ip2user.begin(); +uint32_t sip; + +while (it != ip2user.end()) + { + sip = it->first; + + static UTIME currTime; + gettimeofday(&currTime, NULL); + + if ((it->second.phase.GetPhase() == 2) + && (currTime - it->second.phase.GetTime()) > iaSettings.GetUserDelay()) + { + it->second.phase.SetPhase1(); + printfd(__FILE__, "Phase changed from 2 to 1. Reason: timeout\n"); + } + + if (it->second.phase.GetPhase() == 3) + { + if (!it->second.messagesToSend.empty()) + { + if (it->second.protoVer == 6) + RealSendMessage6(*it->second.messagesToSend.begin(), sip, it->second); + + if (it->second.protoVer == 7) + RealSendMessage7(*it->second.messagesToSend.begin(), sip, it->second); + + if (it->second.protoVer == 8) + RealSendMessage8(*it->second.messagesToSend.begin(), sip, it->second); + + it->second.messagesToSend.erase(it->second.messagesToSend.begin()); + } + + if((currTime - it->second.lastSendAlive) > iaSettings.GetUserDelay()) + { + switch (it->second.protoVer) + { + case 6: + Send_ALIVE_SYN_6(&(it->second), sip); + break; + case 7: + Send_ALIVE_SYN_7(&(it->second), sip); + break; + case 8: + Send_ALIVE_SYN_8(&(it->second), sip); + break; + } + + gettimeofday(&it->second.lastSendAlive, NULL); + } + + if ((currTime - it->second.phase.GetTime()) > iaSettings.GetUserTimeout()) + { + it->second.user->Unauthorize(this); + ip2user.erase(it++); + continue; + } + } + + if ((it->second.phase.GetPhase() == 4) + && ((currTime - it->second.phase.GetTime()) > iaSettings.GetUserDelay())) + { + it->second.phase.SetPhase3(); + printfd(__FILE__, "Phase changed from 4 to 3. Reason: timeout\n"); + } + + ++it; + } + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::PacketProcessor(char * buff, int dataLen, uint32_t sip, uint16_t sport, int protoVer, user_iter * user) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +// ôÕÔ ÓÏÂÒÁÎÙ ÏÂÒÁÂÏÔÞÉËÉ ÒÁÚÎÙÈ ÐÁËÅÔÏ× +int pn = -1; +int packetLen; +const int offset = LOGIN_LEN + 2 + 6; // LOGIN_LEN + sizeOfMagic + sizeOfVer; + +IA_USER * iaUser = NULL; + +CONN_SYN_6 * connSyn6; +CONN_SYN_7 * connSyn7; +CONN_SYN_8 * connSyn8; + +CONN_ACK_6 * connAck; +ALIVE_ACK_6 * aliveAck; +DISCONN_SYN_6 * disconnSyn; +DISCONN_ACK_6 * disconnAck; + +map::iterator it; +it = ip2user.find(sip); + +if (it == ip2user.end() || (*user)->GetID() != it->second.user->GetID()) + { + // åÝÅ ÎÅ ÂÙÌÏ ÚÁÐÒÏÓÏ× Ó ÜÔÏÇÏ IP + printfd(__FILE__, "Add new user\n"); + ip2user[sip].protoVer = protoVer; + ip2user[sip].user = *user; + ip2user[sip].port = sport; + #ifdef IA_PHASE_DEBUG + ip2user[sip].phase.SetLogFileName(stgSettings->GetLogFileName()); + ip2user[sip].phase.SetUserLogin((*user)->GetLogin()); + #endif + + + it = ip2user.find(sip); //TODO + if (it == ip2user.end()) + { + printfd(__FILE__, "+++ ERROR +++\n"); + return -1; + } + } + +iaUser = &(it->second); + +if (iaUser->port != sport) + iaUser->port = sport; + +if (iaUser->password != (*user)->property.password.Get()) + { + InitEncrypt(&iaUser->ctx, (*user)->property.password.Get()); + iaUser->password = (*user)->property.password.Get(); + } + +buff += offset; +Decrypt(&iaUser->ctx, buff, buff, (dataLen - offset) / 8); + +char packetName[IA_MAX_TYPE_LEN]; +strncpy(packetName, buff + 4, IA_MAX_TYPE_LEN); +packetName[IA_MAX_TYPE_LEN - 1] = 0; + +map::iterator pi; +pi = packetTypes.find(packetName); +if (pi == packetTypes.end()) + { + SendError(sip, sport, protoVer, "îÅÐÒÁ×ÉÌØÎÙÊ ÌÏÇÉÎ ÉÌÉ ÐÁÒÏÌØ!"); + printfd(__FILE__, "Login or password is wrong!\n"); + WriteServLog("User's connect failed. IP \'%s\'. Wrong login or password", inet_ntostring(sip).c_str()); + return 0; + } +else + { + pn = pi->second; + } + +packetLen = *(int*)buff; + +if ((*user)->property.disabled.Get()) + { + SendError(sip, sport, protoVer, "õÞÅÔÎÁÑ ÚÁÐÉÓØ ÚÁÂÌÏËÉÒÏ×ÁÎÁ"); + return 0; + } + +if ((*user)->property.passive.Get()) + { + SendError(sip, sport, protoVer, "õÞÅÔÎÁÑ ÚÁÐÉÓØ ÚÁÍÏÒÏÖÅÎÁ"); + return 0; + } + +if ((*user)->GetAuthorized() && (*user)->GetCurrIP() != sip) + { + printfd(__FILE__, "Login %s alredy in use. IP \'%s\'\n", (*user)->GetLogin().c_str(), inet_ntostring(sip).c_str()); + WriteServLog("Login %s alredy in use. IP \'%s\'", (*user)->GetLogin().c_str(), inet_ntostring(sip).c_str()); + SendError(sip, sport, protoVer, "÷ÁÛ ÌÏÇÉÎ ÕÖÅ ÉÓÐÏÌØÚÕÅÔÓÑ!"); + return 0; + } + +user_iter u; +if (users->FindByIPIdx(sip, &u) == 0 && u->GetLogin() != (*user)->GetLogin()) + { + printfd(__FILE__, "IP address alredy in use. IP \'%s\'", inet_ntostring(sip).c_str()); + WriteServLog("IP address alredy in use. IP \'%s\'", inet_ntostring(sip).c_str()); + SendError(sip, sport, protoVer, "÷ÁÛ IP ÁÄÒÅÓ ÕÖÅ ÉÓÐÏÌØÚÕÅÔÓÑ!"); + return 0; + } + +// ôÅÐÅÒØ ÍÙ ÄÏÌÖÎÙ ÐÒÏ×ÅÒÉÔØ, ÍÏÖÅÔ ÌÉ ÐÏÌØÚÏ×ÁÔÅÌØ ÐÏÄËÌÀÞÉÔÓÑ Ó ÜÔÏÇÏ ÁÄÒÅÓÁ. +int ipFound = (*user)->property.ips.Get().IsIPInIPS(sip); +if (!ipFound) + { + printfd(__FILE__, "User %s. IP address is incorrect. IP \'%s\'\n", (*user)->GetLogin().c_str(), inet_ntostring(sip).c_str()); + WriteServLog("User %s. IP address is incorrect. IP \'%s\'", (*user)->GetLogin().c_str(), inet_ntostring(sip).c_str()); + SendError(sip, sport, protoVer, "ðÏÌØÚÏ×ÁÔÅÌØ ÎÅ ÏÐÏÚÎÁÎ! ðÒÏ×ÅÒØÔÅ IP ÁÄÒÅÓ."); + return 0; + } + +int ret = -1; + +switch (pn) + { + case CONN_SYN_N: + switch (protoVer) + { + case 6: + connSyn6 = (CONN_SYN_6*)(buff - offset); + ret = Process_CONN_SYN_6(connSyn6, &(it->second), user, sip); + break; + case 7: + connSyn7 = (CONN_SYN_7*)(buff - offset); + ret = Process_CONN_SYN_7(connSyn7, &(it->second), user, sip); + break; + case 8: + connSyn8 = (CONN_SYN_8*)(buff - offset); + ret = Process_CONN_SYN_8(connSyn8, &(it->second), user, sip); + break; + } + + if (ret != 0) + { + return 0; + } + switch (protoVer) + { + case 6: + Send_CONN_SYN_ACK_6(iaUser, user, sip); + break; + case 7: + Send_CONN_SYN_ACK_7(iaUser, user, sip); + break; + case 8: + Send_CONN_SYN_ACK_8(iaUser, user, sip); + break; + } + break; + + case CONN_ACK_N: + connAck = (CONN_ACK_6*)(buff - offset); + switch (protoVer) + { + case 6: + ret = Process_CONN_ACK_6(connAck, iaUser, user, sip); + break; + case 7: + ret = Process_CONN_ACK_7(connAck, iaUser, user, sip); + break; + case 8: + ret = Process_CONN_ACK_8((CONN_ACK_8*)(buff - offset), iaUser, user, sip); + break; + } + + if (ret != 0) + { + SendError(sip, sport, protoVer, errorStr); + return 0; + } + + switch (protoVer) + { + case 6: + Send_ALIVE_SYN_6(iaUser, sip); + break; + case 7: + Send_ALIVE_SYN_7(iaUser, sip); + break; + case 8: + Send_ALIVE_SYN_8(iaUser, sip); + break; + } + + break; + + // ðÒÉÂÙÌ ÏÔ×ÅÔ Ó ÐÏÄÔ×ÅÒÖÄÅÎÉÅÍ ALIVE + case ALIVE_ACK_N: + aliveAck = (ALIVE_ACK_6*)(buff - offset); + switch (protoVer) + { + case 6: + ret = Process_ALIVE_ACK_6(aliveAck, iaUser, user, sip); + break; + case 7: + ret = Process_ALIVE_ACK_7(aliveAck, iaUser, user, sip); + break; + case 8: + ret = Process_ALIVE_ACK_8((ALIVE_ACK_8*)(buff - offset), iaUser, user, sip); + break; + } + break; + + // úÁÐÒÏÓ ÎÁ ÏÔËÌÀÞÅÎÉÅ + case DISCONN_SYN_N: + + disconnSyn = (DISCONN_SYN_6*)(buff - offset); + switch (protoVer) + { + case 6: + ret = Process_DISCONN_SYN_6(disconnSyn, iaUser, user, sip); + break; + case 7: + ret = Process_DISCONN_SYN_7(disconnSyn, iaUser, user, sip); + break; + case 8: + ret = Process_DISCONN_SYN_8((DISCONN_SYN_8*)(buff - offset), iaUser, user, sip); + break; + } + + if (ret != 0) + return 0; + + switch (protoVer) + { + case 6: + Send_DISCONN_SYN_ACK_6(iaUser, sip); + break; + case 7: + Send_DISCONN_SYN_ACK_7(iaUser, sip); + break; + case 8: + Send_DISCONN_SYN_ACK_8(iaUser, sip); + break; + } + break; + + case DISCONN_ACK_N: + disconnAck = (DISCONN_ACK_6*)(buff - offset); + + switch (protoVer) + { + case 6: + ret = Process_DISCONN_ACK_6(disconnAck, iaUser, user, sip, it); + break; + case 7: + ret = Process_DISCONN_ACK_7(disconnAck, iaUser, user, sip, it); + break; + case 8: + ret = Process_DISCONN_ACK_8((DISCONN_ACK_8*)(buff - offset), iaUser, user, sip, it); + break; + } + + switch (protoVer) + { + case 6: + Send_FIN_6(iaUser, sip, it); + break; + case 7: + Send_FIN_7(iaUser, sip, it); + break; + case 8: + Send_FIN_8(iaUser, sip, it); + break; + } + break; + } + +return 0; +} +//----------------------------------------------------------------------------- +void AUTH_IA::DelUser(user_iter u) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +uint32_t ip = u->GetCurrIP(); + +if (!ip) + return; + +map::iterator it; +it = ip2user.find(ip); +if (it == ip2user.end()) + { + //Nothing to delete + printfd(__FILE__, "Nothing to delete\n"); + return; + } + +if (it->second.user == u) + { + printfd(__FILE__, "User removed!\n"); + it->second.user->Unauthorize(this); + ip2user.erase(it); + } +} +//----------------------------------------------------------------------------- +int AUTH_IA::SendError(uint32_t ip, uint16_t port, int protoVer, const string & text) +{ +struct sockaddr_in sendAddr; +switch (protoVer) + { + int res; + case 6: + case 7: + ERR err; + memset(&err, 0, sizeof(ERR)); + + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = htons(port); + + sendAddr.sin_addr.s_addr = ip;// IP ÐÏÌØÚÏ×ÁÔÅÌÑ + + err.len = 1; + strncpy((char*)err.type, "ERR", 16); + strncpy((char*)err.text, text.c_str(), MAX_MSG_LEN); + + #ifdef ARCH_BE + SwapBytes(err.len); + #endif + + res = sendto(listenSocket, &err, sizeof(err), 0, (struct sockaddr*)&sendAddr, sizeof(sendAddr)); + printfd(__FILE__, "SendError %d bytes sent\n", res); + break; + + case 8: + ERR_8 err8; + memset(&err8, 0, sizeof(ERR_8)); + + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = htons(port); + + sendAddr.sin_addr.s_addr = ip;// IP ÐÏÌØÚÏ×ÁÔÅÌÑ + + err8.len = 256; + strncpy((char*)err8.type, "ERR", 16); + strncpy((char*)err8.text, text.c_str(), MAX_MSG_LEN); + + #ifdef ARCH_BE + SwapBytes(err8.len); + #endif + + res = sendto(listenSocket, &err8, sizeof(err8), 0, (struct sockaddr*)&sendAddr, sizeof(sendAddr)); + printfd(__FILE__, "SendError_8 %d bytes sent\n", res); + break; + } + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send(uint32_t ip, uint16_t port, const char * buffer, int len) +{ +struct sockaddr_in sendAddr; +int res; + +sendAddr.sin_family = AF_INET; +sendAddr.sin_port = htons(port); +sendAddr.sin_addr.s_addr = ip; + +res = sendto(listenSocket, buffer, len, 0, (struct sockaddr*)&sendAddr, sizeof(sendAddr)); + +static struct timeval tv; +gettimeofday(&tv, NULL); + +return res; +} +//----------------------------------------------------------------------------- +void AUTH_IA::InitEncrypt(BLOWFISH_CTX * ctx, const string & password) +{ +unsigned char keyL[PASSWD_LEN]; // ðÁÒÏÌØ ÄÌÑ ÛÉÆÒÏ×ËÉ +memset(keyL, 0, PASSWD_LEN); +strncpy((char *)keyL, password.c_str(), PASSWD_LEN); +Blowfish_Init(ctx, keyL, PASSWD_LEN); +} +//----------------------------------------------------------------------------- +void AUTH_IA::Decrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8) +{ +// len8 - ÄÌÉÎÁ × 8-ÍÉ ÂÁÊÔÏ×ÙÈ ÂÌÏËÁÈ + +for (int i = 0; i < len8; i++) + DecodeString(dst + i * 8, src + i * 8, ctx); +} +//----------------------------------------------------------------------------- +void AUTH_IA::Encrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8) +{ +// len8 - ÄÌÉÎÁ × 8-ÍÉ ÂÁÊÔÏ×ÙÈ ÂÌÏËÁÈ + +for (int i = 0; i < len8; i++) + EncodeString(dst + i * 8, src + i * 8, ctx); +} +//----------------------------------------------------------------------------- +int AUTH_IA::SendMessage(const STG_MSG & msg, uint32_t ip) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +printfd(__FILE__, "SendMessage userIP=%s\n", inet_ntostring(ip).c_str()); + +map::iterator it; +it = ip2user.find(ip); +if (it == ip2user.end()) + { + errorStr = "Unknown user."; + return -1; + } +it->second.messagesToSend.push_back(msg); +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::RealSendMessage6(const STG_MSG & msg, uint32_t ip, IA_USER & user) +{ +printfd(__FILE__, "RealSendMessage 6 user=%s\n", user.user->GetLogin().c_str()); + +char buffer[256]; +INFO_6 info; + +memset(&info, 0, sizeof(INFO_6)); + +info.len = 256; +strncpy((char*)info.type, "INFO", 16); +info.infoType = 'I'; +strncpy((char*)info.text, msg.text.c_str(), 235); +info.text[234] = 0; + +size_t len = info.len; +#ifdef ARCH_BE +SwapBytes(info.len); +#endif + +memcpy(buffer, &info, sizeof(INFO_6)); +Encrypt(&user.ctx, buffer, buffer, len / 8); +Send(ip, iaSettings.GetUserPort(), buffer, len); + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::RealSendMessage7(const STG_MSG & msg, uint32_t ip, IA_USER & user) +{ +printfd(__FILE__, "RealSendMessage 7 user=%s\n", user.user->GetLogin().c_str()); + +char buffer[300]; +INFO_7 info; + +memset(&info, 0, sizeof(INFO_7)); + +info.len = 264; +strncpy((char*)info.type, "INFO_7", 16); +info.infoType = msg.header.type; +info.showTime = msg.header.showTime; + +info.sendTime = msg.header.creationTime; + +size_t len = info.len; +#ifdef ARCH_BE +SwapBytes(info.len); +SwapBytes(info.sendTime); +#endif + +strncpy((char*)info.text, msg.text.c_str(), MAX_MSG_LEN - 1); +info.text[MAX_MSG_LEN - 1] = 0; + +memcpy(buffer, &info, sizeof(INFO_7)); + +Encrypt(&user.ctx, buffer, buffer, len / 8); +Send(ip, iaSettings.GetUserPort(), buffer, len); + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::RealSendMessage8(const STG_MSG & msg, uint32_t ip, IA_USER & user) +{ +printfd(__FILE__, "RealSendMessage 8 user=%s\n", user.user->GetLogin().c_str()); + +char buffer[1500]; +memset(buffer, 0, sizeof(buffer)); + +INFO_8 info; + +memset(&info, 0, sizeof(INFO_8)); + +info.len = 1056; +strncpy((char*)info.type, "INFO_8", 16); +info.infoType = msg.header.type; +info.showTime = msg.header.showTime; +info.sendTime = msg.header.creationTime; + +strncpy((char*)info.text, msg.text.c_str(), IA_MAX_MSG_LEN_8 - 1); +info.text[IA_MAX_MSG_LEN_8 - 1] = 0; + +size_t len = info.len; +#ifdef ARCH_BE +SwapBytes(info.len); +SwapBytes(info.sendTime); +#endif + +memcpy(buffer, &info, sizeof(INFO_8)); + +Encrypt(&user.ctx, buffer, buffer, len / 8); +Send(ip, user.port, buffer, len); + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_CONN_SYN_6(CONN_SYN_6 *, IA_USER * iaUser, user_iter *, uint32_t) +{ +if (!(iaUser->phase.GetPhase() == 1 || iaUser->phase.GetPhase() == 3)) + return -1; + +enabledDirs = 0xFFffFFff; + +iaUser->phase.SetPhase2(); +printfd(__FILE__, "Phase changed from %d to 2. Reason: CONN_SYN_6\n", iaUser->phase.GetPhase()); +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_CONN_SYN_7(CONN_SYN_7 * connSyn, IA_USER * iaUser, user_iter * user, uint32_t sip) +{ +return Process_CONN_SYN_6(connSyn, iaUser, user, sip); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_CONN_SYN_8(CONN_SYN_8 * connSyn, IA_USER * iaUser, user_iter * user, uint32_t sip) +{ +#ifdef ARCH_BE +SwapBytes(connSyn->dirs); +#endif +int ret = Process_CONN_SYN_6((CONN_SYN_6*)connSyn, iaUser, user, sip); +enabledDirs = connSyn->dirs; +return ret; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_CONN_ACK_6(CONN_ACK_6 * connAck, IA_USER * iaUser, user_iter *, uint32_t sip) +{ +#ifdef ARCH_BE +SwapBytes(connAck->len); +SwapBytes(connAck->rnd); +#endif +printfd( __FILE__, "CONN_ACK_6 %s\n", connAck->type); +// ÕÓÔÁÎÏ×ÉÔØ ÎÏ×ÕÀ ÆÁÚÕ É ×ÒÅÍÑ É ÒÁÚÒÅÛÉÔØ ÉÎÅÔ +if ((iaUser->phase.GetPhase() == 2) && (connAck->rnd == iaUser->rnd + 1)) + { + iaUser->phase.UpdateTime(); + + iaUser->lastSendAlive = iaUser->phase.GetTime(); + if (iaUser->user->Authorize(sip, "", enabledDirs, this) == 0) + { + iaUser->phase.SetPhase3(); + printfd(__FILE__, "Phase changed from 2 to 3. Reason: CONN_ACK_6\n"); + return 0; + } + else + { + errorStr = iaUser->user->GetStrError(); + iaUser->phase.SetPhase1(); + printfd(__FILE__, "Phase changed from 2 to 1. Reason: failed to authorize user\n"); + return -1; + } + } +printfd(__FILE__, "Invalid phase or control number. Phase: %d. Control number: %d\n", iaUser->phase.GetPhase(), connAck->rnd); +return -1; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_CONN_ACK_7(CONN_ACK_7 * connAck, IA_USER * iaUser, user_iter * user, uint32_t sip) +{ +return Process_CONN_ACK_6(connAck, iaUser, user, sip); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_CONN_ACK_8(CONN_ACK_8 * connAck, IA_USER * iaUser, user_iter *, uint32_t sip) +{ +#ifdef ARCH_BE +SwapBytes(connAck->len); +SwapBytes(connAck->rnd); +#endif +printfd( __FILE__, "CONN_ACK_8 %s\n", connAck->type); + +if ((iaUser->phase.GetPhase() == 2) && (connAck->rnd == iaUser->rnd + 1)) + { + iaUser->phase.UpdateTime(); + iaUser->lastSendAlive = iaUser->phase.GetTime(); + if (iaUser->user->Authorize(sip, "", enabledDirs, this) == 0) + { + iaUser->phase.SetPhase3(); + printfd(__FILE__, "Phase changed from 2 to 3. Reason: CONN_ACK_8\n"); + return 0; + } + else + { + errorStr = iaUser->user->GetStrError(); + iaUser->phase.SetPhase1(); + printfd(__FILE__, "Phase changed from 2 to 1. Reason: failed to authorize user\n"); + return -1; + } + } +printfd(__FILE__, "Invalid phase or control number. Phase: %d. Control number: %d\n", iaUser->phase.GetPhase(), connAck->rnd); +return -1; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_ALIVE_ACK_6(ALIVE_ACK_6 * aliveAck, IA_USER * iaUser, user_iter *, uint32_t) +{ +#ifdef ARCH_BE +SwapBytes(aliveAck->len); +SwapBytes(aliveAck->rnd); +#endif +printfd(__FILE__, "ALIVE_ACK_6\n"); +if ((iaUser->phase.GetPhase() == 3) && (aliveAck->rnd == iaUser->rnd + 1)) + { + iaUser->phase.UpdateTime(); + #ifdef IA_DEBUG + iaUser->aliveSent = false; + #endif + } +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_ALIVE_ACK_7(ALIVE_ACK_7 * aliveAck, IA_USER * iaUser, user_iter * user, uint32_t sip) +{ +return Process_ALIVE_ACK_6(aliveAck, iaUser, user, sip); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_ALIVE_ACK_8(ALIVE_ACK_8 * aliveAck, IA_USER * iaUser, user_iter *, uint32_t) +{ +#ifdef ARCH_BE +SwapBytes(aliveAck->len); +SwapBytes(aliveAck->rnd); +#endif +printfd(__FILE__, "ALIVE_ACK_8\n"); +if ((iaUser->phase.GetPhase() == 3) && (aliveAck->rnd == iaUser->rnd + 1)) + { + iaUser->phase.UpdateTime(); + #ifdef IA_DEBUG + iaUser->aliveSent = false; + #endif + } +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_DISCONN_SYN_6(DISCONN_SYN_6 *, IA_USER * iaUser, user_iter *, uint32_t) +{ +printfd(__FILE__, "DISCONN_SYN_6\n"); +if (iaUser->phase.GetPhase() != 3) + { + printfd(__FILE__, "Invalid phase. Expected 3, actual %d\n", iaUser->phase.GetPhase()); + errorStr = "Incorrect request DISCONN_SYN"; + return -1; + } + +iaUser->phase.SetPhase4(); +printfd(__FILE__, "Phase changed from 3 to 4. Reason: DISCONN_SYN_6\n"); + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_DISCONN_SYN_7(DISCONN_SYN_7 * disconnSyn, IA_USER * iaUser, user_iter * user, uint32_t sip) +{ +return Process_DISCONN_SYN_6(disconnSyn, iaUser, user, sip); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_DISCONN_SYN_8(DISCONN_SYN_8 *, IA_USER * iaUser, user_iter *, uint32_t) +{ +if (iaUser->phase.GetPhase() != 3) + { + errorStr = "Incorrect request DISCONN_SYN"; + printfd(__FILE__, "Invalid phase. Expected 3, actual %d\n", iaUser->phase.GetPhase()); + return -1; + } + +iaUser->phase.SetPhase4(); +printfd(__FILE__, "Phase changed from 3 to 4. Reason: DISCONN_SYN_6\n"); + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_DISCONN_ACK_6(DISCONN_ACK_6 * disconnAck, + IA_USER * iaUser, + user_iter *, + uint32_t, + map::iterator) +{ +#ifdef ARCH_BE +SwapBytes(disconnAck->len); +SwapBytes(disconnAck->rnd); +#endif +printfd(__FILE__, "DISCONN_ACK_6\n"); +if (!((iaUser->phase.GetPhase() == 4) && (disconnAck->rnd == iaUser->rnd + 1))) + { + printfd(__FILE__, "Invalid phase or control number. Phase: %d. Control number: %d\n", iaUser->phase.GetPhase(), disconnAck->rnd); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_DISCONN_ACK_7(DISCONN_ACK_7 * disconnAck, IA_USER * iaUser, user_iter * user, uint32_t sip, map::iterator it) +{ +return Process_DISCONN_ACK_6(disconnAck, iaUser, user, sip, it); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Process_DISCONN_ACK_8(DISCONN_ACK_8 * disconnAck, IA_USER * iaUser, user_iter *, uint32_t, map::iterator) +{ +#ifdef ARCH_BE +SwapBytes(disconnAck->len); +SwapBytes(disconnAck->rnd); +#endif +printfd(__FILE__, "DISCONN_ACK_8\n"); +if (!((iaUser->phase.GetPhase() == 4) && (disconnAck->rnd == iaUser->rnd + 1))) + { + printfd(__FILE__, "Invalid phase or control number. Phase: %d. Control number: %d\n", iaUser->phase.GetPhase(), disconnAck->rnd); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_CONN_SYN_ACK_6(IA_USER * iaUser, user_iter *, uint32_t sip) +{ +//+++ Fill static data in connSynAck +++ +// TODO Move this code. It must be executed only once +connSynAck6.len = Min8(sizeof(CONN_SYN_ACK_6)); +strcpy((char*)connSynAck6.type, "CONN_SYN_ACK"); +for (int j = 0; j < DIR_NUM; j++) + { + strncpy((char*)connSynAck6.dirName[j], + stgSettings->GetDirName(j).c_str(), + sizeof(string16)); + + connSynAck6.dirName[j][sizeof(string16) - 1] = 0; + } +//--- Fill static data in connSynAck --- + +iaUser->rnd = random(); +connSynAck6.rnd = iaUser->rnd; + +connSynAck6.userTimeOut = iaSettings.GetUserTimeout(); +connSynAck6.aliveDelay = iaSettings.GetUserDelay(); + +#ifdef ARCH_BE +SwapBytes(connSynAck6.len); +SwapBytes(connSynAck6.rnd); +SwapBytes(connSynAck6.userTimeOut); +SwapBytes(connSynAck6.aliveDelay); +#endif + +Encrypt(&iaUser->ctx, (char*)&connSynAck6, (char*)&connSynAck6, Min8(sizeof(CONN_SYN_ACK_6))/8); +return Send(sip, iaSettings.GetUserPort(), (char*)&connSynAck6, Min8(sizeof(CONN_SYN_ACK_6)));; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_CONN_SYN_ACK_7(IA_USER * iaUser, user_iter * user, uint32_t sip) +{ +return Send_CONN_SYN_ACK_6(iaUser, user, sip); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_CONN_SYN_ACK_8(IA_USER * iaUser, user_iter *, uint32_t sip) +{ +strcpy((char*)connSynAck8.hdr.magic, IA_ID); +connSynAck8.hdr.protoVer[0] = 0; +connSynAck8.hdr.protoVer[1] = 8; + +//+++ Fill static data in connSynAck +++ +// TODO Move this code. It must be executed only once +connSynAck8.len = Min8(sizeof(CONN_SYN_ACK_8)); +strcpy((char*)connSynAck8.type, "CONN_SYN_ACK"); +for (int j = 0; j < DIR_NUM; j++) + { + strncpy((char*)connSynAck8.dirName[j], + stgSettings->GetDirName(j).c_str(), + sizeof(string16)); + + connSynAck8.dirName[j][sizeof(string16) - 1] = 0; + } +//--- Fill static data in connSynAck --- + +iaUser->rnd = random(); +connSynAck8.rnd = iaUser->rnd; + +connSynAck8.userTimeOut = iaSettings.GetUserTimeout(); +connSynAck8.aliveDelay = iaSettings.GetUserDelay(); + +#ifdef ARCH_BE +SwapBytes(connSynAck8.len); +SwapBytes(connSynAck8.rnd); +SwapBytes(connSynAck8.userTimeOut); +SwapBytes(connSynAck8.aliveDelay); +#endif + +Encrypt(&iaUser->ctx, (char*)&connSynAck8, (char*)&connSynAck8, Min8(sizeof(CONN_SYN_ACK_8))/8); +return Send(sip, iaUser->port, (char*)&connSynAck8, Min8(sizeof(CONN_SYN_ACK_8))); +//return Send(sip, iaUser->port, (char*)&connSynAck8, 384); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_ALIVE_SYN_6(IA_USER * iaUser, uint32_t sip) +{ +aliveSyn6.len = Min8(sizeof(ALIVE_SYN_6)); +aliveSyn6.rnd = iaUser->rnd = random(); + +strcpy((char*)aliveSyn6.type, "ALIVE_SYN"); + +for (int i = 0; i < DIR_NUM; i++) + { + aliveSyn6.md[i] = iaUser->user->property.down.Get()[i]; + aliveSyn6.mu[i] = iaUser->user->property.up.Get()[i]; + + aliveSyn6.sd[i] = iaUser->user->GetSessionDownload()[i]; + aliveSyn6.su[i] = iaUser->user->GetSessionUpload()[i]; + } + +//TODO +int dn = iaSettings.GetFreeMbShowType(); +const TARIFF * tf = iaUser->user->GetTariff(); + +if (dn < DIR_NUM) + { + double p = tf->GetPriceWithTraffType(aliveSyn6.mu[dn], + aliveSyn6.md[dn], + dn, + stgTime); + p *= (1024 * 1024); + if (p == 0) + { + snprintf((char*)aliveSyn6.freeMb, IA_FREEMB_LEN, "---"); + } + else + { + double fmb = iaUser->user->property.freeMb; + fmb = fmb < 0 ? 0 : fmb; + snprintf((char*)aliveSyn6.freeMb, IA_FREEMB_LEN, "%.3f", fmb / p); + } + } +else + { + if (freeMbNone == iaSettings.GetFreeMbShowType()) + { + aliveSyn6.freeMb[0] = 0; + } + else + { + double fmb = iaUser->user->property.freeMb; + fmb = fmb < 0 ? 0 : fmb; + snprintf((char*)aliveSyn6.freeMb, IA_FREEMB_LEN, "C%.3f", fmb); + } + } + +#ifdef IA_DEBUG +if (iaUser->aliveSent) + { + printfd(__FILE__, "========= ALIVE_ACK_6(7) TIMEOUT !!! %s =========\n", iaUser->user->GetLogin().c_str()); + } +iaUser->aliveSent = true; +#endif + +aliveSyn6.cash =(int64_t) (iaUser->user->property.cash.Get() * 1000.0); +if (!stgSettings->GetShowFeeInCash()) + aliveSyn6.cash -= (int64_t)(tf->GetFee() * 1000.0); + +#ifdef ARCH_BE +SwapBytes(aliveSyn6.len); +SwapBytes(aliveSyn6.rnd); +SwapBytes(aliveSyn6.cash); +for (int i = 0; i < DIR_NUM; ++i) + { + SwapBytes(aliveSyn6.mu[i]); + SwapBytes(aliveSyn6.md[i]); + SwapBytes(aliveSyn6.su[i]); + SwapBytes(aliveSyn6.sd[i]); + } +#endif + +Encrypt(&(iaUser->ctx), (char*)&aliveSyn6, (char*)&aliveSyn6, Min8(sizeof(aliveSyn6))/8); +return Send(sip, iaSettings.GetUserPort(), (char*)&aliveSyn6, Min8(sizeof(aliveSyn6))); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_ALIVE_SYN_7(IA_USER * iaUser, uint32_t sip) +{ +return Send_ALIVE_SYN_6(iaUser, sip); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_ALIVE_SYN_8(IA_USER * iaUser, uint32_t sip) +{ +strcpy((char*)aliveSyn8.hdr.magic, IA_ID); +aliveSyn8.hdr.protoVer[0] = 0; +aliveSyn8.hdr.protoVer[1] = 8; + +aliveSyn8.len = Min8(sizeof(ALIVE_SYN_8)); +aliveSyn8.rnd = iaUser->rnd = random(); + +strcpy((char*)aliveSyn8.type, "ALIVE_SYN"); + +for (int i = 0; i < DIR_NUM; i++) + { + aliveSyn8.md[i] = iaUser->user->property.down.Get()[i]; + aliveSyn8.mu[i] = iaUser->user->property.up.Get()[i]; + + aliveSyn8.sd[i] = iaUser->user->GetSessionDownload()[i]; + aliveSyn8.su[i] = iaUser->user->GetSessionUpload()[i]; + } + +//TODO +int dn = iaSettings.GetFreeMbShowType(); + +if (dn < DIR_NUM) + { + const TARIFF * tf = iaUser->user->GetTariff(); + double p = tf->GetPriceWithTraffType(aliveSyn8.mu[dn], + aliveSyn8.md[dn], + dn, + stgTime); + p *= (1024 * 1024); + if (p == 0) + { + snprintf((char*)aliveSyn8.freeMb, IA_FREEMB_LEN, "---"); + } + else + { + double fmb = iaUser->user->property.freeMb; + fmb = fmb < 0 ? 0 : fmb; + snprintf((char*)aliveSyn8.freeMb, IA_FREEMB_LEN, "%.3f", fmb / p); + } + } +else + { + if (freeMbNone == iaSettings.GetFreeMbShowType()) + { + aliveSyn8.freeMb[0] = 0; + } + else + { + double fmb = iaUser->user->property.freeMb; + fmb = fmb < 0 ? 0 : fmb; + snprintf((char*)aliveSyn8.freeMb, IA_FREEMB_LEN, "C%.3f", fmb); + } + } + +#ifdef IA_DEBUG +if (iaUser->aliveSent) + { + printfd(__FILE__, "========= ALIVE_ACK_8 TIMEOUT !!! =========\n"); + } +iaUser->aliveSent = true; +#endif + +const TARIFF * tf = iaUser->user->GetTariff(); + +aliveSyn8.cash =(int64_t) (iaUser->user->property.cash.Get() * 1000.0); +if (!stgSettings->GetShowFeeInCash()) + aliveSyn8.cash -= (int64_t)(tf->GetFee() * 1000.0); + +#ifdef ARCH_BE +SwapBytes(aliveSyn8.len); +SwapBytes(aliveSyn8.rnd); +SwapBytes(aliveSyn8.cash); +SwapBytes(aliveSyn8.status); +for (int i = 0; i < DIR_NUM; ++i) + { + SwapBytes(aliveSyn8.mu[i]); + SwapBytes(aliveSyn8.md[i]); + SwapBytes(aliveSyn8.su[i]); + SwapBytes(aliveSyn8.sd[i]); + } +#endif + +Encrypt(&(iaUser->ctx), (char*)&aliveSyn8, (char*)&aliveSyn8, Min8(sizeof(aliveSyn8))/8); +return Send(sip, iaUser->port, (char*)&aliveSyn8, Min8(sizeof(aliveSyn8))); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_DISCONN_SYN_ACK_6(IA_USER * iaUser, uint32_t sip) +{ +disconnSynAck6.len = Min8(sizeof(DISCONN_SYN_ACK_6)); +strcpy((char*)disconnSynAck6.type, "DISCONN_SYN_ACK"); +disconnSynAck6.rnd = iaUser->rnd = random(); + +#ifdef ARCH_BE +SwapBytes(disconnSynAck6.len); +SwapBytes(disconnSynAck6.rnd); +#endif + +Encrypt(&iaUser->ctx, (char*)&disconnSynAck6, (char*)&disconnSynAck6, Min8(sizeof(disconnSynAck6))/8); +return Send(sip, iaSettings.GetUserPort(), (char*)&disconnSynAck6, Min8(sizeof(disconnSynAck6))); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_DISCONN_SYN_ACK_7(IA_USER * iaUser, uint32_t sip) +{ +return Send_DISCONN_SYN_ACK_6(iaUser, sip); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_DISCONN_SYN_ACK_8(IA_USER * iaUser, uint32_t sip) +{ +strcpy((char*)disconnSynAck8.hdr.magic, IA_ID); +disconnSynAck8.hdr.protoVer[0] = 0; +disconnSynAck8.hdr.protoVer[1] = 8; + +disconnSynAck8.len = Min8(sizeof(DISCONN_SYN_ACK_8)); +strcpy((char*)disconnSynAck8.type, "DISCONN_SYN_ACK"); +disconnSynAck8.rnd = iaUser->rnd = random(); + +#ifdef ARCH_BE +SwapBytes(disconnSynAck8.len); +SwapBytes(disconnSynAck8.rnd); +#endif + +Encrypt(&iaUser->ctx, (char*)&disconnSynAck8, (char*)&disconnSynAck8, Min8(sizeof(disconnSynAck8))/8); +return Send(sip, iaUser->port, (char*)&disconnSynAck8, Min8(sizeof(disconnSynAck8))); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_FIN_6(IA_USER * iaUser, uint32_t sip, map::iterator it) +{ +fin6.len = Min8(sizeof(FIN_6)); +strcpy((char*)fin6.type, "FIN"); +strcpy((char*)fin6.ok, "OK"); + +#ifdef ARCH_BE +SwapBytes(fin6.len); +#endif + +Encrypt(&iaUser->ctx, (char*)&fin6, (char*)&fin6, Min8(sizeof(fin6))/8); + +iaUser->user->Unauthorize(this); + +int ret = Send(sip, iaSettings.GetUserPort(), (char*)&fin6, Min8(sizeof(fin6))); +ip2user.erase(it); +return ret; +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_FIN_7(IA_USER * iaUser, uint32_t sip, map::iterator it) +{ +return Send_FIN_6(iaUser, sip, it); +} +//----------------------------------------------------------------------------- +int AUTH_IA::Send_FIN_8(IA_USER * iaUser, uint32_t sip, map::iterator it) +{ +strcpy((char*)fin8.hdr.magic, IA_ID); +fin8.hdr.protoVer[0] = 0; +fin8.hdr.protoVer[1] = 8; + +fin8.len = Min8(sizeof(FIN_8)); +strcpy((char*)fin8.type, "FIN"); +strcpy((char*)fin8.ok, "OK"); + +#ifdef ARCH_BE +SwapBytes(fin8.len); +#endif + +Encrypt(&iaUser->ctx, (char*)&fin8, (char*)&fin8, Min8(sizeof(fin8))/8); + +iaUser->user->Unauthorize(this); + +int ret = Send(sip, iaUser->port, (char*)&fin8, Min8(sizeof(fin8))); +ip2user.erase(it); +return ret; +} +//----------------------------------------------------------------------------- +bool AUTH_IA::WaitPackets(int sd) const +{ +fd_set rfds; +FD_ZERO(&rfds); +FD_SET(sd, &rfds); + +struct timeval tv; +tv.tv_sec = 0; +tv.tv_usec = 500000; + +int res = select(sd + 1, &rfds, NULL, NULL, &tv); +if (res == -1) // Error + { + if (errno != EINTR) + { + printfd(__FILE__, "Error on select: '%s'\n", strerror(errno)); + } + return false; + } + +if (res == 0) // Timeout + { + return false; + } + +return true; +} diff --git a/projects/stargazer/plugins/authorization/inetaccess/inetaccess.h b/projects/stargazer/plugins/authorization/inetaccess/inetaccess.h new file mode 100644 index 00000000..0e194db6 --- /dev/null +++ b/projects/stargazer/plugins/authorization/inetaccess/inetaccess.h @@ -0,0 +1,365 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.34 $ + $Date: 2010/09/10 06:39:19 $ + $Author: faust $ + */ + +#ifndef INETACCESS_H +#define INETACCESS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "os_int.h" +#include "base_auth.h" +#include "base_store.h" +#include "notifer.h" +#include "user_ips.h" +#include "../../../user.h" +#include "../../../users.h" +#include "ia_packets.h" +#include "blowfish.h" +#include "stg_logger.h" +#include "utime.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +#define IA_PROTO_VER (6) + +//#define IA_DEBUG (1) +//#define IA_PHASE_DEBUG (1) + +class AUTH_IA; +//----------------------------------------------------------------------------- +enum FREEMB +{ + freeMb0 = 0, + freeMb1, + freeMb2, + freeMb3, + freeMb4, + freeMb5, + freeMb6, + freeMb7, + freeMb8, + freeMb9, + freeMb10, + freeMb11, + freeMb12, + freeMb13, + freeMb14, + freeMb15, + freeMb16, + freeMb17, + freeMb18, + freeMb19, + freeMbCash = 100, + freeMbNone = 101 +}; +//----------------------------------------------------------------------------- +class IA_PHASE +{ +public: + IA_PHASE(); + ~IA_PHASE(); + + void SetPhase1(); + void SetPhase2(); + void SetPhase3(); + void SetPhase4(); + void SetPhase5(); + int GetPhase() const; + + void UpdateTime(); + const UTIME & GetTime() const; + + #ifdef IA_PHASE_DEBUG + void SetUserLogin(const string & login); + void SetLogFileName(const string & logFileName); + #endif + +private: + int phase; + UTIME phaseTime; + + #ifdef IA_PHASE_DEBUG + void WritePhaseChange(int newPhase); + string log; + string login; + FILE * flog; + #endif +}; +//----------------------------------------------------------------------------- +struct IA_USER +{ + IA_USER() + { + //phase = 1; + //phaseTime = 0; + lastSendAlive = 0; + rnd = random(); + port = 0; + password = "NO PASSWORD"; + // +++ Preparing CTX +++ + unsigned char keyL[PASSWD_LEN]; // ðÁÒÏÌØ ÄÌÑ ÛÉÆÒÏ×ËÉ + memset(keyL, 0, PASSWD_LEN); + strncpy((char *)keyL, password.c_str(), PASSWD_LEN); + Blowfish_Init(&ctx, keyL, PASSWD_LEN); + // --- Preparing CTX --- + #ifdef IA_DEBUG + aliveSent = false; + #endif + }; + + IA_USER(const IA_USER & u) + { + user = u.user; + phase = u.phase; + //phaseTime = u.phaseTime; + lastSendAlive = u.lastSendAlive; + rnd = u.rnd; + password = u.password; + protoVer = u.protoVer; + port = u.port; + #ifdef IA_DEBUG + aliveSent = u.aliveSent; + #endif + memcpy(&ctx, &u.ctx, sizeof(BLOWFISH_CTX)); + }; + + user_iter user; + //int phase; + //UTIME phaseTime; + IA_PHASE phase; + UTIME lastSendAlive; + uint32_t rnd; + uint16_t port; + BLOWFISH_CTX ctx; + list messagesToSend; + int protoVer; + string password; + #ifdef IA_DEBUG + bool aliveSent; + #endif +}; +//----------------------------------------------------------------------------- +class AUTH_IA_SETTINGS +{ +public: + AUTH_IA_SETTINGS(); + virtual ~AUTH_IA_SETTINGS() {}; + const string& GetStrError() const { return errorStr; }; + int ParseSettings(const MODULE_SETTINGS & s); + int GetUserDelay() const { return userDelay; }; + int GetUserTimeout() const { return userTimeout; }; + int GetUserPort() const { return port; }; + FREEMB GetFreeMbShowType() const { return freeMbShowType; }; + +private: + int ParseIntInRange(const string & str, int min, int max, int * val); + int userDelay; + int userTimeout; + uint16_t port; + string errorStr; + FREEMB freeMbShowType; +}; +//----------------------------------------------------------------------------- +class AUTH_IA :public BASE_AUTH +{ +public: + AUTH_IA(); + virtual ~AUTH_IA(); + + void SetUsers(USERS * u) { users = u; }; + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER *){}; + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS * s) { stgSettings = s; }; + void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning() { return isRunningRunTimeouter || isRunningRun; }; + + const string & GetStrError() const { return errorStr; }; + const string GetVersion() const { return "InetAccess authorization plugin v.1.4"; }; + uint16_t GetStartPosition() const { return 50; }; + uint16_t GetStopPosition() const { return 50; }; + + void DelUser(user_iter u); + + int SendMessage(const STG_MSG & msg, uint32_t ip) const; + +private: + static void * Run(void *); + static void * RunTimeouter(void * d); + int PrepareNet(); + int FinalizeNet(); + int RecvData(char * buffer, int bufferSize); + int CheckHeader(const char * buffer, int * protoVer); + int PacketProcessor(char * buff, int dataLen, uint32_t sip, uint16_t sport, int protoVer, user_iter * user); + + int Process_CONN_SYN_6(CONN_SYN_6 * connSyn, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_CONN_SYN_7(CONN_SYN_7 * connSyn, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_CONN_SYN_8(CONN_SYN_8 * connSyn, IA_USER * iaUser, user_iter * user, uint32_t sip); + + int Process_CONN_ACK_6(CONN_ACK_6 * connAck, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_CONN_ACK_7(CONN_ACK_7 * connAck, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_CONN_ACK_8(CONN_ACK_8 * connAck, IA_USER * iaUser, user_iter * user, uint32_t sip); + + int Process_ALIVE_ACK_6(ALIVE_ACK_6 * aliveAck, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_ALIVE_ACK_7(ALIVE_ACK_7 * aliveAck, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_ALIVE_ACK_8(ALIVE_ACK_8 * aliveAck, IA_USER * iaUser, user_iter * user, uint32_t sip); + + int Process_DISCONN_SYN_6(DISCONN_SYN_6 * disconnSyn, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_DISCONN_SYN_7(DISCONN_SYN_7 * disconnSyn, IA_USER * iaUser, user_iter * user, uint32_t sip); + int Process_DISCONN_SYN_8(DISCONN_SYN_8 * disconnSyn, IA_USER * iaUser, user_iter * user, uint32_t sip); + + int Process_DISCONN_ACK_6(DISCONN_ACK_6 * disconnSyn, + IA_USER * iaUser, + user_iter * user, + uint32_t sip, + map::iterator it); + int Process_DISCONN_ACK_7(DISCONN_ACK_7 * disconnSyn, + IA_USER * iaUser, + user_iter * user, + uint32_t sip, + map::iterator it); + int Process_DISCONN_ACK_8(DISCONN_ACK_8 * disconnSyn, + IA_USER * iaUser, + user_iter * user, + uint32_t sip, + map::iterator it); + + int Send_CONN_SYN_ACK_6(IA_USER * iaUser, user_iter * user, uint32_t sip); + int Send_CONN_SYN_ACK_7(IA_USER * iaUser, user_iter * user, uint32_t sip); + int Send_CONN_SYN_ACK_8(IA_USER * iaUser, user_iter * user, uint32_t sip); + + int Send_ALIVE_SYN_6(IA_USER * iaUser, uint32_t sip); + int Send_ALIVE_SYN_7(IA_USER * iaUser, uint32_t sip); + int Send_ALIVE_SYN_8(IA_USER * iaUser, uint32_t sip); + + int Send_DISCONN_SYN_ACK_6(IA_USER * iaUser, uint32_t sip); + int Send_DISCONN_SYN_ACK_7(IA_USER * iaUser, uint32_t sip); + int Send_DISCONN_SYN_ACK_8(IA_USER * iaUser, uint32_t sip); + + int Send_FIN_6(IA_USER * iaUser, uint32_t sip, map::iterator it); + int Send_FIN_7(IA_USER * iaUser, uint32_t sip, map::iterator it); + int Send_FIN_8(IA_USER * iaUser, uint32_t sip, map::iterator it); + + int Timeouter(); + + void InitEncrypt(BLOWFISH_CTX * ctx, const string & password); + void Decrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8); + void Encrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8); + + int SendError(uint32_t ip, uint16_t port, int protoVer, const string & text); + int Send(uint32_t ip, uint16_t port, const char * buffer, int len); + int RealSendMessage6(const STG_MSG & msg, uint32_t ip, IA_USER & user); + int RealSendMessage7(const STG_MSG & msg, uint32_t ip, IA_USER & user); + int RealSendMessage8(const STG_MSG & msg, uint32_t ip, IA_USER & user); + + bool WaitPackets(int sd) const; + + BLOWFISH_CTX ctxS; //for loginS + + mutable string errorStr; + AUTH_IA_SETTINGS iaSettings; + MODULE_SETTINGS settings; + + bool nonstop; + + bool isRunningRun; + bool isRunningRunTimeouter; + + USERS * users; + const SETTINGS * stgSettings; + + mutable map ip2user; + + pthread_t recvThread; + pthread_t timeouterThread; + mutable pthread_mutex_t mutex; + + int listenSocket; + + CONN_SYN_ACK_6 connSynAck6; + CONN_SYN_ACK_8 connSynAck8; + + DISCONN_SYN_ACK_6 disconnSynAck6; + DISCONN_SYN_ACK_8 disconnSynAck8; + + ALIVE_SYN_6 aliveSyn6; + ALIVE_SYN_8 aliveSyn8; + FIN_6 fin6; + FIN_8 fin8; + + map packetTypes; + + STG_LOGGER & WriteServLog; + + uint32_t enabledDirs; + + class DEL_USER_NONIFIER: public NOTIFIER_BASE + { + public: + DEL_USER_NONIFIER(AUTH_IA & a) : auth(a) {}; + virtual ~DEL_USER_NONIFIER(){}; + + void Notify(const user_iter & user) + { + auth.DelUser(user); + } + + private: + AUTH_IA & auth; + } onDelUserNotifier; + + class UnauthorizeUser : std::unary_function &, void> { + public: + UnauthorizeUser(AUTH_IA * a) : auth(a) {}; + void operator()(const std::pair & p) + { + p.second.user->Unauthorize(auth); + } + private: + AUTH_IA * auth; + }; + +}; +//----------------------------------------------------------------------------- + +#endif + + diff --git a/projects/stargazer/plugins/authorization/stress/Makefile b/projects/stargazer/plugins/authorization/stress/Makefile new file mode 100644 index 00000000..2bdcf1b0 --- /dev/null +++ b/projects/stargazer/plugins/authorization/stress/Makefile @@ -0,0 +1,14 @@ +############################################################################### +# $Id: Makefile,v 1.4 2008/12/04 17:01:28 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_auth_stress.so + +SRCS = ./stress.cpp + +LIBS += $(LIB_THREAD) + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/authorization/stress/stress.cpp b/projects/stargazer/plugins/authorization/stress/stress.cpp new file mode 100644 index 00000000..d0d86d02 --- /dev/null +++ b/projects/stargazer/plugins/authorization/stress/stress.cpp @@ -0,0 +1,422 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.5 $ + $Date: 2009/06/19 12:50:32 $ + $Author: faust $ + */ + +#include +#include +#include + +#include "stress.h" +#include "../../../user.h" + +class STRESS_CREATOR +{ +private: + AUTH_STRESS * dc; + +public: + STRESS_CREATOR() + { + printfd(__FILE__, "constructor STRESS_CREATOR\n"); + dc = new AUTH_STRESS(); + }; + ~STRESS_CREATOR() + { + printfd(__FILE__, "destructor STRESS_CREATOR\n"); + delete dc; + }; + + BASE_PLUGIN * GetPlugin() + { + return dc; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +STRESS_CREATOR stressc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// ëÌÁÓÓ ÄÌÑ ÐÏÉÓËÁ ÀÚÅÒÁ × ÓÐÉÓËÅ ÎÏÔÉÆÉËÁÔÏÒÏ× +template +class IS_CONTAINS_USER: public binary_function +{ +public: + bool operator()(varType notifier, user_iter user) const + { + return notifier.GetUser() == user; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +//printf("BASE_CAPTURER * GetCapturer()\n"); +return stressc.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +AUTH_STRESS_SETTINGS::AUTH_STRESS_SETTINGS() + : averageOnlineTime(0) +{ +} +//----------------------------------------------------------------------------- +int AUTH_STRESS_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_STRESS_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +PARAM_VALUE pv; +vector::const_iterator pvi; + +pv.param = "AverageOnlineTime"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'" + pv.param + "\' not found."; + return -1; + } + +if (ParseIntInRange(pvi->value[0], 5, 10*3600, &averageOnlineTime)) + { + errorStr = "Cannot parse parameter \'" + pv.param + "\': " + errorStr; + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_STRESS_SETTINGS::GetAverageOnlineTime() const +{ +return averageOnlineTime; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string AUTH_STRESS::GetVersion() const +{ +return "Stress authorizator v.0.1"; +} +//----------------------------------------------------------------------------- +AUTH_STRESS::AUTH_STRESS() +{ +pthread_mutex_init(&mutex, NULL); +isRunning = false; +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::SetUsers(USERS * u) +{ +users = u; +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int AUTH_STRESS::ParseSettings() +{ +int ret = stressSettings.ParseSettings(settings); +if (ret) + errorStr = stressSettings.GetStrError(); +return ret; +} +//----------------------------------------------------------------------------- +const string & AUTH_STRESS::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int AUTH_STRESS::Start() +{ +GetUsers(); +nonstop = true; + +list::iterator users_iter; + +onAddUserNotifier.SetAuthorizator(this); +onDelUserNotifier.SetAuthorizator(this); +users->AddNotifierUserAdd(&onAddUserNotifier); +users->AddNotifierUserDel(&onDelUserNotifier); + +if (!isRunning) + { + if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + return -1; + } + } + +users_iter = usersList.begin(); +while (users_iter != usersList.end()) + { + Authorize(*users_iter); + users_iter++; + } + +//isRunning = true; +return 0; +} +//----------------------------------------------------------------------------- +int AUTH_STRESS::Stop() +{ +nonstop = false; +if (isRunning) + { + //5 seconds to thread stops itself + int i; + for (i = 0; i < 25; i++) + { + if (!isRunning) + break; + stgUsleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (isRunning) + { + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + return -1; + } + printfd(__FILE__, "AUTH_STRESS killed Run\n"); + } + } + +users->DelNotifierUserAdd(&onAddUserNotifier); +users->DelNotifierUserDel(&onDelUserNotifier); + +return 0; +} +//----------------------------------------------------------------------------- +bool AUTH_STRESS::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +uint16_t AUTH_STRESS::GetStartPosition() const +{ +return 70; +} +//----------------------------------------------------------------------------- +uint16_t AUTH_STRESS::GetStopPosition() const +{ +return 70; +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::SetUserNotifiers(user_iter u) +{ +// ---------- IP ------------------- +CHG_BEFORE_NOTIFIER BeforeChgIPNotifier; +CHG_AFTER_NOTIFIER AfterChgIPNotifier; + +BeforeChgIPNotifier.SetAuthorizator(this); +BeforeChgIPNotifier.SetUser(u); +BeforeChgIPNotifierList.push_front(BeforeChgIPNotifier); + +AfterChgIPNotifier.SetAuthorizator(this); +AfterChgIPNotifier.SetUser(u); +AfterChgIPNotifierList.push_front(AfterChgIPNotifier); + +u->property.ips.AddBeforeNotifier(&(*BeforeChgIPNotifierList.begin())); +u->property.ips.AddAfterNotifier(&(*AfterChgIPNotifierList.begin())); +// ---------- IP end --------------- +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::UnSetUserNotifiers(user_iter u) +{ +// --- IP --- +IS_CONTAINS_USER > IsContainsUserIPB; +IS_CONTAINS_USER > IsContainsUserIPA; + +list >::iterator ipBIter; +list >::iterator ipAIter; + +ipBIter = find_if(BeforeChgIPNotifierList.begin(), + BeforeChgIPNotifierList.end(), + bind2nd(IsContainsUserIPB, u)); + +if (ipBIter != BeforeChgIPNotifierList.end()) + { + ipBIter->GetUser()->property.ips.DelBeforeNotifier(&(*ipBIter)); + BeforeChgIPNotifierList.erase(ipBIter); + } + +ipAIter = find_if(AfterChgIPNotifierList.begin(), + AfterChgIPNotifierList.end(), + bind2nd(IsContainsUserIPA, u)); + +if (ipAIter != AfterChgIPNotifierList.end()) + { + ipAIter->GetUser()->property.ips.DelAfterNotifier(&(*ipAIter)); + AfterChgIPNotifierList.erase(ipAIter); + } +// --- IP end --- +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::GetUsers() +{ +user_iter u; +printfd(__FILE__, "users->OpenSearch() usernum=%d\n", users->GetUserNum()); +int h = users->OpenSearch(); +if (!h) + { + printfd(__FILE__, "users->OpenSearch() error\n"); + return; + } + +while (1) + { + if (users->SearchNext(h, &u)) + { + break; + } + usersList.push_back(u); + SetUserNotifiers(u); + } + +users->CloseSearch(h); +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::Unauthorize(user_iter u) const +{ +if (!u->IsAuthorizedBy(this)) + return; + +printfd(__FILE__, "Unauthorized user %s\n", u->GetLogin().c_str()); +u->Unauthorize(this); +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::Authorize(user_iter u) const +{ +USER_IPS ips = u->property.ips; +if (ips.OnlyOneIP() && !u->IsAuthorizedBy(this)) + { + if (u->Authorize(ips[0].ip, "", 0xFFffFFff, this) == 0) + { + printfd(__FILE__, "Authorized user %s\n", u->GetLogin().c_str()); + } + } +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::AddUser(user_iter u) +{ +//printfd(__FILE__, "User added to list %s\n", u->GetLogin().c_str()); +SetUserNotifiers(u); +usersList.push_back(u); +} +//----------------------------------------------------------------------------- +void AUTH_STRESS::DelUser(user_iter u) +{ +Unauthorize(u); +UnSetUserNotifiers(u); + +list::iterator users_iter; +users_iter = usersList.begin(); + +while (users_iter != usersList.end()) + { + if (u == *users_iter) + { + usersList.erase(users_iter); + printfd(__FILE__, "User removed from list %s\n", u->GetLogin().c_str()); + break; + } + users_iter++; + } +} +//----------------------------------------------------------------------------- +int AUTH_STRESS::SendMessage(const STG_MSG & msg, uint32_t ip) const +{ +errorStr = "Authorization modele \'AUTH_STRESS\' does not support sending messages"; +return -1; +} +//----------------------------------------------------------------------------- +void * AUTH_STRESS::Run(void * d) +{ +AUTH_STRESS * ia; +ia = (AUTH_STRESS *)d; + +ia->isRunning = true; + +while (ia->nonstop) + { + printfd(__FILE__, "AUTH_STRESS::Run\n"); + + list::iterator users_iter; + users_iter = ia->usersList.begin(); + while (users_iter != ia->usersList.end()) + { + if (random() % 2*ia->stressSettings.GetAverageOnlineTime() == 1) + { + ia->Authorize(*users_iter); + printfd(__FILE__, "AUTH_STRESS::Authorize\n"); + } + if (random() % 2*ia->stressSettings.GetAverageOnlineTime() == 2) + { + ia->Unauthorize(*users_iter); + printfd(__FILE__, "AUTH_STRESS::Unauthorize\n"); + } + + users_iter++; + } + + sleep(1); + } + +ia->isRunning = false; +return NULL; +} + +//----------------------------------------------------------------------------- +template +void CHG_BEFORE_NOTIFIER::Notify(const varParamType & oldValue, const varParamType & newValue) +{ +auth->Unauthorize(user); +} +//----------------------------------------------------------------------------- +template +void CHG_AFTER_NOTIFIER::Notify(const varParamType & oldValue, const varParamType & newValue) +{ +auth->Unauthorize(user); +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/authorization/stress/stress.h b/projects/stargazer/plugins/authorization/stress/stress.h new file mode 100644 index 00000000..1caf3a47 --- /dev/null +++ b/projects/stargazer/plugins/authorization/stress/stress.h @@ -0,0 +1,175 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.3 $ + $Date: 2009/06/19 12:50:32 $ + $Author: faust $ + */ + + +#ifndef STRESS_H +#define STRESS_H + +#include +#include +#include "base_auth.h" +#include "notifer.h" +#include "user_ips.h" +#include "../../../users.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +class AUTH_STRESS; +//----------------------------------------------------------------------------- +template +class CHG_BEFORE_NOTIFIER: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const varParamType & oldValue, const varParamType & newValue); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() {return user; } + void SetAuthorizator(const AUTH_STRESS * a) { auth = a; } + +private: + user_iter user; + const AUTH_STRESS * auth; +}; +//----------------------------------------------------------------------------- +template +class CHG_AFTER_NOTIFIER: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const varParamType & oldValue, const varParamType & newValue); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() {return user; } + void SetAuthorizator(const AUTH_STRESS * a) { auth = a; } + +private: + user_iter user; + const AUTH_STRESS * auth; +}; +//----------------------------------------------------------------------------- +class AUTH_STRESS_SETTINGS +{ +public: + AUTH_STRESS_SETTINGS(); + const string& GetStrError() const { return errorStr; } + int ParseSettings(const MODULE_SETTINGS & s); + int GetAverageOnlineTime() const; +private: + int ParseIntInRange(const string & str, int min, int max, int * val); + int averageOnlineTime; + string errorStr; +}; +//----------------------------------------------------------------------------- +class AUTH_STRESS :public BASE_AUTH +{ +public: + AUTH_STRESS(); + virtual ~AUTH_STRESS(){}; + + void SetUsers(USERS * u); + void SetTariffs(TARIFFS * t){}; + void SetAdmins(ADMINS * a){}; + void SetTraffcounter(TRAFFCOUNTER * tc){}; + void SetStore(BASE_STORE * ){}; + void SetStgSettings(const SETTINGS *){}; + + int Start(); + int Stop(); + bool IsRunning(); + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + + void AddUser(user_iter u); + void DelUser(user_iter u); + + void Authorize(user_iter u) const; + void Unauthorize(user_iter u) const; + + int SendMessage(const STG_MSG & msg, uint32_t ip) const; + +private: + void GetUsers(); + void SetUserNotifiers(user_iter u); + void UnSetUserNotifiers(user_iter u); + + bool nonstop; + + static void * Run(void *); + + mutable string errorStr; + AUTH_STRESS_SETTINGS stressSettings; + USERS * users; + list usersList; + bool isRunning; + MODULE_SETTINGS settings; + + pthread_t thread; + pthread_mutex_t mutex; + + list > BeforeChgIPNotifierList; + list > AfterChgIPNotifierList; + + class ADD_USER_NONIFIER: public NOTIFIER_BASE + { + public: + ADD_USER_NONIFIER(){}; + virtual ~ADD_USER_NONIFIER(){}; + + void SetAuthorizator(AUTH_STRESS * a) { auth = a; } + void Notify(const user_iter & user) + { + auth->AddUser(user); + } + + private: + AUTH_STRESS * auth; + } onAddUserNotifier; + + class DEL_USER_NONIFIER: public NOTIFIER_BASE + { + public: + DEL_USER_NONIFIER(){}; + virtual ~DEL_USER_NONIFIER(){}; + + void SetAuthorizator(AUTH_STRESS * a) { auth = a; } + void Notify(const user_iter & user) + { + auth->DelUser(user); + } + + private: + AUTH_STRESS * auth; + } onDelUserNotifier; + +}; +//----------------------------------------------------------------------------- + +#endif + + diff --git a/projects/stargazer/plugins/capture/cap_debug/Makefile b/projects/stargazer/plugins/capture/cap_debug/Makefile new file mode 100644 index 00000000..ade37382 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/Makefile @@ -0,0 +1,24 @@ +############################################################################### +# $Id: Makefile,v 1.10 2008/12/04 17:03:25 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_cap_debug.so + +SRCS = ./debug_cap.cpp \ + ./checksum.c \ + ./icmp.c \ + ./ip.c \ + ./misc.c \ + ./packet.c \ + ./socket.c \ + ./tcp.c \ + ./udp.c + +STGLIBS = -lstg_common + +LIBS += $(LIB_THREAD) + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/capture/cap_debug/checksum.c b/projects/stargazer/plugins/capture/cap_debug/checksum.c new file mode 100644 index 00000000..28cef341 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/checksum.c @@ -0,0 +1,47 @@ +/*$Id: checksum.c,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +unsigned short +in_cksum(unsigned short *addr, int len) +{ + int nleft = len; + int sum = 0; + unsigned short *w = addr; + unsigned short answer = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), we add + * sequential 16 bit words to it, and at the end, fold back all the + * carry bits from the top 16 bits into the lower 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) { + *(unsigned char *)(&answer) = *(unsigned char *)w ; + sum += answer; + } + + /* add back carry outs from top 16 bits to low 16 bits */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return(answer); +} diff --git a/projects/stargazer/plugins/capture/cap_debug/checksum.h b/projects/stargazer/plugins/capture/cap_debug/checksum.h new file mode 100644 index 00000000..2698a526 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/checksum.h @@ -0,0 +1,20 @@ +/* $Id: checksum.h,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +unsigned short in_cksum(unsigned short *addr, int len); diff --git a/projects/stargazer/plugins/capture/cap_debug/constants.h b/projects/stargazer/plugins/capture/cap_debug/constants.h new file mode 100644 index 00000000..e845a44c --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/constants.h @@ -0,0 +1,106 @@ +/* $Id: constants.h,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* +* socket types +*/ +#define PKT_RAW SOCK_RAW +#define PKT_STREAM SOCK_STREAM +#define PKT_DGRAM SOCK_DGRAM + +/* +* Link Layer +*/ +#define PKT_LINK_ARP 0x01 +#define PKT_LINK_RARP 0x02 + +/* +* Network Layer +*/ +#define PKT_NET_IP 0x01 +#define PKT_NET_ICMP 0x02 +#define PKT_NET_IGMP 0x04 + +/* +* Transport layer +*/ +#define PKT_TRANS_TCP 0x01 +#define PKT_TRANS_UDP 0x02 + +/* --- [ IP ] ------------------------ */ +/* IP options */ +#define PKT_IP_OPT_EOL 0 /* end of option list */ +#define PKT_IP_OPT_END PKT_IP_OPT_EOL +#define PKT_IP_OPT_NOP 1 /* no operation */ +#define PKT_IP_OPT_NOOP PKT_IP_OPT_NOP + +#define PKT_IP_OPT_RR 7 /* record packet route */ +#define PKT_IP_OPT_TS 68 /* timestamp */ +#define PKT_IP_OPT_TIMESTAMP PKT_IP_OPT_TS +#define PKT_IP_OPT_SECURITY 130 /* provide s,c,h,tcc */ +#define PKT_IP_OPT_SEC PKT_IP_OPT_SECURITY +#define PKT_IP_OPT_LSRR 131 /* loose source route */ +#define PKT_IP_OPT_SATID 136 /* satnet id */ +#define PKT_IP_OPT_SID PKT_IP_OPT_SATID +#define PKT_IP_OPT_SSRR 137 /* strict source route */ +#define PKT_IP_OPT_RA 148 /* router alert */ + +/* flag bits for ipt_flg */ +#define PKT_IP_OPT_TS_TSONLY 0 /* timestamps only */ +#define PKT_IP_OPT_TS_TSANDADDR 1 /* timestamps and addresses */ +#define PKT_IP_OPT_TS_PRESPEC 3 /* specified modules only */ + +/* --- [ TCP ] ------------------------ */ +/* tcp flags */ +#ifndef __FAVOUR_BSD +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#endif +/* additional flags */ +#define TH_XMAS 0x40 +#define TH_YMAS 0x80 + +/* tcp options */ +#define PKT_TCP_OPT_END 0x00 +#define PKT_TCP_OPT_NOP 0x01 +#define PKT_TCP_OPT_MSS 0x02 +#define PKT_TCP_OPT_WSF 0x03 /*window scale factor*/ +#define PKT_TCP_OPT_SACK_PERM 0x04 +#define PKT_TCP_OPT_SACK 0x05 +#define PKT_TCP_OPT_TIME 0x08 /* timestamp option */ + +/* tcp option lenghts */ +#define PKT_TCP_OPT_END_LEN 0x01 +#define PKT_TCP_OPT_NOP_LEN 0x01 +#define PKT_TCP_OPT_MSS_LEN 0x04 +#define PKT_TCP_OPT_WSF_LEN 0x03 /*window scale factor*/ +#define PKT_TCP_OPT_SACK_PERM_LEN 0x02 +#define PKT_TCP_OPT_SACK_LEN 0x01 +#define PKT_TCP_OPT_TIME_LEN 0x0a /* timestamp option */ + +/* return values and errors */ +#define PKTOK 0 +#define EPKTRANGE -64 +#define EERRNO -63 /* errno has been set */ +#define EPKTINVALPTR -62 +#define EPKTUNKNOWNTYPE -61 diff --git a/projects/stargazer/plugins/capture/cap_debug/debug_cap.cpp b/projects/stargazer/plugins/capture/cap_debug/debug_cap.cpp new file mode 100644 index 00000000..799e399d --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/debug_cap.cpp @@ -0,0 +1,519 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +Date: 18.09.2002 +*/ + +/* +* Author : Boris Mikhailenko +*/ + +/* +$Revision: 1.21 $ +$Date: 2009/03/19 20:03:35 $ +$Author: faust $ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug_cap.h" +#include "../../../traffcounter.h" +#include "libpal.h" + +//----------------------------------------------------------------------------- +void WriteStat(uint32_t u, uint32_t d) +{ +FILE * f; +f = fopen("/tmp/cap.stat", "at"); +fprintf(f, "up %5.2f Mbit, down %5.2f Mbit, sum %5.2f Mbit\n", + u / (1000000*8.0), + d / (1000000*8.0), + (u + d) / (1000000*8.0)); +fclose(f); +} +//----------------------------------------------------------------------------- + +class CAP_DEBUG_CREATOR +{ +private: + DEBUG_CAP * dc; + +public: + CAP_DEBUG_CREATOR() + : dc(new DEBUG_CAP()) + { + }; + ~CAP_DEBUG_CREATOR() + { + delete dc; + }; + + DEBUG_CAP * GetCapturer() + { + return dc; + }; +}; +//----------------------------------------------------------------------------- +RAW_PACKET MakeTCPPacket(const char * src, + const char * dst, + uint16_t sport, + uint16_t dport, + uint16_t len); +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CAP_DEBUG_CREATOR cdc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return cdc.GetCapturer(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string DEBUG_CAP::GetVersion() const +{ +return "Debug_cap v.0.01a"; +} +//----------------------------------------------------------------------------- +DEBUG_CAP::DEBUG_CAP() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void DEBUG_CAP::SetTraffcounter(TRAFFCOUNTER * tc) +{ +traffCnt = tc; +} +//----------------------------------------------------------------------------- +const string & DEBUG_CAP::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int DEBUG_CAP::Start() +{ +if (isRunning) + return 0; + +printfd(__FILE__, "DEBUG_CAP::Start()\n"); + +nonstop = true; + +if (pthread_create(&thread, NULL, Run1, this) == 0) + { + return 0; + } + +errorStr = "Cannot create thread."; +return -1; +} +//----------------------------------------------------------------------------- +int DEBUG_CAP::Stop() +{ +if (!isRunning) + return 0; + +nonstop = false; + +//5 seconds to thread stops itself +int i; +for (i = 0; i < 25; i++) + { + if (!isRunning) + break; + + stgUsleep(200000); + //printf("."); + } + +/*if (i) + printf("\n");*/ + +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + //TODO pthread_cancel() + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + return -1; + } + } + +return 0; +} +//----------------------------------------------------------------------------- +bool DEBUG_CAP::IsRunning() +{ +return nonstop; +} +//----------------------------------------------------------------------------- +void * DEBUG_CAP::Run1(void * data) +{ +printfd(__FILE__, "=====================| pid: %d |===================== \n", getpid()); + +DEBUG_CAP * dc = (DEBUG_CAP *)data; +dc->isRunning = true; + +RAW_PACKET rp; +rp = MakeTCPPacket("192.168.1.1", "192.168.1.21", 255, 255, 200); +int a = 0; +sleep(3); + +struct tm * tm; +time_t t; +uint32_t u = 0; +uint32_t d = 0; + +//2 upload : 3 download + +int usize; +int dsize; + +t = stgTime; +tm = localtime(&t); +int min = tm->tm_min; +int sec = tm->tm_sec; + +char cliIP[20]; +char srvIP[20]; +char trashIP1[20]; +char trashIP2[20]; + +while (dc->nonstop) + { + for (int i = 8; i <= 252; i++) + { + + usize = random()%100 + 100; + dsize = random()%500 + 900; + + for (int j = 2; j < 11; j++) + { + sprintf(cliIP, "192.168.%d.%d", j, i); + sprintf(srvIP, "10.1.%d.%d", random()%8, 1); + + rp = MakeTCPPacket(srvIP, cliIP, 80, random()%2 + 2000, dsize); + d += dsize; + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(cliIP, srvIP, random()%2 + 2000, 80, usize); + u += usize; + dc->traffCnt->Process(rp); + } + } + + usleep(100000); + t = stgTime; + + if (min != localtime(&t)->tm_min) + { + min = localtime(&t)->tm_min; + WriteStat(u, d); + u = d = 0; + } + + a++; + + } + +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +void * DEBUG_CAP::Run2(void * data) +{ +printfd(__FILE__, "=====================| pid: %d |===================== \n", getpid()); + +DEBUG_CAP * dc = (DEBUG_CAP *)data; +dc->isRunning = true; + +RAW_PACKET rp; +rp = MakeTCPPacket("192.168.1.1", "192.168.1.21", 255, 255, 200); +int a = 0; +sleep(3); + +struct tm * tm; +time_t t; +uint32_t u = 0; +uint32_t d = 0; + +//2 upload : 3 download + +int usize = 200; +int dsize = 1500; + +t = stgTime; +tm = localtime(&t); +int min = tm->tm_min; + +char cliIP[20]; +char srvIP[20]; + +while (dc->nonstop) + { + for (int i = 101; i <= 150; i++) + { + sprintf(cliIP, "192.168.1.%d", i); + for (int dp = 0; dp < 1; dp++) + { + //sprintf(srvIP, "10.1.%d.%d", i, 10 + dp); + sprintf(srvIP, "10.1.%d.%d", i, 10 + dp); + + rp = MakeTCPPacket(srvIP, cliIP, 80, 10000 + i + dp, dsize); + d += dsize; + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(srvIP, cliIP, 80, 10000 + i + dp, dsize); + d += dsize; + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(srvIP, cliIP, 80, 10000 + i + dp, dsize); + dc->traffCnt->Process(rp); + d += dsize; + + + rp = MakeTCPPacket(cliIP, srvIP, 10000 + i + dp, 80, usize); + u += usize; + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(cliIP, srvIP, 10000 + i + dp, 80, usize); + u += usize; + dc->traffCnt->Process(rp); + } + } + + //usleep(20000); + t = stgTime; + + if (min != localtime(&t)->tm_min) + { + min = localtime(&t)->tm_min; + WriteStat(u, d); + u = d = 0; + } + + a++; + + } + +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +void * DEBUG_CAP::Run3(void * data) +{ +printfd(__FILE__, "=====================| pid: %d |===================== \n", getpid()); + +DEBUG_CAP * dc = (DEBUG_CAP *)data; +dc->isRunning = true; + +RAW_PACKET rp; +rp = MakeTCPPacket("192.168.1.1", "192.168.1.21", 255, 255, 200); +int a = 0; +sleep(3); + +struct tm * tm; +time_t t; +uint32_t u = 0; +uint32_t d = 0; + +//2 upload : 3 download + +int usize = 200; +int dsize = 1500; + +t = stgTime; +tm = localtime(&t); + +char cliIP[20]; +char srvIP1[20]; +char srvIP2[20]; +char srvIP3[20]; + +int firstTime = true; + +while (dc->nonstop) + { + if (firstTime) + { + sprintf(srvIP1, "10.1.%d.%d", random() % 14 + 153, random() % 11 + 35); + + sprintf(srvIP2, "%d.%d.%d.%d", + random() % 20 + 81, + random() % 28 + 153, + random() % 28 + 37, + random() % 28 + 13); + + sprintf(srvIP3, "%d.%d.%d.%d", + random() % 20 + 81, + random() % 28 + 153, + random() % 28 + 37, + random() % 28 + 13); + + printfd(__FILE__, "firstTime=false\n"); + firstTime = false; + } + + int rnd = random() % 400; + if (rnd < 5) + { + sprintf(srvIP1, "10.1.%d.%d", random() % 14 + 153, random() % 11 + 35); + printfd(__FILE__, "srvIP1=%s\n", srvIP1); + } + if (rnd == 9) + { + sprintf(srvIP2, "%d.%d.%d.%d", + random() % 20 + 81, + random() % 28 + 153, + random() % 28 + 37, + random() % 28 + 13); + printfd(__FILE__, "srvIP2=%s\n", srvIP2); + } + if (rnd == 5) + { + sprintf(srvIP2, "%d.%d.%d.%d", + random() % 20 + 81, + random() % 28 + 153, + random() % 28 + 37, + random() % 28 + 13); + printfd(__FILE__, "srvIP3=%s\n", srvIP3); + } + + for (int i = 2; i < 52; i++) + { + sprintf(cliIP, "192.168.1.%d", i); + for (int dp = 0; dp < 1; dp++) + { + usize = 50 + random() % 100; + dsize = 1000 + random() % 400; + + rp = MakeTCPPacket(srvIP1, cliIP, 80, 10000 + i + dp, dsize); + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(srvIP2, cliIP, 80, 10000 + i + dp, dsize); + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(srvIP3, cliIP, 80, 10000 + i + dp, dsize); + dc->traffCnt->Process(rp); + + + rp = MakeTCPPacket(cliIP, srvIP1, 10000 + i + dp, 80, usize); + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(cliIP, srvIP2, 10000 + i + dp, 80, usize); + dc->traffCnt->Process(rp); + + rp = MakeTCPPacket(cliIP, srvIP3, 10000 + i + dp, 80, usize); + dc->traffCnt->Process(rp); + } + } + + usleep(300000); + /*t = stgTime; + + if (min != localtime(&t)->tm_min) + { + min = localtime(&t)->tm_min; + WriteStat(u, d); + u = d = 0; + }*/ + + a++; + + } + +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t DEBUG_CAP::GetStartPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +uint16_t DEBUG_CAP::GetStopPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +RAW_PACKET MakeTCPPacket(const char * src, + const char * dst, + uint16_t sport, + uint16_t dport, + uint16_t len) +{ +struct packet pkt; +if (pkt_init(&pkt, 0, 100)) + { + printfd(__FILE__, "pkt_init error!\n"); + } + +in_addr_t sip = inet_addr(src); +in_addr_t dip = inet_addr(dst); + +pkt_ip_header(&pkt, + 5, //header len + 4, + 0, + len, //total len + 0, + 0, + 64, //ttl + 6, //TCP + 0, + *(uint32_t*)&sip, + *(uint32_t*)&dip); + +pkt_move_actptr(&pkt, 20); + +pkt_tcp_header(&pkt, + sport, + dport, + 1, // seq, + 1, // ackseq, + 5, // headerlen, + 0, // reserved, + 0, // flags, + 0, // window, + 0, // checksum, + 0); // urgent + +RAW_PACKET rp; +memcpy(&rp, pkt.pkt, sizeof(rp)); + +if (pkt_free(&pkt)) + { + printfd(__FILE__, "pkt_free error!\n"); + } +rp.dataLen = -1; +strcpy(rp.iface, "eth0"); +return rp; +} +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/plugins/capture/cap_debug/debug_cap.h b/projects/stargazer/plugins/capture/cap_debug/debug_cap.h new file mode 100644 index 00000000..35bcec47 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/debug_cap.h @@ -0,0 +1,102 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +Date: 18.09.2002 +*/ + +/* +* Author : Boris Mikhailenko +*/ + +/* +$Revision: 1.12 $ +$Date: 2009/06/23 11:32:27 $ +$Author: faust $ +*/ + +#include +#include + +#include "os_int.h" +#include "base_plugin.h" +#include "base_settings.h" + +using namespace std; +extern "C" BASE_PLUGIN * GetPlugin(); + +//----------------------------------------------------------------------------- +struct iphdr_eth +{ + uint8_t ihl:4, + version:4; + uint8_t tos; + uint16_t tot_len; + uint16_t id; + uint16_t frag_off; + uint8_t ttl; + uint8_t protocol; + uint16_t check; + uint32_t saddr; + uint32_t daddr; + int32_t len; + char iface[10]; +}; +//----------------------------------------------------------------------------- +class CAP_SETTINGS//: public BASE_SETTINGS +{ +public: + const string& GetStrError() const { static string s; return s; } + int ParseSettings(const MODULE_SETTINGS & s) { return 0; }; +}; +//----------------------------------------------------------------------------- +class DEBUG_CAP :public BASE_PLUGIN +{ +public: + DEBUG_CAP(); + virtual ~DEBUG_CAP(){}; + + void SetUsers(USERS * u){}; + void SetTariffs(TARIFFS * t){}; + void SetAdmins(ADMINS * a){}; + void SetTraffcounter(TRAFFCOUNTER * tc); + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS *){}; + + int Start(); + int Stop(); + int Reload() { return 0; }; + int ParseSettings() { return 0; }; + void SetSettings(const MODULE_SETTINGS & s){}; + bool IsRunning(); + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; +private: + static void * Run1(void *); + static void * Run2(void *); + static void * Run3(void *); + mutable string errorStr; + CAP_SETTINGS capSettings; + pthread_t thread; + bool nonstop; + bool isRunning; + + TRAFFCOUNTER * traffCnt; +}; +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/capture/cap_debug/icmp.c b/projects/stargazer/plugins/capture/cap_debug/icmp.c new file mode 100644 index 00000000..abbaef10 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/icmp.c @@ -0,0 +1,154 @@ +/* $Id: icmp.c,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "libpal.h" + +int +pkt_icmp_header(struct packet *pkt, unsigned char type, unsigned char code, unsigned short int checksum) +{ + + struct icmp *icmp; + if (pkt) { + icmp = (struct icmp *) pkt->pkt_ptr; + icmp->icmp_type = type; + icmp->icmp_code = code; + icmp->icmp_cksum = htons(checksum); + return 0; + } else + return EPKTINVALPTR; +} + +int pkt_icmp_addr_mask(struct packet *pkt, unsigned short int id, unsigned short int seqno, unsigned int mask, char *cmask) +{ + struct icmp *icmp; + struct in_addr inetaddr; + + if (pkt) { + icmp = (struct icmp *)pkt->pkt_ptr; + } else + return EPKTINVALPTR; + icmp->icmp_id = htons(id); + icmp->icmp_seq = htons(seqno); + if (!cmask) { + icmp->icmp_mask = htons(mask); + } else { + if (inet_aton(cmask, &inetaddr) != 0) { + icmp->icmp_mask = inetaddr.s_addr; + } else + return EERRNO; + } + return 0; +} + +int +pkt_icmp_cksum(struct packet *pkt, unsigned int len) +{ + struct icmp *icmp; + + if (!pkt) + return EPKTINVALPTR; + + icmp = (struct icmp *) pkt->pkt_ptr; + icmp->icmp_cksum = 0; + icmp->icmp_cksum = in_cksum((unsigned short *)icmp, len); + return 0; +} + +int +pkt_icmp_dest_unreach(struct packet *pkt, unsigned int unused) +{ + struct icmp *icmp; + + if (pkt) { + icmp = (struct icmp *)pkt->pkt_ptr; + } else + return EPKTINVALPTR; + icmp->icmp_void = htons(unused); + return 0; +} + +int +pkt_icmp_source_quench(struct packet *pkt, unsigned int unused) +{ + struct icmp *icmp; + + if (pkt) { + icmp = (struct icmp *)pkt->pkt_ptr; + } else + return EPKTINVALPTR; + icmp->icmp_void = htons(unused); + return 0; +} + +int +pkt_icmp_redirect(struct packet *pkt, unsigned int routerip, char *crouterip) +{ + struct icmp *icmp; + struct in_addr inetaddr; + + if (pkt) { + icmp = (struct icmp *)pkt->pkt_ptr; + } else { + return EPKTINVALPTR; + } + if (crouterip) { + if (inet_aton(crouterip, &inetaddr) != 0) { + icmp->icmp_gwaddr = inetaddr; + } else + return EERRNO; + } else { + inetaddr.s_addr = htons(routerip); + icmp->icmp_gwaddr = inetaddr; + } + return 0; +} + +int +pkt_icmp_echo(struct packet *pkt, unsigned short int id, unsigned short int seqno, void *data, size_t data_len) +{ + struct icmp *icmp; + + if (pkt) { + icmp = (struct icmp *) pkt->pkt_ptr; + } else + return EPKTINVALPTR; + icmp->icmp_id = htons(id); + icmp->icmp_seq = htons(seqno); + if (data) { + memcpy(icmp->icmp_data, data, data_len); + } + return 0; +} + +int +pkt_icmp_timestamp(struct packet *pkt, unsigned short int id, unsigned short int seqno, unsigned int ts_otime, unsigned int ts_rtime, unsigned int ts_ttime) +{ + struct icmp *icmp; + + if (pkt) + icmp = (struct icmp *) pkt->pkt_ptr; + else + return EPKTINVALPTR; + icmp->icmp_id = htons(id); + icmp->icmp_seq = htons(seqno); + icmp->icmp_otime = htons(ts_otime); + icmp->icmp_rtime = htons(ts_rtime); + icmp->icmp_ttime = htons(ts_ttime); + return 0; +} diff --git a/projects/stargazer/plugins/capture/cap_debug/ip.c b/projects/stargazer/plugins/capture/cap_debug/ip.c new file mode 100644 index 00000000..bc632f37 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/ip.c @@ -0,0 +1,124 @@ +/* $Id: ip.c,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "libpal.h" + +int +pkt_ip_header(struct packet *pkt, + unsigned int iphdr_len, + unsigned int version, + unsigned char tos, + unsigned short int total_len, + unsigned short int id, + unsigned short int frag_off, + unsigned char ttl, + unsigned char protocol, + unsigned short int cksum, + unsigned int saddr, + unsigned int daddr) +{ + struct ip *ip; + + if (!pkt) + return EPKTINVALPTR; + + ip = (struct ip *) pkt->pkt_ptr; + + ip->ip_hl = iphdr_len; + ip->ip_v = version; + ip->ip_tos = tos; + ip->ip_len = htons(total_len); + ip->ip_id = htons(id); + ip->ip_off = htons(frag_off); + ip->ip_ttl = ttl; + ip->ip_p = protocol; + ip->ip_src.s_addr = saddr; + ip->ip_dst.s_addr = daddr; + + return 0; +} + +int +pkt_ip_option_header(struct packet *pkt, unsigned char type, unsigned char len, unsigned char ptr, unsigned char oflw_flg, void *optval, size_t optlen) +{ + unsigned char *vp; + + if (!pkt) + return EPKTINVALPTR; + + vp = pkt->pkt_ptr; + *vp = type; + switch (type) { + case PKT_IP_OPT_END: + break; + case PKT_IP_OPT_NOP: + break; + case PKT_IP_OPT_SEC: + if ((pkt->pkt_pos + 2 + optlen) <= pkt->pkt_size) { + *(vp+1) = len; + memcpy((void*)(vp+2), optval, optlen); + } else { + return EPKTRANGE; + } + break; + case PKT_IP_OPT_RR: + case PKT_IP_OPT_LSRR: + case PKT_IP_OPT_SSRR: + case PKT_IP_OPT_SID: + if ((pkt->pkt_pos + 3 + optlen) <= pkt->pkt_size) { + *(vp+1) = len; + *(vp+2) = ptr; + memcpy((void*)(vp+3), optval, optlen); + } else { + return EPKTRANGE; + } + break; + case PKT_IP_OPT_TS: + if ((pkt->pkt_pos + 4 + optlen) <= pkt->pkt_size) { + *(vp+1) = len; + *(vp+2) = ptr; + *(vp+3) = oflw_flg; + memcpy((void*)(vp+4), optval, optlen); + } else { + return EPKTRANGE; + } + break; + default: + return EPKTUNKNOWNTYPE; + } + return PKTOK; +} + +int +pkt_ip_cksum(struct packet *pkt) +{ + /* + * checksum should be calculated by kernel + * when we are using SOCK_RAW access. + */ + struct ip *ip; + + if (!pkt) + return EPKTINVALPTR; + + ip = (struct ip *) pkt->pkt_ptr; + ip->ip_sum = 0; + ip->ip_sum = in_cksum((unsigned short *)ip, sizeof(struct ip)); + return 0; +} diff --git a/projects/stargazer/plugins/capture/cap_debug/libpal.h b/projects/stargazer/plugins/capture/cap_debug/libpal.h new file mode 100644 index 00000000..cbd239fc --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/libpal.h @@ -0,0 +1,130 @@ +/* $Id: libpal.h,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _LIBPAL_H_ +#define _LIBPAL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "checksum.h" +#include "constants.h" + +/* +* generic packet structure +* +* we maintain a so called "active ptr" inside the packet. Most operations +* on the packet will take place at the position the pointer points at. +* The pointer should never be moved directly, instead the functions +* pkt_set_actpr() and pkt_move_actptr() are provided. +* Using this approach we can perform bounds checking and make sure our +* library functions won't segfault. +* pkt_type is not really being used at the moment. I don't know if it +* ever will be. It might just be left out one day, so keep your fingers +* off. Use pkt_init() to set it. And dont even think of messing with the +* packet payload pointer pkt. You do NOT want to do that. Use the provided +* library functions. +* To be clear: don't do _anything_ with this struct. Just pass it as a +* parameter, be happy and your programs will (hopefully) work. +*/ +struct packet { + unsigned long pkt_type; + unsigned char *pkt; + unsigned int pkt_size; + unsigned char *pkt_ptr; /* active ptr inside packet */ + unsigned int pkt_pos; /* pkt_ptr position, starting at 0 */ +}; + +/* +* our socket structure +* +* same as above. Use the provided library fuctions to change its values. +* Do not do it on your own. Live and let die. You have been warned. +*/ +struct pkt_socket { + int rawfd; + struct sockaddr_in *sckad; + /*struct sockaddr_ll *sckll;*/ + socklen_t sckad_len; +}; + +/* memory management */ +int pkt_init(struct packet *pkt, unsigned long type, unsigned int size); +int pkt_free(struct packet *pkt); + +/* pointer movement */ +int pkt_set_actptr(struct packet *pkt, unsigned int bytepos); +int pkt_move_actptr(struct packet *pkt, int relmov); + +/* raw data */ +int pkt_add_systimestamp(struct packet *pkt); +int pkt_add_data(struct packet *pkt, char *data, size_t data_len); +int pkt_resize(struct packet *pkt, unsigned int newsize); + +/* socket operations */ +int pkt_socket_open(struct pkt_socket *sck, int type); +int pkt_socket_close(struct pkt_socket *sck); +int pkt_socket_prepare(struct pkt_socket *sck, char *daddr); +int pkt_socket_setopt(struct pkt_socket *sck, int level, int optname, void *optval, socklen_t optlen); + +/* sending */ +int pkt_send(struct packet *pkt, struct pkt_socket *sck); + +/* IP */ +int pkt_ip_header(struct packet *pkt, unsigned int iphdr_len, unsigned int version, unsigned char tos, unsigned short int total_len, unsigned short int id, unsigned short int frag_off /* 3bit flag, 13bit offset */, unsigned char ttl, unsigned char protocol, unsigned short int cksum, unsigned int saddr, unsigned int daddr); +int pkt_ip_cksum(struct packet *pkt); +int pkt_ip_option_header(struct packet *pkt, unsigned char type, unsigned char len, unsigned char ptr, unsigned char oflw_flg, void *optval, size_t optlen); + +/* TCP */ +int pkt_tcp_header(struct packet *pkt, unsigned short int sport, unsigned short int dport, unsigned int seq, unsigned int ackseq, unsigned char headerlen, unsigned char reserved, unsigned char flags, unsigned short window, unsigned short int checksum, unsigned short int urgent); +int pkt_tcp_cksum(struct packet *pkt, char *saddr, char *daddr, unsigned int tcp_pkt_size); +int pkt_tcp_option(struct packet *pkt, unsigned char kind, unsigned char len, void *optval, size_t optlen); + +/* UDP */ +int pkt_udp_header(struct packet *pkt, unsigned short int sport, unsigned short int dport, unsigned short int udp_total_len, unsigned short int checksum); +int pkt_udp_cksum(struct packet *pkt, char *saddr, char *daddr, unsigned int udp_total_len); + +/* ICMP */ +int pkt_icmp_header(struct packet *pkt, unsigned char type, unsigned char code, unsigned short int checksum); +int pkt_icmp_cksum(struct packet *pkt, unsigned int len); +int pkt_icmp_addr_mask(struct packet *pkt, unsigned short int id, unsigned short int seqno, unsigned int mask, char *cmask); +int pkt_icmp_dest_unreach(struct packet *pkt, unsigned int unused); +int pkt_icmp_source_quench(struct packet *pkt, unsigned int unused); +int pkt_icmp_redirect(struct packet *pkt, unsigned int routerip, char *crouterip); +int pkt_icmp_echo(struct packet *pkt, unsigned short int id, unsigned short int seqno, void *data, size_t data_len); +int pkt_icmp_timestamp(struct packet *pkt, unsigned short int id, unsigned short int seqno, unsigned int ts_otime, unsigned int ts_rtime, unsigned int ts_ttime); + +/* functions that might be useful and might be added some day ... +int pkt_shift_data(struct packet *pkt, unsigned int from, unsigned int to, unsigned int len); +int pkt_tcp_change_seqno(int rel_seq, int rel_ackseq); +int pkt_tcp_set_seqno(unsigned int seq, unsigned int ackseq); +int pkt_ip_option(struct packet *pkt, unsigned char code, unsigned char len, unsigned char ptr); +int pkt_ip_option_addval(struct *pkt, unsigned char posptr, unsigned int optval); +*/ + +#endif diff --git a/projects/stargazer/plugins/capture/cap_debug/misc.c b/projects/stargazer/plugins/capture/cap_debug/misc.c new file mode 100644 index 00000000..bd0b06a7 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/misc.c @@ -0,0 +1,41 @@ +/* $Id: misc.c,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "libpal.h" + +/* +<++doc++> +@name int pkt_add_systimestamp(struct packet *pkt) +@desc Adds a system timestamp (struct timeval) at the position where pkt->pkt_ptr points to. +@param pkt packet into which a timestamp with the current system time will be added. +<--doc--> +*/ +int +pkt_add_systimestamp(struct packet *pkt) +{ + if (!pkt) + return EPKTINVALPTR; + if (pkt->pkt_size < (pkt->pkt_pos + sizeof(struct timeval))) + return EPKTRANGE; + if (gettimeofday((struct timeval *)pkt->pkt_ptr, NULL) == 0) + return 0; + else + return EERRNO; +} + diff --git a/projects/stargazer/plugins/capture/cap_debug/packet.c b/projects/stargazer/plugins/capture/cap_debug/packet.c new file mode 100644 index 00000000..6b76f005 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/packet.c @@ -0,0 +1,161 @@ +/* $Id: packet.c,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "libpal.h" + +/* +<++doc++> +@name int pkt_init(struct packet *pkt, unsigned long type, unsigned int size) +@desc Allocates memory for a new packet and initializes its internal variables +@param pkt pointer to the new packet +@param type type of the packet. Its setting is currently ignored, but this is likely to change. Possible values are e.g. PKT_NET_IP|PKT_TRANS_TCP. See packet.h for other values +@param size overall size if the packet not including ethernet headers or trailers. +<--doc--> +*/ +int +pkt_init(struct packet *pkt, unsigned long type, unsigned int size) +{ + if ((pkt->pkt = (unsigned char *) malloc(size)) == NULL) { + /* no mem, we assume errno will be set */ + return -1; + } else { + /* clear mem out */ + memset(pkt->pkt, 0, size); + /* init vars */ + pkt->pkt_type = type; + pkt->pkt_size = size; + /* set active ptr to beginning of mem */ + pkt->pkt_ptr = pkt->pkt; + pkt->pkt_pos = 0; + return 0; + } +} + +/* +<++doc++> +@name int pkt_free(struct packet *pkt) +@desc Used to destroy a packet and to free used memory +@param pkt the packet to destroy +<--doc--> +*/ +int +pkt_free(struct packet *pkt) +{ + if (pkt) { + free(pkt->pkt); + pkt->pkt=NULL; + return 0; + } + return EPKTINVALPTR; +} + +/* +<++doc++> +@name int pkt_set_actptr(struct packet *pkt, unsigned int bytepos) +@desc This function sets the active pointer position inside the packet. +@param pkt the packet whose active ptr is to be set +@param bytepos the byte position where to set the active ptr to, starting from 0 +<--doc--> +*/ + +int +pkt_set_actptr(struct packet *pkt, unsigned int bytepos) +{ + if (!pkt) + return EPKTINVALPTR; + if (bytepos > pkt->pkt_size) { + return EPKTRANGE; + } else { + pkt->pkt_ptr = pkt->pkt + bytepos; + return PKTOK; + } +} + +/* +<++doc++> +@name int pkt_move_actptr(struct packet *pkt, int relmov) +@desc This function moves the active pointer inside the packet. +@param pkt the packet whose active ptr is to be moved +@param relmov number of bytes the active ptr shall be moved. To move it backward, negative values may be used. +<--doc--> +*/ +int +pkt_move_actptr(struct packet *pkt, int relmov) +{ + if (!pkt) + return EPKTINVALPTR; + if ((pkt->pkt_pos + relmov > pkt->pkt_size) || + (pkt->pkt_pos + relmov < 0)) { + return EPKTRANGE; + } else { + pkt->pkt_ptr += relmov; + return PKTOK; + } +} + +/* +<++doc++> +@name int pkt_add_data(struct packet *pkt, char *data, size_t data_len) +@desc Adds supplied data at the current active ptr position of the given packet.This basically is a memcpy() wrapper function. +@param pkt data will be written into this packet +@param data a pointer to the data that shall be copied into the packet +@param data_len the amount of data (in bytes) that will be copied +<--doc--> +*/ +int +pkt_add_data(struct packet *pkt, char *data, size_t data_len) +{ + if (!pkt || !data) + return EPKTINVALPTR; + if (pkt->pkt_size >= (pkt->pkt_pos + data_len)) { + memcpy(pkt->pkt_ptr, data, data_len); + return 0; + } else + return EPKTRANGE; +} + +/* +<++doc++> +@name int pkt_resize(struct packet *pkt, unsigned int newsize) +@desc The given packet will be resized to newsize +@param pkt the packet to resize +@param newsize the new size of the packet +<--doc--> +*/ +int +pkt_resize(struct packet *pkt, unsigned int newsize) +{ + unsigned char *newpkt; + + if (!pkt) + return EPKTINVALPTR; + + if ((newpkt = (unsigned char *) malloc(newsize)) != NULL) { + memset(newpkt, 0, newsize); + memcpy(newpkt, pkt->pkt, (pkt->pkt_size < newsize ? pkt->pkt_size : newsize)); + pkt->pkt_size = newsize; + pkt->pkt_ptr = newpkt; + pkt->pkt_pos = 0; + /* free old mem */ + free (pkt->pkt); + pkt->pkt = newpkt; + return 0; + } else + return EERRNO; +} diff --git a/projects/stargazer/plugins/capture/cap_debug/socket.c b/projects/stargazer/plugins/capture/cap_debug/socket.c new file mode 100644 index 00000000..b7044bc6 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/socket.c @@ -0,0 +1,163 @@ +/* $Id: socket.c,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "libpal.h" + +/* +<++doc++> +@name int pkt_socket_open(struct pkt_socket *sck, int type) +@desc Opens a packet socket. This is necessary to be able to send any packages. +@param sck pointer to a pkt_socket structure +@param type The type of the socket. Possible values are PKT_RAW (to open a raw socket. IP, TCP/UDP and application header have to be forged before sending) , PKT_STREAM, PKT_DGRAM. STREAM and DGRAM are supported by this function, but other function do not care. +<--doc--> +*/ +int +pkt_socket_open(struct pkt_socket *sck, int type) +{ + int proto; + int iphdrincl = 1; + int ret = 0; + + if (!sck) + return EPKTINVALPTR; + + /* get mem for sockaddr structure */ + if ((sck->sckad = (struct sockaddr_in *) malloc (sizeof(struct sockaddr_in))) == NULL) { + return -1; + } + sck->sckad_len = sizeof(struct sockaddr_in); + memset(sck->sckad, 0, sck->sckad_len); + + /* except for raw sockets, proto is set to 0 */ + /* (Stevens, UNP 2nd ed., 1998) */ + switch (type) { + case PKT_RAW: + proto = IPPROTO_RAW; + break; + default: + proto = 0; + break; + } + if ((sck->rawfd = socket(AF_INET, type, proto)) == -1) { + return -1; + } + if (type == PKT_RAW) { + if ((ret = setsockopt(sck->rawfd, IPPROTO_IP, IP_HDRINCL, (const void *) &iphdrincl, sizeof(iphdrincl))) < 0) { + return -1; + } + } + return 0; +} + + +/* +<++doc++> +@name int pkt_socket_close(struct pkt_socket *sck) +@desc Closes a packet socket and frees used memory. +@param sck pointer to a pkt_socket structure +<--doc--> +*/ +int +pkt_socket_close(struct pkt_socket *sck) +{ + if (!sck) + return EPKTINVALPTR; + close(sck->rawfd); + free(sck->sckad); + sck->sckad=NULL; + return 0; +} + +/* +<++doc++> +@name int pkt_socket_prepare(struct pkt_socket *sck, char *daddr) +@desc this function is necessary to enable the kernel to determine the correct outgoing interface +@param sck pointer to a pkt_socket structure +@param daddr dotted-decimal destination IP address +<--doc--> +*/ +int +pkt_socket_prepare(struct pkt_socket *sck, char *daddr) +{ + /* + * this is necessary for the kernel to determine outgoing + * interface. + */ + + int ret = 0; + + if (!sck || !daddr) + return EPKTINVALPTR; + + /* set up sockaddr struct */ + memset(sck->sckad, 0, sck->sckad_len); + if ((ret = inet_pton(AF_INET, daddr, &(sck->sckad->sin_addr))) == 0) { + return -1; + } + sck->sckad->sin_family = AF_INET; + + /* + * i dont think we need to set the dest port... + */ + return 0; +} + +/* +<++doc++> +@name int pkt_socket_setopt(struct pkt_socket *sck, int level, int optname, void *optval, socklen_t optlen) +@desc this is basically a wrapper function for setsockopt(2) +@param sck pointer to a pkt_socket structure +@param level level the socket option will apply to (should be SOL_SOCKET) +@param optname option name, see sys/socket.h for values +@param optval new option value (for boolean options: 0=false) +@param optlen option value size (this is a value-result parameter!) +<--doc--> +*/ +int +pkt_socket_setopt(struct pkt_socket *sck, int level, + int optname, void *optval, socklen_t optlen) +{ + if (!sck) + return EPKTINVALPTR; + return (setsockopt(sck->rawfd, level, optname, optval, optlen)); +} + + +/* +<++doc++> +@name int pkt_send(struct packet *pkt, struct pkt_socket *sck) +@desc sends the packet pkt unsing the socket sck +@param pkt pointer to a packet structure +@param sck pointer to a pkt_socket structure +<--doc--> +*/ +int +pkt_send(struct packet *pkt, struct pkt_socket *sck) +{ + int ret = 0; + + if (!pkt || !sck) + return EPKTINVALPTR; + + /* no sendto-flags support */ + if ((ret = sendto(sck->rawfd, pkt->pkt, pkt->pkt_size, 0, (struct sockaddr *) sck->sckad, sck->sckad_len)) == -1) { + return -1; + } + return 0; +} diff --git a/projects/stargazer/plugins/capture/cap_debug/tcp.c b/projects/stargazer/plugins/capture/cap_debug/tcp.c new file mode 100644 index 00000000..ad39931d --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/tcp.c @@ -0,0 +1,154 @@ +/* $Id: tcp.c,v 1.2 2009/06/19 12:50:47 faust Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "libpal.h" +#include "types.h" /* internal types */ + +int +pkt_tcp_header(struct packet *pkt, + unsigned short int sport, + unsigned short int dport, + unsigned int seq, + unsigned int ackseq, + unsigned char headerlen, + unsigned char reserved, + unsigned char flags, + unsigned short window, + unsigned short int checksum, + unsigned short int urgent) +{ + struct tcphdr *tcp; + + if (!pkt) + return EPKTINVALPTR; + + tcp = (struct tcphdr *) pkt->pkt_ptr; + tcp->source = htons(sport); + tcp->dest = htons(dport); + tcp->seq = htonl(seq); + tcp->ack_seq = htonl(ackseq); + tcp->window = htons(window); + tcp->urg_ptr = htons(urgent); + + tcp->doff = headerlen; + tcp->res1 = reserved; + if (flags) { + tcp->fin = ((flags & TH_FIN) != 0); + tcp->syn = ((flags & TH_SYN) != 0); + tcp->rst = ((flags & TH_RST) != 0); + tcp->psh = ((flags & TH_RST) != 0); + tcp->ack = ((flags & TH_ACK) != 0); + tcp->urg = ((flags & TH_URG) != 0); +# if __BYTE_ORDER == __LITTLE_ENDIAN + tcp->res2 = (flags & (TH_XMAS | TH_YMAS)) >> 6; +# elif __BYTE_ORDER == __BIG_ENDIAN + tcp->res2 = (flags & (TH_XMAS | TH_YMAS)); +# else +# error "Adjust your defines" +# endif + } else { + tcp->fin = 0; + tcp->syn = 0; + tcp->rst = 0; + tcp->psh = 0; + tcp->ack = 0; + tcp->urg = 0; + tcp->res2 = 0; + } + tcp->check = htons(checksum); + return 0; +} + +int +pkt_tcp_cksum(struct packet *pkt, char *saddr, char *daddr, + unsigned int tcp_pkt_size) +{ + char *tosum; + struct pseudohdr *psh; + struct tcphdr *tcp; + struct in_addr addr; + + if (!pkt || !saddr || !daddr) + return EPKTINVALPTR; + + if ((tcp_pkt_size + pkt->pkt_pos) > pkt->pkt_size -1) + return EPKTRANGE; + + if ((tosum = (char *) malloc(tcp_pkt_size+sizeof(struct pseudohdr))) != NULL) { + memset(tosum, 0, tcp_pkt_size+sizeof(struct pseudohdr)); + psh = (struct pseudohdr *) tosum; + tcp = (struct tcphdr *) pkt->pkt_ptr; + + tcp->check = 0; + + if (inet_pton(AF_INET, saddr, &addr) < 0) + return EERRNO; + psh->saddr = addr.s_addr; + if (inet_pton(AF_INET, daddr, &addr) < 0) + return EERRNO; + psh->daddr = addr.s_addr; + psh->zero = 0x00; + psh->protocol = IPPROTO_TCP; + psh->length = htons(tcp_pkt_size); + + memcpy(tosum + sizeof(struct pseudohdr), tcp, tcp_pkt_size); + tcp->check = in_cksum((unsigned short *)tosum, tcp_pkt_size + sizeof(struct pseudohdr)); + free(tosum); + return 0; + } else + return EERRNO; +} + +int +pkt_tcp_option(struct packet *pkt, unsigned char kind, + unsigned char len, void *optval, size_t optlen) +{ + void *vp; + unsigned short int mss; + + if (!pkt) + return EPKTINVALPTR; + + if ((pkt->pkt_size) < (pkt->pkt_pos+2+optlen)) + return EPKTRANGE; + + vp = (void *)pkt->pkt_ptr; + + memcpy(vp, &kind, 1); + char * p; + p = (char*)vp; + p++; + vp = p; + //vp++; + memcpy(vp, &len, 1); + //vp++; + p = (char*)vp; + p++; + vp = p; + if (kind == PKT_TCP_OPT_MSS) { + mss = htons(*(unsigned short int *)optval); + memcpy(vp, &mss, optlen); + } else if (kind == PKT_TCP_OPT_TIME) { + unsigned int time = htonl(*(unsigned int *)optval); + memcpy(vp, &time, optlen); + } else { + memcpy(vp, optval, optlen); + } + return 0; +} diff --git a/projects/stargazer/plugins/capture/cap_debug/types.h b/projects/stargazer/plugins/capture/cap_debug/types.h new file mode 100644 index 00000000..3d428fe9 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/types.h @@ -0,0 +1,35 @@ +/* $Id: types.h,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _LIBPAL_TYPES_H_ +#define _LIBPAL_TYPES_H_ + +/* +* these are types users wont need +*/ + +/* Stevens' pseudo header */ +struct pseudohdr { + u_int32_t saddr; + u_int32_t daddr; + u_int8_t zero; + u_int8_t protocol; + u_int16_t length; +}; +#endif diff --git a/projects/stargazer/plugins/capture/cap_debug/udp.c b/projects/stargazer/plugins/capture/cap_debug/udp.c new file mode 100644 index 00000000..238c95db --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_debug/udp.c @@ -0,0 +1,82 @@ +/* $Id: udp.c,v 1.1 2005/12/12 18:14:22 nobunaga Exp $ + +Copyright (C) 2002 Marc Kirchner + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "libpal.h" +#include "types.h" + +int +pkt_udp_header(struct packet *pkt, unsigned short int sport, unsigned short int dport, unsigned short int udp_total_len, unsigned short int checksum) +{ + struct udphdr *udp; + + if (!pkt) + return EPKTINVALPTR; + if (pkt->pkt_size >= (pkt->pkt_pos + sizeof(struct udphdr))) { + udp = (struct udphdr *) pkt->pkt_ptr; + udp->source = htons(sport); + udp->dest = htons(dport); + udp->len = htons(udp_total_len); + udp->check = htons(checksum); + return 0; + } else + return EPKTRANGE; +} + +int +pkt_udp_cksum(struct packet *pkt, char *saddr, char *daddr, + unsigned int udp_total_len) +{ + char *tosum; + struct pseudohdr *psh; + struct udphdr *udp; + unsigned short int check=0; + struct in_addr addr; + + if (!pkt || !saddr || !daddr) + return EPKTINVALPTR; + + if ((udp_total_len + pkt->pkt_pos) > pkt->pkt_size -1) + return EPKTRANGE; + + if ((tosum = (char *) malloc(udp_total_len+sizeof(struct pseudohdr))) != NULL) { + memset(tosum, 0, udp_total_len+sizeof(struct pseudohdr)); + psh = (struct pseudohdr *) tosum; + udp = (struct udphdr *) pkt->pkt_ptr; + + udp->check = 0; + + if (inet_pton(AF_INET, saddr, &addr) < 0) + return EERRNO; + psh->saddr = addr.s_addr; + if (inet_pton(AF_INET, daddr, &addr) < 0) + return EERRNO; + psh->daddr = addr.s_addr; + psh->zero = 0x00; + psh->protocol = IPPROTO_UDP; + psh->length = htons(udp_total_len); + + memcpy(tosum + sizeof(struct pseudohdr), udp, udp_total_len); + check = in_cksum((unsigned short *)tosum, udp_total_len + sizeof(struct pseudohdr)); + /* _no_ call to htons(), because tosum is in network byte order */ + udp->check = check == 0 ? 0xffff : check; + free(tosum); + return 0; + } else + return EERRNO; +} diff --git a/projects/stargazer/plugins/capture/cap_nf/Makefile b/projects/stargazer/plugins/capture/cap_nf/Makefile new file mode 100644 index 00000000..3eddb29e --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_nf/Makefile @@ -0,0 +1,15 @@ +############################################################################### +# $Id: Makefile,v 1.2 2008/12/04 17:04:41 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_cap_nf.so + +SRCS = ./cap_nf.cpp + +LIBS += $(LIB_THREAD) +STGLIBS = -lstg_common + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/capture/cap_nf/cap_nf.cpp b/projects/stargazer/plugins/capture/cap_nf/cap_nf.cpp new file mode 100644 index 00000000..11a96ef1 --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_nf/cap_nf.cpp @@ -0,0 +1,440 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +Date: 16.05.2008 +*/ + +/* +* Author : Maxim Mamontov +*/ + +/* +$Revision: 1.11 $ +$Date: 2010/09/10 06:41:06 $ +$Author: faust $ +*/ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common.h" +#include "cap_nf.h" +#include "raw_ip_packet.h" + +#include "../../../traffcounter.h" + +class CAP_NF_CREATOR +{ +public: + CAP_NF_CREATOR() + : nf(new NF_CAP()) + { + }; + + ~CAP_NF_CREATOR() + { + delete nf; + }; + + NF_CAP * GetCapturer() { return nf; }; +private: + NF_CAP * nf; +} cnc; + +BASE_PLUGIN * GetPlugin() +{ +return cnc.GetCapturer(); +} + +NF_CAP::NF_CAP() + : traffCnt(NULL), + tidTCP(0), + tidUDP(0), + runningTCP(false), + runningUDP(false), + stoppedTCP(true), + stoppedUDP(true), + portT(0), + portU(0), + sockTCP(-1), + sockUDP(-1) +{ +} + +NF_CAP::~NF_CAP() +{ +} + +int NF_CAP::ParseSettings() +{ +vector::iterator it; +for (it = settings.moduleParams.begin(); it != settings.moduleParams.end(); ++it) + { + if (it->param == "TCPPort") + { + if (str2x(it->value[0], portT)) + { + errorStr = "Invalid TCPPort value"; + printfd(__FILE__, "Error: Invalid TCPPort value\n"); + return -1; + } + continue; + } + if (it->param == "UDPPort") + { + if (str2x(it->value[0], portU)) + { + errorStr = "Invalid UDPPort value"; + printfd(__FILE__, "Error: Invalid UDPPort value\n"); + return -1; + } + continue; + } + printfd(__FILE__, "'%s' is not a valid module param\n", it->param.c_str()); + } +return 0; +} + +int NF_CAP::Start() +{ +if (portU > 0) + { + if (OpenUDP()) + { + return -1; + } + runningUDP = true; + if (pthread_create(&tidUDP, NULL, RunUDP, this)) + { + runningUDP = false; + CloseUDP(); + errorStr = "Cannot create UDP thread"; + printfd(__FILE__, "Error: Cannot create UDP thread\n"); + return -1; + } + } +if (portT > 0) + { + if (OpenTCP()) + { + return -1; + } + runningTCP = true; + if (pthread_create(&tidTCP, NULL, RunTCP, this)) + { + runningTCP = false; + CloseTCP(); + errorStr = "Cannot create TCP thread"; + printfd(__FILE__, "Error: Cannot create TCP thread\n"); + return -1; + } + } +return 0; +} + +int NF_CAP::Stop() +{ +runningTCP = runningUDP = false; +if (portU && !stoppedUDP) + { + CloseUDP(); + for (int i = 0; i < 25 && !stoppedUDP; ++i) + { + usleep(200000); + } + if (stoppedUDP) + { + pthread_join(tidUDP, NULL); + } + else + { + if (pthread_kill(tidUDP, SIGUSR1)) + { + errorStr = "Error sending signal to UDP thread"; + printfd(__FILE__, "Error: Error sending signal to UDP thread\n"); + return -1; + } + printfd(__FILE__, "UDP thread NOT stopped\n"); + } + } +if (portT && !stoppedTCP) + { + CloseTCP(); + for (int i = 0; i < 25 && !stoppedTCP; ++i) + { + usleep(200000); + } + if (stoppedTCP) + { + pthread_join(tidTCP, NULL); + } + else + { + if (pthread_kill(tidTCP, SIGUSR1)) + { + errorStr = "Error sending signal to TCP thread"; + printfd(__FILE__, "Error: Error sending signal to TCP thread\n"); + return -1; + } + printfd(__FILE__, "TCP thread NOT stopped\n"); + } + } +return 0; +} + +bool NF_CAP::OpenUDP() +{ +struct sockaddr_in sin; +sockUDP = socket(PF_INET, SOCK_DGRAM, 0); +if (sockUDP <= 0) + { + errorStr = "Error opening UDP socket"; + printfd(__FILE__, "Error: Error opening UDP socket\n"); + return true; + } +sin.sin_family = AF_INET; +sin.sin_port = htons(portU); +sin.sin_addr.s_addr = inet_addr("0.0.0.0"); +if (bind(sockUDP, (struct sockaddr *)&sin, sizeof(sin))) + { + errorStr = "Error binding UDP socket"; + printfd(__FILE__, "Error: Error binding UDP socket\n"); + return true; + } +return false; +} + +bool NF_CAP::OpenTCP() +{ +struct sockaddr_in sin; +sockTCP = socket(PF_INET, SOCK_STREAM, 0); +if (sockTCP <= 0) + { + errorStr = "Error opening TCP socket"; + printfd(__FILE__, "Error: Error opening TCP socket\n"); + return true; + } +sin.sin_family = AF_INET; +sin.sin_port = htons(portT); +sin.sin_addr.s_addr = inet_addr("0.0.0.0"); +if (bind(sockTCP, (struct sockaddr *)&sin, sizeof(sin))) + { + errorStr = "Error binding TCP socket"; + printfd(__FILE__, "Error: Error binding TCP socket\n"); + return true; + } +if (listen(sockTCP, 1)) + { + errorStr = "Error listening on TCP socket"; + printfd(__FILE__, "Error: Error listening TCP socket\n"); + return true; + } +return false; +} + +void * NF_CAP::RunUDP(void * c) +{ +NF_CAP * cap = static_cast(c); +uint8_t buf[BUF_SIZE]; +int res; +struct sockaddr_in sin; +socklen_t slen; +cap->stoppedUDP = false; +while (cap->runningUDP) + { + if (!cap->WaitPackets(cap->sockUDP)) + { + continue; + } + + // Data + slen = sizeof(sin); + res = recvfrom(cap->sockUDP, buf, BUF_SIZE, 0, reinterpret_cast(&sin), &slen); + if (!cap->runningUDP) + break; + + if (res == 0) // EOF + { + continue; + } + + + // Wrong logic! + // Need to check actual data length and wait all data to receive + if (res < 24) + { + if (errno != EINTR) + { + cap->errorStr = "Invalid data received"; + printfd(__FILE__, "Error: Invalid data received through UDP\n"); + } + continue; + } + + cap->ParseBuffer(buf, res); + } +cap->stoppedUDP = true; +return NULL; +} + +void * NF_CAP::RunTCP(void * c) +{ +NF_CAP * cap = static_cast(c); +uint8_t buf[BUF_SIZE]; +int res; +int sd; +struct sockaddr_in sin; +socklen_t slen; +cap->stoppedTCP = false; +while (cap->runningTCP) + { + if (!cap->WaitPackets(cap->sockTCP)) + { + continue; + } + + // Data + slen = sizeof(sin); + sd = accept(cap->sockTCP, reinterpret_cast(&sin), &slen); + if (!cap->runningTCP) + break; + + if (sd <= 0) + { + if (errno != EINTR) + { + cap->errorStr = "Error accepting connection"; + printfd(__FILE__, "Error: Error accepting connection\n"); + } + continue; + } + + if (!cap->WaitPackets(sd)) + { + close(sd); + continue; + } + + res = recv(sd, buf, BUF_SIZE, MSG_WAITALL); + close(sd); + + if (!cap->runningTCP) + break; + + if (res == 0) // EOF + { + continue; + } + + // Wrong logic! + // Need to check actual data length and wait all data to receive + if (res < 24) + { + if (errno != EINTR) + { + cap->errorStr = "Invalid data received"; + printfd(__FILE__, "Error: Invalid data received through TCP\n"); + } + continue; + } + + cap->ParseBuffer(buf, res); + } +cap->stoppedTCP = true; +return NULL; +} + +void NF_CAP::ParseBuffer(uint8_t * buf, int size) +{ +RAW_PACKET ip; +NF_HEADER * hdr = reinterpret_cast(buf); +if (htons(hdr->version) != 5) + { + return; + } + +int packets = htons(hdr->count); + +if (packets < 0 || packets > 30) + { + return; + } + +if (24 + 48 * packets != size) + { + // See 'wrong logic' upper + return; + } + +for (int i = 0; i < packets; ++i) + { + NF_DATA * data = reinterpret_cast(buf + 24 + i * 48); + + /*ip.pckt[0] = 4 << 4; + ip.pckt[0] |= 5; + ip.pckt[9] = data->proto; + ip.dataLen = ntohl(data->octets); + *(uint32_t *)(ip.pckt + 12) = data->srcAddr; + *(uint32_t *)(ip.pckt + 16) = data->dstAddr; + *(uint16_t *)(ip.pckt + 20) = data->srcPort; + *(uint16_t *)(ip.pckt + 22) = data->dstPort;*/ + ip.ipHeader.ip_v = 4; + ip.ipHeader.ip_hl = 5; + ip.ipHeader.ip_p = data->proto; + ip.dataLen = ntohl(data->octets); + ip.ipHeader.ip_src.s_addr = data->srcAddr; + ip.ipHeader.ip_dst.s_addr = data->dstAddr; + ip.sPort = data->srcPort; + ip.dPort = data->dstPort; + + traffCnt->Process(ip); + } +} + +bool NF_CAP::WaitPackets(int sd) const +{ +fd_set rfds; +FD_ZERO(&rfds); +FD_SET(sd, &rfds); + +struct timeval tv; +tv.tv_sec = 0; +tv.tv_usec = 500000; + +int res = select(sd + 1, &rfds, NULL, NULL, &tv); +if (res == -1) // Error + { + if (errno != EINTR) + { + printfd(__FILE__, "Error on select: '%s'\n", strerror(errno)); + } + return false; + } + +if (res == 0) // Timeout + { + return false; + } + +return true; +} diff --git a/projects/stargazer/plugins/capture/cap_nf/cap_nf.h b/projects/stargazer/plugins/capture/cap_nf/cap_nf.h new file mode 100644 index 00000000..6844fe2a --- /dev/null +++ b/projects/stargazer/plugins/capture/cap_nf/cap_nf.h @@ -0,0 +1,135 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +Date: 16.05.2008 +*/ + +/* +* Author : Maxim Mamontov +*/ + +/* +$Revision: 1.5 $ +$Date: 2009/12/13 12:56:07 $ +$Author: faust $ +*/ +#ifndef __CAP_NF_H__ +#define __CAP_NF_H__ + +#include +#include + +#include "os_int.h" +#include "base_plugin.h" + +#define VERSION "CAP_NF v. 0.4" +#define START_POS 0 +#define STOP_POS 0 + +struct NF_HEADER +{ + uint16_t version; // Protocol version + uint16_t count; // Flows count + uint32_t uptime; // System uptime + uint32_t timestamp; // UNIX timestamp + uint32_t nsecs; // Residual nanoseconds + uint32_t flowSeq; // Sequence counter + uint8_t eType; // Engine type + uint8_t eID; // Engine ID + uint16_t sInterval; // Sampling mode and interval +} __attribute__ ((packed)); + +struct NF_DATA +{ + uint32_t srcAddr; // Flow source address + uint32_t dstAddr; // Flow destination address + uint32_t nextHop; // IP addres on next hop router + uint16_t inSNMP; // SNMP index of input iface + uint16_t outSNMP; // SNMP index of output iface + uint32_t packets; // Packets in flow + uint32_t octets; // Total number of bytes in flow + uint32_t timeStart; // Uptime on first packet in flow + uint32_t timeFinish;// Uptime on last packet in flow + uint16_t srcPort; // Flow source port + uint16_t dstPort; // Flow destination port + uint8_t pad1; // 1-byte padding + uint8_t TCPFlags; // Cumulative OR of TCP flags + uint8_t proto; // IP protocol type (tcp, udp, etc.) + uint8_t tos; // IP Type of Service (ToS) + uint16_t srcAS; // Source BGP autonomous system number + uint16_t dstAS; // Destination BGP autonomus system number + uint8_t srcMask; // Source address mask in "slash" notation + uint8_t dstMask; // Destination address mask in "slash" notation + uint16_t pad2; // 2-byte padding +} __attribute__ ((packed)); + +#define BUF_SIZE (sizeof(NF_HEADER) + 30 * sizeof(NF_DATA)) + +class NF_CAP : public BASE_PLUGIN +{ +public: + NF_CAP(); + ~NF_CAP(); + + void SetUsers(USERS *) {}; + void SetTariffs(TARIFFS *) {}; + void SetAdmins(ADMINS *) {}; + void SetTraffcounter(TRAFFCOUNTER * tc) { traffCnt = tc; }; + void SetStore(BASE_STORE *) {}; + void SetStgSettings(const SETTINGS *) {}; + void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning() { return runningTCP || runningUDP; }; + const string & GetStrError() const { return errorStr; }; + const string GetVersion() const { return VERSION; }; + uint16_t GetStartPosition() const { return START_POS; }; + uint16_t GetStopPosition() const { return STOP_POS; }; + +private: + TRAFFCOUNTER * traffCnt; + MODULE_SETTINGS settings; + pthread_t tidTCP; + pthread_t tidUDP; + bool runningTCP; + bool runningUDP; + bool stoppedTCP; + bool stoppedUDP; + uint16_t portT; + uint16_t portU; + int sockTCP; + int sockUDP; + mutable std::string errorStr; + + static void * RunUDP(void *); + static void * RunTCP(void *); + void ParseBuffer(uint8_t *, int); + + bool OpenTCP(); + bool OpenUDP(); + void CloseTCP() { close(sockTCP); }; + void CloseUDP() { close(sockUDP); }; + + bool WaitPackets(int sd) const; +}; + +extern "C" BASE_PLUGIN * GetPlugin(); + +#endif diff --git a/projects/stargazer/plugins/capture/divert_freebsd/Makefile b/projects/stargazer/plugins/capture/divert_freebsd/Makefile new file mode 100644 index 00000000..cd7824a7 --- /dev/null +++ b/projects/stargazer/plugins/capture/divert_freebsd/Makefile @@ -0,0 +1,16 @@ +############################################################################### +# $Id: Makefile,v 1.4 2008/12/04 17:05:21 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_cap_divert.so + +SRCS = ./divert_cap.cpp + +STGLIBS = -lstg_common + +LIBS += $(LIB_THREAD) + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/capture/divert_freebsd/divert_cap.cpp b/projects/stargazer/plugins/capture/divert_freebsd/divert_cap.cpp new file mode 100644 index 00000000..a76629fb --- /dev/null +++ b/projects/stargazer/plugins/capture/divert_freebsd/divert_cap.cpp @@ -0,0 +1,373 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +* Author : Boris Mikhailenko +*/ + +/* +$Revision: 1.13 $ +$Date: 2010/09/10 06:43:03 $ +*/ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common.h" +#include "divert_cap.h" + +#define BUFF_LEN (16384) /* max mtu -> lo=16436 TODO why?*/ + +//----------------------------------------------------------------------------- +struct DIVERT_DATA +{ +int sock; +short int port; +unsigned char buffer[BUFF_LEN]; +char iface[10]; +}; +//----------------------------------------------------------------------------- +pollfd pollddiv; +DIVERT_DATA cddiv; //capture data +//----------------------------------------------------------------------------- +class DIVERT_CAP_CREATOR +{ +private: + DIVERT_CAP * divc; + +public: + DIVERT_CAP_CREATOR() + : divc(new DIVERT_CAP()) + { + }; + ~DIVERT_CAP_CREATOR() + { + delete divc; + }; + + DIVERT_CAP * GetCapturer() + { + return divc; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +DIVERT_CAP_CREATOR dcc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return dcc.GetCapturer(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string DIVERT_CAP::GetVersion() const +{ +return "Divert_cap v.1.0"; +} +//----------------------------------------------------------------------------- +DIVERT_CAP::DIVERT_CAP() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void DIVERT_CAP::SetTraffcounter(TRAFFCOUNTER * tc) +{ +traffCnt = tc; +} +//----------------------------------------------------------------------------- +const string & DIVERT_CAP::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::Start() +{ +if (isRunning) + return 0; + +if (DivertCapOpen() < 0) + { + errorStr = "Cannot open socket!"; + printfd(__FILE__, "Cannot open socket\n"); + return -1; + } + +nonstop = true; + +if (pthread_create(&thread, NULL, Run, this) == 0) + { + return 0; + } + +errorStr = "Cannot create thread."; +printfd(__FILE__, "Cannot create thread\n"); +return -1; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::Stop() +{ +if (!isRunning) + return 0; + +DivertCapClose(); + +nonstop = false; + +//5 seconds to thread stops itself +int i; +for (i = 0; i < 25; i++) + { + if (!isRunning) + break; + + usleep(200000); + } + +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return -1; + } + } + +return 0; +} +//----------------------------------------------------------------------------- +bool DIVERT_CAP::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * DIVERT_CAP::Run(void * d) +{ +DIVERT_CAP * dc = (DIVERT_CAP *)d; +dc->isRunning = true; + +/*struct ETH_IP +{ +uint16_t ethHdr[8]; +RAW_PACKET rp; +char padding[4]; +char padding1[8]; +}; + +ETH_IP * ethIP; + +char ethip[sizeof(ETH_IP)]; + +//memset(ðIP, 0, sizeof(ethIP)); +memset(ðip, 0, sizeof(ETH_IP)); + +ethIP = (ETH_IP *)ðip; +ethIP->rp.dataLen = -1; +*/ +//char * iface = NULL; +char buffer[64]; +while (dc->nonstop) + { + RAW_PACKET rp; + dc->DivertCapRead(buffer, 64, NULL); + + //printf("%x %x %x %x \n", buffer[0], buffer[4], buffer[8], buffer[12]); + //printf("%x %x %x %x \n", buffer[16], buffer[20], buffer[24], buffer[28]); + //printf("%x %x %x %x \n", buffer[32], buffer[36], buffer[40], buffer[44]); + + if (buffer[12] != 0x8) + continue; + + memcpy(rp.pckt, &buffer[14], pcktSize); + + //dc->traffCnt->Process(*((RAW_PACKET*)( &buffer[14] ))); // - too dirty! + dc->traffCnt->Process(rp); + } + +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t DIVERT_CAP::GetStartPosition() const +{ +return 10; +} +//----------------------------------------------------------------------------- +uint16_t DIVERT_CAP::GetStopPosition() const +{ +return 10; +} +//----------------------------------------------------------------------------- +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +//----------------------------------------------------------------------------- +int DIVERT_CAP::DivertCapOpen() +{ +memset(&pollddiv, 0, sizeof(pollddiv)); +memset(&cddiv, 0, sizeof(DIVERT_DATA)); + +strcpy(cddiv.iface, "foo"); +cddiv.port = port; + +DivertCapOpen(0); +pollddiv.events = POLLIN; +pollddiv.fd = cddiv.sock; + +return 0; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::DivertCapOpen(int) +{ +int ret; +cddiv.sock = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT); +if (cddiv.sock < 0) + { + errorStr = "Create divert socket error."; + printfd(__FILE__, "Cannot create divert socket\n"); + return -1; + } + +struct sockaddr_in divAddr; + +memset(&divAddr, 0, sizeof(divAddr)); + +divAddr.sin_family = AF_INET; +divAddr.sin_port = htons(cddiv.port); +divAddr.sin_addr.s_addr = INADDR_ANY; + +ret = bind(cddiv.sock, (struct sockaddr *)&divAddr, sizeof(divAddr)); + +if (ret < 0) + { + errorStr = "Bind divert socket error."; + printfd(__FILE__, "Cannot bind divert socket\n"); + return -1; + } + +return cddiv.sock; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::DivertCapRead(char * b, int blen, char ** iface) +{ +poll(&pollddiv, 1, -1); + +if (pollddiv.revents & POLLIN) + { + DivertCapRead(b, blen, iface, 0); + pollddiv.revents = 0; + return 0; + } + +return 0; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::DivertCapRead(char * b, int blen, char ** iface, int) +{ +static char buf[BUFF_LEN]; +static struct sockaddr_in divertaddr; +static int bytes; +static socklen_t divertaddrSize = sizeof(divertaddr); + +if ((bytes = recvfrom (cddiv.sock, buf, BUFF_LEN, + 0, (struct sockaddr*) &divertaddr, &divertaddrSize)) > 50) + { + memcpy(b + 14, buf, blen - 14); + b[12] = 0x8; + + if (iface) + *iface = cddiv.iface; + + sendto(cddiv.sock, buf, bytes, 0, (struct sockaddr*)&divertaddr, divertaddrSize); + } + +return 0; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::DivertCapClose() +{ +close(cddiv.sock); +return 0; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::ParseSettings() +{ +int p; +PARAM_VALUE pv; +vector::const_iterator pvi; + +pv.param = "Port"; +pvi = find(settings.moduleParams.begin(), settings.moduleParams.end(), pv); +if (pvi == settings.moduleParams.end()) + { + port = 15701; + return 0; + } + +if (ParseIntInRange(pvi->value[0], 1, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'Port'\n"); + return -1; + } + +port = p; + +return 0; +} +//----------------------------------------------------------------------------- +int DIVERT_CAP::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +void DIVERT_CAP::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/capture/divert_freebsd/divert_cap.h b/projects/stargazer/plugins/capture/divert_freebsd/divert_cap.h new file mode 100644 index 00000000..84234219 --- /dev/null +++ b/projects/stargazer/plugins/capture/divert_freebsd/divert_cap.h @@ -0,0 +1,96 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* + Author : Boris Mikhailenko +*/ + +/* +$Revision: 1.6 $ +$Date: 2009/06/23 11:32:27 $ +*/ + +#ifndef DIVERT_CAP_H +#define DIVERT_CAP_H + +#include +#include + +#include "base_plugin.h" +#include "base_settings.h" +#include "../../../traffcounter.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +//----------------------------------------------------------------------------- +class DIVERT_CAP :public BASE_PLUGIN +{ +public: + DIVERT_CAP(); + virtual ~DIVERT_CAP(){}; + + void SetUsers(USERS *){}; + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER * tc); + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS *){}; + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + static void * Run(void *); + + int DivertCapOpen(); + int DivertCapOpen(int n); + int DivertCapRead(char * buffer, int blen, char ** iface); + int DivertCapRead(char * buffer, int blen, char ** iface, int n); + int DivertCapClose(); + + int ParseIntInRange(const string & str, int min, int max, int * val); + + MODULE_SETTINGS settings; + + int port; + + mutable string errorStr; + + pthread_t thread; + + bool nonstop; + bool isRunning; + + //int capSock; + + TRAFFCOUNTER * traffCnt; +}; +//----------------------------------------------------------------------------- + + +#endif diff --git a/projects/stargazer/plugins/capture/ether_freebsd/Makefile b/projects/stargazer/plugins/capture/ether_freebsd/Makefile new file mode 100644 index 00000000..4ab0480c --- /dev/null +++ b/projects/stargazer/plugins/capture/ether_freebsd/Makefile @@ -0,0 +1,16 @@ +############################################################################### +# $Id: Makefile,v 1.7 2008/12/04 17:06:56 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_cap_bpf.so + +SRCS = ./ether_cap.cpp + +STGLIBS = -lstg_common + +LIBS += $(LIB_THREAD) + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/capture/ether_freebsd/ether_cap.cpp b/projects/stargazer/plugins/capture/ether_freebsd/ether_cap.cpp new file mode 100644 index 00000000..0ebec5b6 --- /dev/null +++ b/projects/stargazer/plugins/capture/ether_freebsd/ether_cap.cpp @@ -0,0 +1,450 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +Date: 18.09.2002 +*/ + +/* +* Author : Boris Mikhailenko +*/ + +/* +$Revision: 1.19 $ +$Date: 2009/03/24 11:20:15 $ +$Author: faust $ +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ether_cap.h" +#include "common.h" +#include "raw_ip_packet.h" + +using namespace std; + +//#define CAP_DEBUG 1 +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +class BPF_CAP_CREATOR +{ +private: + BPF_CAP * bpfc; + +public: + BPF_CAP_CREATOR() + : bpfc(new BPF_CAP()) + { + }; + ~BPF_CAP_CREATOR() + { + delete bpfc; + }; + + BPF_CAP * GetCapturer() + { + return bpfc; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BPF_CAP_CREATOR bcc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return bcc.GetCapturer(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int BPF_CAP_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +//char sep[]= ", \t\n\r"; +//char *s; +string ifaces; +//char * str; +//char *p; + +iface.erase(iface.begin(), iface.end()); + +//PARAM_VALUE pv; +//pv.param = "WorkDir"; +//vector::const_iterator pvi; + +if (s.moduleParams.empty()) + { + errorStr = "Parameter \'iface\' not found."; + printfd(__FILE__, "Parameter 'iface' not found\n"); + return -1; + } + +for (unsigned i = 0; i < s.moduleParams.size(); i++) + { + if (s.moduleParams[i].param != "iface") + { + errorStr = "Parameter \'" + s.moduleParams[i].param + "\' unrecognized."; + printfd(__FILE__, "Invalid parameter: '%s'\n", s.moduleParams[i].param.c_str()); + return -1; + } + for (unsigned j = 0; j < s.moduleParams[i].value.size(); j++) + { + iface.push_back(s.moduleParams[i].value[j]); + } + } + +/*if (cf.ReadString("Iface", &ifaces, "NoIface") < 0) + { + errorStr = "Cannot read parameter \'Iface\' from " + cf.GetFileName(); + return -1; + } + +str = new char[ifaces.size() + 1]; +strcpy(str, ifaces.c_str()); +p = str; + +while((s = strtok(p, sep))) + { + printfd(__FILE__, "iface[] = %s\n", s); + p = NULL; + iface.push_back(s); + //strncpy(iface[i++], s, DEV_NAME_LEN); + //devNum++; + } + +delete[] str; +if (!ifaces.size()) + { + errorStr = "Error read parameter \'Iface\' from " + cf.GetFileName(); + return -1; + }*/ + +return 0; +} +//----------------------------------------------------------------------------- +string BPF_CAP_SETTINGS::GetIface(unsigned int num) +{ +if (num >= iface.size()) + { + return ""; + } +return iface[num]; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string BPF_CAP::GetVersion() const +{ +return "bpf_cap v.1.0"; +} +//----------------------------------------------------------------------------- +BPF_CAP::BPF_CAP() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void BPF_CAP::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int BPF_CAP::ParseSettings() +{ +int ret = capSettings.ParseSettings(settings); +if (ret) + { + errorStr = capSettings.GetStrError(); + return ret; + } +return 0; +} +//----------------------------------------------------------------------------- +void BPF_CAP::SetTraffcounter(TRAFFCOUNTER * tc) +{ +traffCnt = tc; +} +//----------------------------------------------------------------------------- +const string & BPF_CAP::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int BPF_CAP::Start() +{ +if (isRunning) + return 0; + +if (BPFCapOpen() < 0) + { + //errorStr = "Cannot open bpf device!"; + return -1; + } + +nonstop = true; + +if (pthread_create(&thread, NULL, Run, this) == 0) + { + return 0; + } + +errorStr = "Cannot create thread."; +printfd(__FILE__, "Cannot create thread\n"); +return -1; +} +//----------------------------------------------------------------------------- +int BPF_CAP::Stop() +{ +if (!isRunning) + return 0; + +BPFCapClose(); + +nonstop = false; + +//5 seconds to thread stops itself +int i; +for (i = 0; i < 25; i++) + { + if (!isRunning) + break; + + usleep(200000); + } + +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + //TODO pthread_cancel() + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return -1; + } + } + +return 0; +} +//----------------------------------------------------------------------------- +bool BPF_CAP::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * BPF_CAP::Run(void * d) +{ +BPF_CAP * dc = (BPF_CAP *)d; +dc->isRunning = true; + +uint8_t hdr[96]; //68 + 14 + 4(size) + 9(SYS_IFACE) + 1(align to 4) = 96 + +RAW_PACKET * rpp = (RAW_PACKET *)&hdr[14]; +memset(hdr, 0, sizeof(hdr)); + +rpp->dataLen = -1; +char * iface; + +while (dc->nonstop) + { + dc->BPFCapRead((char*)&hdr, 68 + 14, &iface); + + if (!(hdr[12] == 0x8 && hdr[13] == 0x0)) + { + continue; + } + + dc->traffCnt->Process(*rpp); + } + +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t BPF_CAP::GetStartPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +uint16_t BPF_CAP::GetStopPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +int BPF_CAP::BPFCapOpen() +{ +//for (int i = 0; i < settings->devNum; i++) +int i = 0; +BPF_DATA bd; +pollfd pd; + +while ((bd.iface = capSettings.GetIface(i)) != "") + { + bpfData.push_back(bd); + if (BPFCapOpen(&bpfData[i]) < 0) + { + return -1; + } + + pd.events = POLLIN; + pd.fd = bpfData[i].fd; + polld.push_back(pd); + i++; + } + +return 0; +} +//----------------------------------------------------------------------------- +//int BPF_CAP::BPFCapOpen(string ifaceToOpen) +int BPF_CAP::BPFCapOpen(BPF_DATA * bd) +{ +char devbpf[20]; +int i = 0; +int l = BUFF_LEN; +int im = 1; +struct ifreq ifr; + +do { + sprintf(devbpf, "/dev/bpf%d", i); + i++; + bd->fd = open(devbpf, O_RDONLY); + //cd[n].fd = open(devbpf, O_RDONLY); + } while(bd->fd < 0 && errno == EBUSY); + //while(cd[n].fd < 0 && errno == EBUSY); + +//if (cd[n].fd < 0) +if (bd->fd < 0) + { + errorStr = "Can't capture packets. Open bpf device for " + bd->iface + " error."; + printfd(__FILE__, "Cannot open BPF device\n"); + return -1; + } + +//strncpy(ifr.ifr_name, settings->iface[n], sizeof(ifr.ifr_name)); +strncpy(ifr.ifr_name, bd->iface.c_str(), sizeof(ifr.ifr_name)); + +//if (ioctl(cd[n].fd, BIOCSBLEN, (caddr_t)&l) < 0) +if (ioctl(bd->fd, BIOCSBLEN, (caddr_t)&l) < 0) + { + errorStr = bd->iface + " BIOCSBLEN " + string(strerror(errno)); + printfd(__FILE__, "ioctl failed: '%s'\n", errorStr.c_str()); + return -1; + } + +//if (ioctl(cd[n].fd, BIOCSETIF, (caddr_t)&ifr) < 0 ) +if (ioctl(bd->fd, BIOCSETIF, (caddr_t)&ifr) < 0) + { + errorStr = bd->iface + " BIOCSETIF " + string(strerror(errno)); + printfd(__FILE__, "ioctl failed: '%s'\n", errorStr.c_str()); + return -1; + } + +//if (ioctl(cd[n].fd, BIOCIMMEDIATE, &im) < 0 ) +if (ioctl(bd->fd, BIOCIMMEDIATE, &im) < 0) + { + errorStr = bd->iface + " BIOCIMMEDIATE " + string(strerror(errno)); + printfd(__FILE__, "ioctl failed: '%s'\n", errorStr.c_str()); + return -1; + } + +return bd->fd; +//return 0; +} +//----------------------------------------------------------------------------- +int BPF_CAP::BPFCapClose() +{ +for (unsigned int i = 0; i < bpfData.size(); i++) + close(bpfData[i].fd); +return 0; +} +//----------------------------------------------------------------------------- +int BPF_CAP::BPFCapRead(char * buffer, int blen, char ** capIface) +{ +poll(&polld[0], polld.size(), -1); + +for (unsigned int i = 0; i < polld.size(); i++) + { + if (polld[i].revents & POLLIN) + { + BPFCapRead(buffer, blen, capIface, &bpfData[i]); + polld[i].revents = 0; + return 0; + } + } +return 0; +} +//----------------------------------------------------------------------------- +int BPF_CAP::BPFCapRead(char * buffer, int blen, char **, BPF_DATA * bd) +{ +if (bd->canRead) + { + bd->r = read(bd->fd, bd->buffer, BUFF_LEN); + if (bd->r < 0) + { + //printfd(__FILE__, " error read\n"); + usleep(20000); + } + + bd->p = bd->buffer; + bd->bh = (struct bpf_hdr*)bd->p; + bd->canRead = 0; + } + +if(bd->r > bd->sum) + { + memcpy(buffer, (char*)(bd->p) + bd->bh->bh_hdrlen, blen); + //strncpy(iface, settings->iface[n], 9); + //*iface = settings->iface[n]; + + bd->sum += BPF_WORDALIGN(bd->bh->bh_hdrlen + bd->bh->bh_caplen); + bd->p = bd->p + BPF_WORDALIGN(bd->bh->bh_hdrlen + bd->bh->bh_caplen); + bd->bh = (struct bpf_hdr*)bd->p; + } + +if(bd->r <= bd->sum) + { + bd->canRead = 1; + bd->sum = 0; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/capture/ether_freebsd/ether_cap.h b/projects/stargazer/plugins/capture/ether_freebsd/ether_cap.h new file mode 100644 index 00000000..6e651d35 --- /dev/null +++ b/projects/stargazer/plugins/capture/ether_freebsd/ether_cap.h @@ -0,0 +1,158 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + /* + $Revision: 1.11 $ + $Date: 2009/06/23 11:32:27 $ + $Author: faust $ + */ + +/* +* Author : Boris Mikhailenko +*/ + +#ifndef ETHER_CAP_H +#define ETHER_CAP_H + +#include +#include +#include + +#ifdef FREE_BSD5 +#include +#endif + +#ifdef FREE_BSD +#include +#endif + +#include "base_plugin.h" +#include "base_settings.h" +#include "../../../traffcounter.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +#define BUFF_LEN (128) + +//----------------------------------------------------------------------------- +struct BPF_DATA +{ + BPF_DATA() + { + fd = 0; + p = NULL; + r = 0; + sum = 0; + memset(buffer, 0, BUFF_LEN); + bh = NULL; + canRead = 1; + iface = ""; + //memset(&polld, 0, sizeof(pollfd)); + }; + + BPF_DATA(const BPF_DATA & bd) + { + fd = bd.fd; + p = bd.p; + r = bd.r; + sum = bd.sum; + memcpy(buffer, bd.buffer, BUFF_LEN); + bh = bd.bh; + canRead = bd.canRead; + iface = bd.iface; + //memcpy(&polld, &bd.polld, sizeof(pollfd)); + }; + +int fd; +uint8_t * p; +int r; +int sum; +uint8_t buffer[BUFF_LEN]; +struct bpf_hdr * bh; +int canRead; +string iface; +//pollfd polld; +}; +//----------------------------------------------------------------------------- +class BPF_CAP_SETTINGS +{ +public: + virtual ~BPF_CAP_SETTINGS(){}; + const string& GetStrError() const { return errorStr; } + int ParseSettings(const MODULE_SETTINGS & s); + string GetIface(unsigned int num); + +private: + vector iface; + mutable string errorStr; +}; +//----------------------------------------------------------------------------- +class BPF_CAP :public BASE_PLUGIN +{ +public: + BPF_CAP(); + virtual ~BPF_CAP(){}; + + void SetUsers(USERS *){}; + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER * tc); + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS *){}; + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + static void * Run(void *); + int BPFCapOpen(); + //int BPFCapOpen(int n); + int BPFCapOpen(BPF_DATA * bd); + int BPFCapClose(); + int BPFCapRead(char * buffer, int blen, char ** iface); + int BPFCapRead(char * buffer, int blen, char ** iface, BPF_DATA * bd); + + BPF_CAP_SETTINGS capSettings; + + mutable string errorStr; + + vector bpfData; + vector polld; + + pthread_t thread; + bool nonstop; + bool isRunning; + int capSock; + MODULE_SETTINGS settings; + + TRAFFCOUNTER * traffCnt; +}; +//----------------------------------------------------------------------------- + +#endif //ETHER_CAP_H + diff --git a/projects/stargazer/plugins/capture/ether_linux/Makefile b/projects/stargazer/plugins/capture/ether_linux/Makefile new file mode 100644 index 00000000..f10886c6 --- /dev/null +++ b/projects/stargazer/plugins/capture/ether_linux/Makefile @@ -0,0 +1,15 @@ +############################################################################### +# $Id: Makefile,v 1.9 2008/12/04 17:07:37 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_cap_ether.so + +SRCS = ./ether_cap.cpp + +LIBS += $(LIB_THREAD) +STGLIBS = -lstg_common + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/capture/ether_linux/ether_cap.cpp b/projects/stargazer/plugins/capture/ether_linux/ether_cap.cpp new file mode 100644 index 00000000..5c76be91 --- /dev/null +++ b/projects/stargazer/plugins/capture/ether_linux/ether_cap.cpp @@ -0,0 +1,292 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +Date: 18.09.2002 +*/ + +/* +* Author : Boris Mikhailenko +*/ + +/* +$Revision: 1.23 $ +$Date: 2009/12/13 13:45:13 $ +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ether_cap.h" +#include "common.h" +#include "raw_ip_packet.h" + +//#define CAP_DEBUG 1 + + +//----------------------------------------------------------------------------- +class ETHER_CAP_CREATOR +{ +private: + ETHER_CAP * ec; + +public: + ETHER_CAP_CREATOR() + : ec(new ETHER_CAP()) + { + }; + ~ETHER_CAP_CREATOR() + { + delete ec; + }; + + ETHER_CAP * GetCapturer() + { + return ec; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ETHER_CAP_CREATOR ecc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return ecc.GetCapturer(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string ETHER_CAP::GetVersion() const +{ +return "Ether_cap v.1.2"; +} +//----------------------------------------------------------------------------- +ETHER_CAP::ETHER_CAP() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void ETHER_CAP::SetTraffcounter(TRAFFCOUNTER * tc) +{ +traffCnt = tc; +} +//----------------------------------------------------------------------------- +const string & ETHER_CAP::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int ETHER_CAP::Start() +{ +if (isRunning) + return 0; + +if (EthCapOpen() < 0) + { + errorStr = "Cannot open socket!"; + printfd(__FILE__, "Cannot open socket\n"); + return -1; + } + +nonstop = true; + +if (pthread_create(&thread, NULL, Run, this) == 0) + { + return 0; + } + +errorStr = "Cannot create thread."; +printfd(__FILE__, "Cannot create thread\n"); +return -1; +} +//----------------------------------------------------------------------------- +int ETHER_CAP::Stop() +{ +if (!isRunning) + return 0; + +nonstop = false; + +//5 seconds to thread stops itself +for (int i = 0; i < 25 && isRunning; i++) + { + usleep(200000); + } +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + if (pthread_kill(thread, SIGUSR1)) + { + errorStr = "Cannot kill thread."; + return -1; + } + for (int i = 0; i < 25 && isRunning; ++i) + usleep(200000); + if (isRunning) + { + errorStr = "ETHER_CAP not stopped."; + printfd(__FILE__, "Cannot stop thread\n"); + return -1; + } + else + { + pthread_join(thread, NULL); + } + } + +EthCapClose(); +return 0; +} +//----------------------------------------------------------------------------- +bool ETHER_CAP::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * ETHER_CAP::Run(void * d) +{ +ETHER_CAP * dc = (ETHER_CAP *)d; +dc->isRunning = true; + +struct ETH_IP +{ +uint16_t ethHdr[8]; +RAW_PACKET rp; +char padding[4]; +char padding1[8]; +}; + +ETH_IP * ethIP; + +char ethip[sizeof(ETH_IP)]; + +memset(ðip, 0, sizeof(ETH_IP)); + +ethIP = (ETH_IP *)ðip; +ethIP->rp.dataLen = -1; + +char * iface = NULL; + +while (dc->nonstop) + { + if (dc->EthCapRead(ðip, 68 + 14, &iface)) + { + continue; + } + + if (ethIP->ethHdr[7] != 0x8) + continue; + + dc->traffCnt->Process(ethIP->rp); + } + +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t ETHER_CAP::GetStartPosition() const +{ +return 10; +} +//----------------------------------------------------------------------------- +uint16_t ETHER_CAP::GetStopPosition() const +{ +return 10; +} +//----------------------------------------------------------------------------- +int ETHER_CAP::EthCapOpen() +{ +capSock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); +return capSock; +} +//----------------------------------------------------------------------------- +int ETHER_CAP::EthCapClose() +{ +close(capSock); +return 0; +} +//----------------------------------------------------------------------------- +int ETHER_CAP::EthCapRead(void * buffer, int blen, char **) +{ +struct sockaddr_ll addr; +int addrLen, res; + +if (!WaitPackets(capSock)) + { + return ENODATA; + } + +addrLen = sizeof(addr); + +res = recvfrom(capSock, ((char*)buffer) + 2, blen, 0, (struct sockaddr *)&addr, (socklen_t*)&addrLen); + +if (-1 == res) + { + if (errno != EINTR) + { + printfd(__FILE__, "Error on recvfrom: '%s'\n", strerror(errno)); + } + return ENODATA; + } + +return 0; +} +//----------------------------------------------------------------------------- +bool ETHER_CAP::WaitPackets(int sd) const +{ +fd_set rfds; +FD_ZERO(&rfds); +FD_SET(sd, &rfds); + +struct timeval tv; +tv.tv_sec = 0; +tv.tv_usec = 500000; + +int res = select(sd + 1, &rfds, NULL, NULL, &tv); +if (res == -1) // Error + { + if (errno != EINTR) + { + printfd(__FILE__, "Error on select: '%s'\n", strerror(errno)); + } + return false; + } + +if (res == 0) // Timeout + { + return false; + } + +return true; +} diff --git a/projects/stargazer/plugins/capture/ether_linux/ether_cap.h b/projects/stargazer/plugins/capture/ether_linux/ether_cap.h new file mode 100644 index 00000000..9643076b --- /dev/null +++ b/projects/stargazer/plugins/capture/ether_linux/ether_cap.h @@ -0,0 +1,94 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + /* + $Revision: 1.12 $ + $Date: 2009/12/13 13:45:13 $ + */ + +/* +* Author : Boris Mikhailenko +*/ + +#ifndef ETHER_CAP_H +#define ETHER_CAP_H + +#include +#include + +#include "base_plugin.h" +#include "base_settings.h" +#include "../../../traffcounter.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +//----------------------------------------------------------------------------- +class ETHER_CAP_SETTINGS +{ +public: + const string& GetStrError() const { static string s; return s; } + int ParseSettings(const MODULE_SETTINGS &) { return 0; } +}; +//----------------------------------------------------------------------------- +class ETHER_CAP :public BASE_PLUGIN +{ +public: + ETHER_CAP(); + virtual ~ETHER_CAP(){}; + + void SetUsers(USERS *){}; + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER * tc); + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS *){}; + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + void SetSettings(const MODULE_SETTINGS &){}; + int ParseSettings(){ return 0; }; + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + static void * Run(void *); + int EthCapOpen(); + int EthCapClose(); + int EthCapRead(void * buffer, int blen, char ** iface); + bool WaitPackets(int sd) const; + + ETHER_CAP_SETTINGS capSettings; + + mutable string errorStr; + + pthread_t thread; + bool nonstop; + bool isRunning; + int capSock; + + TRAFFCOUNTER * traffCnt; +}; +//----------------------------------------------------------------------------- + +#endif //ETHER_CAP_H + diff --git a/projects/stargazer/plugins/capture/ipq_linux/.#ipq_cap.cpp.1.3 b/projects/stargazer/plugins/capture/ipq_linux/.#ipq_cap.cpp.1.3 new file mode 100644 index 00000000..de2f39be --- /dev/null +++ b/projects/stargazer/plugins/capture/ipq_linux/.#ipq_cap.cpp.1.3 @@ -0,0 +1,190 @@ +#include +#include +#include + +#include "ipq_cap.h" +#include "raw_ip_packet.h" +#include "libipq.h" + +class IPQ_CAP_CREATOR +{ +private: + IPQ_CAP * ic; + +public: + IPQ_CAP_CREATOR() + { + printf("constructor IPQ_CAP_CREATOR\n"); + ic = new IPQ_CAP(); + }; + ~IPQ_CAP_CREATOR() + { + printf("destructor IPQ_CAP_CREATOR\n"); + if (ic) + delete ic; + }; + + BASE_PLUGIN * GetCapturer() + { + return ic; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +IPQ_CAP_CREATOR icc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return icc.GetCapturer(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string IPQ_CAP::GetVersion() const +{ +return "ipq_cap v.1.1"; +} +//----------------------------------------------------------------------------- +IPQ_CAP::IPQ_CAP() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void IPQ_CAP::SetTraffcounter(TRAFFCOUNTER * tc) +{ +traffCnt = tc; +} +//----------------------------------------------------------------------------- +const string & IPQ_CAP::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::Start() +{ +if (isRunning) + return 0; +printfd(__FILE__, "IPQ_CAP::Start()\n"); +if (IPQCapOpen() < 0) + { + errorStr = "Cannot open socket!"; + return -1; + } +nonstop = true; +if (pthread_create(&thread, NULL, Run, this) == 0) + { + return 0; + } +errorStr = "Cannot create thread."; +return -1; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::Stop() +{ +if (!isRunning) + return 0; +IPQCapClose(); +nonstop = false; +//5 seconds to thread stops itself +for (int i = 0; i < 25; i++) + { + if (!isRunning) + break; + usleep(200000); + } +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + return -1; + } + } +return 0; +} +//----------------------------------------------------------------------------- +bool IPQ_CAP::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * IPQ_CAP::Run(void * d) +{ +RAW_PACKET raw_packet; +int status; + +sleep(2); +IPQ_CAP * dc = (IPQ_CAP *)d; +dc->isRunning = true; +memset(&raw_packet, 0, sizeof(raw_packet)); +raw_packet.dataLen = -1; +while (dc->nonstop) + { + status=dc->IPQCapRead(&raw_packet, 68); + if(status==-1||status==-2) + continue; + dc->traffCnt->Process(raw_packet); + } +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t IPQ_CAP::GetStartPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +uint16_t IPQ_CAP::GetStopPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::IPQCapOpen() +{ +int status; + +ipq_h = ipq_create_handle(0, PF_INET); +if (ipq_h == NULL) + { + ipq_destroy_handle(ipq_h); + errorStr = "Cannot create ipq handle!"; + return -1; + } +status = ipq_set_mode(ipq_h, IPQ_COPY_PACKET, PAYLOAD_LEN); +if (status < 0) + { + ipq_destroy_handle(ipq_h); + errorStr = "Cannot set IPQ_COPY_PACKET mode!"; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::IPQCapClose() +{ +ipq_destroy_handle(ipq_h); +return 0; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::IPQCapRead(void * buffer, int blen) +{ +int status; +static ipq_packet_msg_t *m; + +memset(buf, 0, BUFSIZE); +status = ipq_read(ipq_h, buf, BUFSIZE, 0); +if (status < 0) + return -1; +if (ipq_message_type(buf) != IPQM_PACKET) + return -2; +m = ipq_get_packet(buf); +memcpy(buffer, m->payload, blen); +ipq_set_verdict(ipq_h, m->packet_id, NF_ACCEPT, 0, NULL); +return 0; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/capture/ipq_linux/Makefile b/projects/stargazer/plugins/capture/ipq_linux/Makefile new file mode 100644 index 00000000..7cf92996 --- /dev/null +++ b/projects/stargazer/plugins/capture/ipq_linux/Makefile @@ -0,0 +1,16 @@ +############################################################################### +# $Id: Makefile,v 1.6 2008/12/04 17:08:56 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_cap_ipq.so + +SRCS = ./ipq_cap.cpp \ + ./libipq.c + +LIBS += $(LIB_THREAD) +STGLIBS = -lstg_common + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/capture/ipq_linux/ipq_cap.cpp b/projects/stargazer/plugins/capture/ipq_linux/ipq_cap.cpp new file mode 100644 index 00000000..9fc946ca --- /dev/null +++ b/projects/stargazer/plugins/capture/ipq_linux/ipq_cap.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include + +#include "ipq_cap.h" +#include "raw_ip_packet.h" + +extern "C" + { + #include "libipq.h" + } + +class IPQ_CAP_CREATOR +{ +private: + IPQ_CAP * ic; + +public: + IPQ_CAP_CREATOR() + : ic(new IPQ_CAP()) + { + }; + ~IPQ_CAP_CREATOR() + { + delete ic; + }; + + IPQ_CAP * GetCapturer() + { + return ic; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +IPQ_CAP_CREATOR icc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return icc.GetCapturer(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string IPQ_CAP::GetVersion() const +{ +return "ipq_cap v.1.2"; +} +//----------------------------------------------------------------------------- +IPQ_CAP::IPQ_CAP() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void IPQ_CAP::SetTraffcounter(TRAFFCOUNTER * tc) +{ +traffCnt = tc; +} +//----------------------------------------------------------------------------- +const string & IPQ_CAP::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::Start() +{ +if (isRunning) + return 0; +if (IPQCapOpen() < 0) + { + errorStr = "Cannot open socket!"; + printfd(__FILE__, "Cannot open socket\n"); + return -1; + } +nonstop = true; +if (pthread_create(&thread, NULL, Run, this) == 0) + { + return 0; + } +errorStr = "Cannot create thread."; +printfd(__FILE__, "Cannot create thread\n"); +return -1; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::Stop() +{ +if (!isRunning) + return 0; +nonstop = false; +//5 seconds to thread stops itself +for (int i = 0; i < 25; i++) + { + if (!isRunning) + break; + usleep(200000); + } +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + return -1; + } + for (int i = 0; i < 25 && isRunning; ++i) + { + usleep(200000); + } + if (isRunning) + { + printfd(__FILE__, "Thread not stopped\n"); + } + else + { + pthread_join(thread, NULL); + } + } +IPQCapClose(); +return 0; +} +//----------------------------------------------------------------------------- +bool IPQ_CAP::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * IPQ_CAP::Run(void * d) +{ +RAW_PACKET raw_packet; +int status; + +IPQ_CAP * dc = (IPQ_CAP *)d; +dc->isRunning = true; +memset(&raw_packet, 0, sizeof(raw_packet)); +raw_packet.dataLen = -1; +while (dc->nonstop) + { + status = dc->IPQCapRead(&raw_packet, 68); + if (status == -1 || + status == -2 || + status == -3 || + status == -4) + continue; + dc->traffCnt->Process(raw_packet); + } +dc->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t IPQ_CAP::GetStartPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +uint16_t IPQ_CAP::GetStopPosition() const +{ +return 0; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::IPQCapOpen() +{ +int status; + +ipq_h = ipq_create_handle(0, PF_INET); +if (ipq_h == NULL) + { + ipq_destroy_handle(ipq_h); + errorStr = "Cannot create ipq handle!"; + return -1; + } +status = ipq_set_mode(ipq_h, IPQ_COPY_PACKET, PAYLOAD_LEN); +if (status < 0) + { + ipq_destroy_handle(ipq_h); + errorStr = "Cannot set IPQ_COPY_PACKET mode!"; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::IPQCapClose() +{ +ipq_destroy_handle(ipq_h); +return 0; +} +//----------------------------------------------------------------------------- +int IPQ_CAP::IPQCapRead(void * buffer, int blen) +{ +int status; +static ipq_packet_msg_t *m; + +memset(buf, 0, BUFSIZE); +status = ipq_read(ipq_h, buf, BUFSIZE, 1); +if (status == 0) + return -4; +if (errno == EINTR) + return -3; +if (status < 0) + return -1; +if (ipq_message_type(buf) != IPQM_PACKET) + return -2; +m = ipq_get_packet(buf); +memcpy(buffer, m->payload, blen); +ipq_set_verdict(ipq_h, m->packet_id, NF_ACCEPT, 0, NULL); +return 0; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/capture/ipq_linux/ipq_cap.h b/projects/stargazer/plugins/capture/ipq_linux/ipq_cap.h new file mode 100644 index 00000000..3b088ada --- /dev/null +++ b/projects/stargazer/plugins/capture/ipq_linux/ipq_cap.h @@ -0,0 +1,56 @@ +#include + +#include "base_plugin.h" +#include "base_settings.h" +#include "../../../traffcounter.h" + +#define BUFSIZE (256) +#define PAYLOAD_LEN (96) + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +//----------------------------------------------------------------------------- +class IPQ_CAP :public BASE_PLUGIN +{ +public: + IPQ_CAP(); + virtual ~IPQ_CAP(){}; + + void SetUsers(USERS *){}; + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER * tc); + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS *){}; + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + void SetSettings(const MODULE_SETTINGS &){}; + int ParseSettings(){ return 0; }; + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + static void * Run(void *); + int IPQCapOpen(); + int IPQCapClose(); + int IPQCapRead(void * buffer, int blen); + + struct ipq_handle *ipq_h; + mutable string errorStr; + + pthread_t thread; + bool nonstop; + bool isRunning; + int capSock; + + TRAFFCOUNTER * traffCnt; + unsigned char buf[BUFSIZE]; +}; diff --git a/projects/stargazer/plugins/capture/ipq_linux/libipq.c b/projects/stargazer/plugins/capture/ipq_linux/libipq.c new file mode 100644 index 00000000..798faebb --- /dev/null +++ b/projects/stargazer/plugins/capture/ipq_linux/libipq.c @@ -0,0 +1,408 @@ +/* + * libipq.c + * + * IPQ userspace library. + * + * Please note that this library is still developmental, and there may + * be some API changes. + * + * Author: James Morris + * + * 07-11-2001 Modified by Fernando Anton to add support for IPv6. + * + * Copyright (c) 2000-2001 Netfilter Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "libipq.h" + +/**************************************************************************** + * + * Private interface + * + ****************************************************************************/ + +enum + { + IPQ_ERR_NONE = 0, + IPQ_ERR_IMPL, + IPQ_ERR_HANDLE, + IPQ_ERR_SOCKET, + IPQ_ERR_BIND, + IPQ_ERR_BUFFER, + IPQ_ERR_RECV, + IPQ_ERR_NLEOF, + IPQ_ERR_ADDRLEN, + IPQ_ERR_STRUNC, + IPQ_ERR_RTRUNC, + IPQ_ERR_NLRECV, + IPQ_ERR_SEND, + IPQ_ERR_SUPP, + IPQ_ERR_RECVBUF, + IPQ_ERR_TIMEOUT, + IPQ_ERR_PROTOCOL + }; +#define IPQ_MAXERR IPQ_ERR_PROTOCOL + +/*struct ipq_errmap_t + { + int errcode; + char *message; + } ipq_errmap[] = { + { IPQ_ERR_NONE, "Unknown error"}, + { IPQ_ERR_IMPL, "Implementation error"}, + { IPQ_ERR_HANDLE, "Unable to create netlink handle"}, + { IPQ_ERR_SOCKET, "Unable to create netlink socket"}, + { IPQ_ERR_BIND, "Unable to bind netlink socket"}, + { IPQ_ERR_BUFFER, "Unable to allocate buffer"}, + { IPQ_ERR_RECV, "Failed to receive netlink message"}, + { IPQ_ERR_NLEOF, "Received EOF on netlink socket"}, + { IPQ_ERR_ADDRLEN, "Invalid peer address length"}, + { IPQ_ERR_STRUNC, "Sent message truncated"}, + { IPQ_ERR_RTRUNC, "Received message truncated"}, + { IPQ_ERR_NLRECV, "Received error from netlink"}, + { IPQ_ERR_SEND, "Failed to send netlink message"}, + { IPQ_ERR_SUPP, "Operation not supported"}, + { IPQ_ERR_RECVBUF, "Receive buffer size invalid"}, + { IPQ_ERR_TIMEOUT, "Timeout"}, + { IPQ_ERR_PROTOCOL, "Invalid protocol specified"} +};*/ + +static int ipq_errno = IPQ_ERR_NONE; + +static ssize_t ipq_netlink_sendto(const struct ipq_handle *h, + const void *msg, size_t len); + +static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h, + unsigned char *buf, size_t len, + int timeout); + +static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h, + const struct msghdr *msg, + unsigned int flags); + +//static char *ipq_strerror(int errcode); +//----------------------------------------------------------------------------- +static ssize_t ipq_netlink_sendto(const struct ipq_handle *h, + const void *msg, size_t len) +{ + int status = sendto(h->fd, msg, len, 0, + (struct sockaddr *)&h->peer, sizeof(h->peer)); + if (status < 0) + ipq_errno = IPQ_ERR_SEND; + return status; +} +//----------------------------------------------------------------------------- +static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h, + const struct msghdr *msg, + unsigned int flags) +{ + int status = sendmsg(h->fd, msg, flags); + if (status < 0) + ipq_errno = IPQ_ERR_SEND; + return status; +} +//----------------------------------------------------------------------------- +static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h, + unsigned char *buf, size_t len, + int timeout) +{ + socklen_t addrlen; + int status; + struct nlmsghdr *nlh; + + if (len < sizeof(struct nlmsgerr)) + { + ipq_errno = IPQ_ERR_RECVBUF; + return -1; + } + addrlen = sizeof(h->peer); + + if (timeout != 0) + { + int ret; + struct timeval tv; + fd_set read_fds; + + if (timeout < 0) + { + /* non-block non-timeout */ + tv.tv_sec = 0; + tv.tv_usec = 0; + } + else + { + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + } + + FD_ZERO(&read_fds); + FD_SET(h->fd, &read_fds); + ret = select(h->fd+1, &read_fds, NULL, NULL, &tv); + if (ret < 0) + { + if (errno == EINTR) + { + return 0; + } + else + { + ipq_errno = IPQ_ERR_RECV; + return -1; + } + } + if (!FD_ISSET(h->fd, &read_fds)) + { + ipq_errno = IPQ_ERR_TIMEOUT; + return 0; + } + } + status = recvfrom(h->fd, buf, len, 0, + (struct sockaddr *)&h->peer, &addrlen); + if (status < 0) + { + ipq_errno = IPQ_ERR_RECV; + return status; + } + if (addrlen != sizeof(h->peer)) + { + ipq_errno = IPQ_ERR_RECV; + return -1; + } + if (h->peer.nl_pid != 0) + { + ipq_errno = IPQ_ERR_RECV; + return -1; + } + if (status == 0) + { + ipq_errno = IPQ_ERR_NLEOF; + return -1; + } + nlh = (struct nlmsghdr *)buf; + if (nlh->nlmsg_flags & MSG_TRUNC || (int)nlh->nlmsg_len > status) + { + ipq_errno = IPQ_ERR_RTRUNC; + return -1; + } + return status; +} +//----------------------------------------------------------------------------- +/*static char *ipq_strerror(int errcode) +{ + if (errcode < 0 || errcode > IPQ_MAXERR) + errcode = IPQ_ERR_IMPL; + return ipq_errmap[errcode].message; +}*/ + +/**************************************************************************** + * + * Public interface + * + ****************************************************************************/ + +/* + * Create and initialise an ipq handle. + */ +struct ipq_handle *ipq_create_handle(u_int32_t __attribute__((unused)) flags, u_int32_t protocol) + { + int status; + struct ipq_handle *h; + + h = (struct ipq_handle *)malloc(sizeof(struct ipq_handle)); + if (h == NULL) + { + ipq_errno = IPQ_ERR_HANDLE; + return NULL; + } + + memset(h, 0, sizeof(struct ipq_handle)); + + if (protocol == PF_INET) + h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_FIREWALL); + else if (protocol == PF_INET6) + h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_IP6_FW); + else + { + ipq_errno = IPQ_ERR_PROTOCOL; + free(h); + return NULL; + } + + if (h->fd == -1) + { + ipq_errno = IPQ_ERR_SOCKET; + close(h->fd); + free(h); + return NULL; + } + memset(&h->local, 0, sizeof(struct sockaddr_nl)); + h->local.nl_family = AF_NETLINK; + h->local.nl_pid = getpid(); + h->local.nl_groups = 0; + status = bind(h->fd, (struct sockaddr *)&h->local, sizeof(h->local)); + if (status == -1) + { + ipq_errno = IPQ_ERR_BIND; + close(h->fd); + free(h); + return NULL; + } + memset(&h->peer, 0, sizeof(struct sockaddr_nl)); + h->peer.nl_family = AF_NETLINK; + h->peer.nl_pid = 0; + h->peer.nl_groups = 0; + return h; + } +//----------------------------------------------------------------------------- +/* + * No error condition is checked here at this stage, but it may happen + * if/when reliable messaging is implemented. + */ +int ipq_destroy_handle(struct ipq_handle *h) +{ + if (h) + { + close(h->fd); + free(h); + } + return 0; +} +//----------------------------------------------------------------------------- +int ipq_set_mode(const struct ipq_handle *h, + u_int8_t mode, size_t range) +{ + #define FAKE_ARRAY_SIZE 16 + struct + { + struct nlmsghdr nlh; + ipq_peer_msg_t pm; + char s[FAKE_ARRAY_SIZE]; + } req; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req)-FAKE_ARRAY_SIZE); + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_type = IPQM_MODE; + req.nlh.nlmsg_pid = h->local.nl_pid; + req.pm.msg.mode.value = mode; + req.pm.msg.mode.range = range; + return ipq_netlink_sendto(h, (void *)&req, req.nlh.nlmsg_len); + //return ipq_netlink_sendto(h, (void *)&req, sizeof(req)); +} +//----------------------------------------------------------------------------- +/* + * timeout is in microseconds (1 second is 1000000 (1 million) microseconds) + * + */ +ssize_t ipq_read(const struct ipq_handle *h, + unsigned char *buf, size_t len, int timeout) +{ + return ipq_netlink_recvfrom(h, buf, len, timeout); +} +//----------------------------------------------------------------------------- +int ipq_message_type(const unsigned char *buf) +{ + return((struct nlmsghdr*)buf)->nlmsg_type; +} +//----------------------------------------------------------------------------- +int ipq_get_msgerr(const unsigned char *buf) +{ + struct nlmsghdr *h = (struct nlmsghdr *)buf; + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + return -err->error; +} +//----------------------------------------------------------------------------- +ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf) +{ + return(ipq_packet_msg_t *)(NLMSG_DATA((struct nlmsghdr *)(buf))); +} +//----------------------------------------------------------------------------- +int ipq_set_verdict(const struct ipq_handle *h, + ipq_id_t id, + unsigned int verdict, + size_t data_len, + unsigned char *buf) +{ + unsigned char nvecs; + size_t tlen; + struct nlmsghdr nlh; + ipq_peer_msg_t pm; + struct iovec iov[3]; + struct msghdr msg; + + memset(&nlh, 0, sizeof(nlh)); + nlh.nlmsg_flags = NLM_F_REQUEST; + nlh.nlmsg_type = IPQM_VERDICT; + nlh.nlmsg_pid = h->local.nl_pid; + memset(&pm, 0, sizeof(pm)); + pm.msg.verdict.value = verdict; + pm.msg.verdict.id = id; + pm.msg.verdict.data_len = data_len; + iov[0].iov_base = &nlh; + iov[0].iov_len = sizeof(nlh); + iov[1].iov_base = ± + iov[1].iov_len = sizeof(pm); + tlen = sizeof(nlh) + sizeof(pm); + nvecs = 2; + if (data_len && buf) + { + iov[2].iov_base = buf; + iov[2].iov_len = data_len; + tlen += data_len; + nvecs++; + } + msg.msg_name = (void *)&h->peer; + msg.msg_namelen = sizeof(h->peer); + msg.msg_iov = iov; + msg.msg_iovlen = nvecs; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + nlh.nlmsg_len = tlen; + return ipq_netlink_sendmsg(h, &msg, 0); +} +//----------------------------------------------------------------------------- +/* Not implemented yet */ +int ipq_ctl(const struct ipq_handle __attribute__((unused)) * handle, int __attribute__((unused)) request, ...) +{ + return 1; +} +//----------------------------------------------------------------------------- +/*char *ipq_errstr(void) +{ + return ipq_strerror(ipq_errno); +}*/ +//----------------------------------------------------------------------------- +/*void ipq_perror(const char *s) +{ + if (s) + fputs(s, stderr); + else + fputs("ERROR", stderr); + if (ipq_errno) + fprintf(stderr, ": %s", ipq_errstr()); + if (errno) + fprintf(stderr, ": %s", strerror(errno)); + fputc('\n', stderr); +}*/ +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/plugins/capture/ipq_linux/libipq.h b/projects/stargazer/plugins/capture/ipq_linux/libipq.h new file mode 100644 index 00000000..41cca57e --- /dev/null +++ b/projects/stargazer/plugins/capture/ipq_linux/libipq.h @@ -0,0 +1,89 @@ +/* + * libipq.h + * + * IPQ library for userspace. + * + * Author: James Morris + * + * Copyright (c) 2000-2001 Netfilter Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LIBIPQ_H +#define _LIBIPQ_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERNEL_64_USERSPACE_32 +#include "ip_queue_64.h" +typedef u_int64_t ipq_id_t; +#else +#include +typedef unsigned long ipq_id_t; +#endif + +#ifdef DEBUG_LIBIPQ +#include +#define LDEBUG(x...) fprintf(stderr, ## x) +#else +#define LDEBUG(x...) +#endif /* DEBUG_LIBIPQ */ + +/* FIXME: glibc sucks */ +#ifndef MSG_TRUNC +#define MSG_TRUNC 0x20 +#endif + +struct ipq_handle +{ + int fd; + u_int8_t blocking; + struct sockaddr_nl local; + struct sockaddr_nl peer; +}; + +struct ipq_handle *ipq_create_handle(u_int32_t flags, u_int32_t protocol); + +int ipq_destroy_handle(struct ipq_handle *h); + +ssize_t ipq_read(const struct ipq_handle *h, + unsigned char *buf, size_t len, int timeout); + +int ipq_set_mode(const struct ipq_handle *h, u_int8_t mode, size_t len); + +ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf); + +int ipq_message_type(const unsigned char *buf); + +int ipq_get_msgerr(const unsigned char *buf); + +int ipq_set_verdict(const struct ipq_handle *h, + ipq_id_t id, + unsigned int verdict, + size_t data_len, + unsigned char *buf); + +int ipq_ctl(const struct ipq_handle *h, int request, ...); + +/*char *ipq_errstr(void); +void ipq_perror(const char *s);*/ + +#endif /* _LIBIPQ_H */ + diff --git a/projects/stargazer/plugins/configuration/rpcconfig/Makefile b/projects/stargazer/plugins/configuration/rpcconfig/Makefile new file mode 100644 index 00000000..c2fb03d6 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/Makefile @@ -0,0 +1,30 @@ +############################################################################### +# $Id: Makefile,v 1.10 2009/08/03 10:25:40 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_conf_rpc.so + +SRCS = ./rpcconfig.cpp \ + ./user_helper.cpp \ + ./tariff_helper.cpp \ + ./info_methods.cpp \ + ./users_methods.cpp \ + ./tariffs_methods.cpp \ + ./admins_methods.cpp \ + ./messages_methods.cpp + +XMLRPC_C_LIBS = $(shell xmlrpc-c-config c++2 abyss-server --libs) + +LIBS += $(XMLRPC_C_LIBS) \ + $(LIB_THREAD) + +ifneq ($(OS),linux) +LIBS += -liconv +endif + +STGLIBS = -lstg_common -lstg_logger + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/configuration/rpcconfig/admins_methods.cpp b/projects/stargazer/plugins/configuration/rpcconfig/admins_methods.cpp new file mode 100644 index 00000000..4eae81fd --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/admins_methods.cpp @@ -0,0 +1,256 @@ +#include "admins_methods.h" + +#include "rpcconfig.h" + +//------------------------------------------------------------------------------ + +void METHOD_ADMIN_GET::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +paramList.verifyEnd(2); + +std::map structVal; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(login, &admin)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +structVal["result"] = xmlrpc_c::value_boolean(true); +structVal["login"] = xmlrpc_c::value_string(admin.GetLogin()); +structVal["password"] = xmlrpc_c::value_string(admin.GetPassword()); + +const PRIV * priv = admin.GetPriv(); + +structVal["user_stat"] = xmlrpc_c::value_boolean(priv->userStat); +structVal["user_conf"] = xmlrpc_c::value_boolean(priv->userConf); +structVal["user_cash"] = xmlrpc_c::value_boolean(priv->userCash); +structVal["user_passwd"] = xmlrpc_c::value_boolean(priv->userPasswd); +structVal["user_add_del"] = xmlrpc_c::value_boolean(priv->userAddDel); +structVal["admin_chg"] = xmlrpc_c::value_boolean(priv->adminChg); +structVal["tariff_chg"] = xmlrpc_c::value_boolean(priv->tariffChg); + +*retvalPtr = xmlrpc_c::value_struct(structVal); +} + +//------------------------------------------------------------------------------ + +void METHOD_ADMIN_ADD::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +paramList.verifyEnd(2); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + printfd(__FILE__, "METHOD_ADMIN_ADD::execute(): 'Not logged or cookie timeout'\n"); + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + printfd(__FILE__, "METHOD_ADMIN_ADD::execute(): 'Invalid admin (logged)'\n"); + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +if (admins->Add(login, admin)) + { + printfd(__FILE__, "METHOD_ADMIN_ADD::execute(): 'Failed to add admin'\n"); + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +//------------------------------------------------------------------------------ + +void METHOD_ADMIN_DEL::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +paramList.verifyEnd(2); + +std::map structVal; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +if (admins->Del(login, admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +//------------------------------------------------------------------------------ + +void METHOD_ADMIN_CHG::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +xmlrpc_c::value_struct info(paramList.getStruct(2)); +paramList.verifyEnd(3); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN loggedAdmin; + +if (admins->FindAdmin(adminInfo.admin, &loggedAdmin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(login, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN_CONF conf; + +conf.priv = *admin.GetPriv(); +conf.password = admin.GetPassword(); +conf.login = login; + +std::map structVal( + static_cast >(xmlrpc_c::value_struct(info)) + ); + +std::map::iterator it; + +if ((it = structVal.find("password")) != structVal.end()) + { + conf.password = xmlrpc_c::value_string(it->second); + } + +if ((it = structVal.find("user_stat")) != structVal.end()) + { + conf.priv.userStat = xmlrpc_c::value_boolean(it->second); + } + +if ((it = structVal.find("user_conf")) != structVal.end()) + { + conf.priv.userConf = xmlrpc_c::value_boolean(it->second); + } + +if ((it = structVal.find("user_cash")) != structVal.end()) + { + conf.priv.userCash = xmlrpc_c::value_boolean(it->second); + } + +if ((it = structVal.find("user_passwd")) != structVal.end()) + { + conf.priv.userPasswd = xmlrpc_c::value_boolean(it->second); + } + +if ((it = structVal.find("user_add_del")) != structVal.end()) + { + conf.priv.userAddDel = xmlrpc_c::value_boolean(it->second); + } + +if ((it = structVal.find("admin_chg")) != structVal.end()) + { + conf.priv.adminChg = xmlrpc_c::value_boolean(it->second); + } + +if ((it = structVal.find("tariff_chg")) != structVal.end()) + { + conf.priv.tariffChg = xmlrpc_c::value_boolean(it->second); + } + +if (admins->Change(conf, loggedAdmin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + } + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +//------------------------------------------------------------------------------ + +void METHOD_ADMINS_GET::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +paramList.verifyEnd(1); + +std::map structVal; +std::vector retval; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +ADMIN_CONF ac; +int h = admins->OpenSearch(); + +while (admins->SearchNext(h, &ac) == 0) + { + std::map structVal; + structVal["result"] = xmlrpc_c::value_boolean(true); + structVal["login"] = xmlrpc_c::value_string(ac.login); + structVal["password"] = xmlrpc_c::value_string(ac.password); + structVal["user_stat"] = xmlrpc_c::value_boolean(ac.priv.userStat); + structVal["user_conf"] = xmlrpc_c::value_boolean(ac.priv.userConf); + structVal["user_cash"] = xmlrpc_c::value_boolean(ac.priv.userCash); + structVal["user_passwd"] = xmlrpc_c::value_boolean(ac.priv.userPasswd); + structVal["user_add_del"] = xmlrpc_c::value_boolean(ac.priv.userAddDel); + structVal["admin_chg"] = xmlrpc_c::value_boolean(ac.priv.adminChg); + structVal["tariff_chg"] = xmlrpc_c::value_boolean(ac.priv.tariffChg); + + retval.push_back(xmlrpc_c::value_struct(structVal)); + } + +*retvalPtr = xmlrpc_c::value_array(retval); +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/admins_methods.h b/projects/stargazer/plugins/configuration/rpcconfig/admins_methods.h new file mode 100644 index 00000000..7c5ceb10 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/admins_methods.h @@ -0,0 +1,92 @@ +#ifndef __ADMINS_METHODS_H__ +#define __ADMINS_METHODS_H__ + +#include +#include + +#include "../../../admins.h" +#include "../../../admin.h" + +class RPC_CONFIG; + +class METHOD_ADMIN_GET : public xmlrpc_c::method { +public: + METHOD_ADMIN_GET(RPC_CONFIG * c, + ADMINS * a) + : config(c), + admins(a) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + ADMINS * admins; +}; + +class METHOD_ADMIN_ADD : public xmlrpc_c::method { +public: + METHOD_ADMIN_ADD(RPC_CONFIG * c, + ADMINS * a) + : config(c), + admins(a) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + ADMINS * admins; +}; + +class METHOD_ADMIN_DEL : public xmlrpc_c::method { +public: + METHOD_ADMIN_DEL(RPC_CONFIG * c, + ADMINS * a) + : config(c), + admins(a) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + ADMINS * admins; +}; + +class METHOD_ADMIN_CHG : public xmlrpc_c::method { +public: + METHOD_ADMIN_CHG(RPC_CONFIG * c, + ADMINS * a) + : config(c), + admins(a) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + ADMINS * admins; +}; + +class METHOD_ADMINS_GET : public xmlrpc_c::method { +public: + METHOD_ADMINS_GET(RPC_CONFIG * c, + ADMINS * a) + : config(c), + admins(a) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + ADMINS * admins; +}; + +#endif diff --git a/projects/stargazer/plugins/configuration/rpcconfig/deps b/projects/stargazer/plugins/configuration/rpcconfig/deps new file mode 100644 index 00000000..65b198a4 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/deps @@ -0,0 +1,362 @@ +rpcconfig.o: rpcconfig.cpp rpcconfig.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + ../../../admins.h ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../users.h ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/dotconfpp.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/mempool.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + ../../../user.h ../../../tariff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + ../../../curr_ip.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + ../../../user_property.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/notifer.h \ + ../../../script_executer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_auth.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + ../../../tariffs.h ../../../actions.h ../../../actions.inl.h \ + ../../../eventloop.h ../../../tariffs.h ../../../traffcounter.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/raw_ip_packet.h \ + ../../../users.h ../../../settings.h info_methods.h users_methods.h \ + ../../../user.h tariffs_methods.h admins_methods.h messages_methods.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX +user_helper.o: user_helper.cpp user_helper.h ../../../users.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/dotconfpp.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/mempool.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../user.h ../../../tariff.h ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + ../../../curr_ip.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + ../../../user_property.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/notifer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + ../../../script_executer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_auth.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../tariffs.h ../../../actions.h ../../../actions.inl.h \ + ../../../eventloop.h ../../../admin.h utils.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX +tariff_helper.o: tariff_helper.cpp tariff_helper.h ../../../tariff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX +info_methods.o: info_methods.cpp info_methods.h ../../../users.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/dotconfpp.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/mempool.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../user.h ../../../tariff.h ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + ../../../curr_ip.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + ../../../user_property.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/notifer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + ../../../script_executer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_auth.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../tariffs.h ../../../actions.h ../../../actions.inl.h \ + ../../../eventloop.h ../../../tariffs.h ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/version.h \ + rpcconfig.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + ../../../admin.h ../../../admins.h ../../../traffcounter.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/raw_ip_packet.h \ + ../../../users.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX +users_methods.o: users_methods.cpp users_methods.h ../../../users.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/dotconfpp.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/mempool.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../user.h ../../../tariff.h ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + ../../../curr_ip.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + ../../../user_property.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/notifer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + ../../../script_executer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_auth.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../tariffs.h ../../../actions.h ../../../actions.inl.h \ + ../../../eventloop.h ../../../user.h rpcconfig.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + ../../../admin.h ../../../admins.h ../../../tariffs.h \ + ../../../traffcounter.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/raw_ip_packet.h \ + ../../../users.h ../../../settings.h user_helper.h utils.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX +tariffs_methods.o: tariffs_methods.cpp rpcconfig.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + ../../../admins.h ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../users.h ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/dotconfpp.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/mempool.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + ../../../user.h ../../../tariff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + ../../../curr_ip.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + ../../../user_property.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/notifer.h \ + ../../../script_executer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_auth.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + ../../../tariffs.h ../../../actions.h ../../../actions.inl.h \ + ../../../eventloop.h ../../../tariffs.h ../../../traffcounter.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/raw_ip_packet.h \ + ../../../users.h ../../../settings.h tariffs_methods.h tariff_helper.h \ + ../../../tariff.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX +admins_methods.o: admins_methods.cpp admins_methods.h ../../../admins.h \ + ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../admin.h rpcconfig.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + ../../../users.h ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/dotconfpp.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/mempool.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + ../../../user.h ../../../tariff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + ../../../curr_ip.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + ../../../user_property.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/notifer.h \ + ../../../script_executer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_auth.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + ../../../tariffs.h ../../../actions.h ../../../actions.inl.h \ + ../../../eventloop.h ../../../tariffs.h ../../../traffcounter.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/raw_ip_packet.h \ + ../../../users.h ../../../settings.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX +messages_methods.o: messages_methods.cpp messages_methods.h \ + ../../../users.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ../../../settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/dotconfpp.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/mempool.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_logger.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../user.h ../../../tariff.h ../../../admin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.inc.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + ../../../curr_ip.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + ../../../user_property.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/notifer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + ../../../script_executer.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_auth.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + ../../../tariffs.h ../../../actions.h ../../../actions.inl.h \ + ../../../eventloop.h rpcconfig.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/base_plugin.h \ + ../../../admin.h ../../../admins.h ../../../tariffs.h \ + ../../../traffcounter.h \ + /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include/raw_ip_packet.h \ + ../../../users.h ../../../settings.h utils.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /home/faust/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX diff --git a/projects/stargazer/plugins/configuration/rpcconfig/info_methods.cpp b/projects/stargazer/plugins/configuration/rpcconfig/info_methods.cpp new file mode 100644 index 00000000..fdb56048 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/info_methods.cpp @@ -0,0 +1,89 @@ +#include "info_methods.h" + +#include +#include "version.h" +#include "rpcconfig.h" + +void METHOD_INFO::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +paramList.verifyEnd(0); +std::map structVal; + +std::string un; +struct utsname utsn; + +uname(&utsn); +un[0] = 0; + +un += utsn.sysname; +un += " "; +un += utsn.release; +un += " "; +un += utsn.machine; +un += " "; +un += utsn.nodename; + +structVal["version"] = xmlrpc_c::value_string(SERVER_VERSION); +structVal["tariff_num"] = xmlrpc_c::value_int(tariffs->GetTariffsNum()); +structVal["tariff"] = xmlrpc_c::value_int(2); +structVal["users_num"] = xmlrpc_c::value_int(users->GetUserNum()); +structVal["uname"] = xmlrpc_c::value_string(un); +structVal["dir_num"] = xmlrpc_c::value_int(DIR_NUM); +structVal["day_fee"] = xmlrpc_c::value_int(settings->GetDayFee()); + +std::vector dirnameVal; + +for (int i = 0; i< DIR_NUM; i++) + { + string dn2e; + Encode12str(dn2e, settings->GetDirName(i)); + dirnameVal.push_back(xmlrpc_c::value_string(dn2e)); + } + +structVal["dir_names"] = xmlrpc_c::value_array(dirnameVal); + +*retvalPtr = xmlrpc_c::value_struct(structVal); +} + +void METHOD_LOGIN::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string login = paramList.getString(0); +std::string password = paramList.getString(1); +paramList.verifyEnd(2); + +std::map structVal; + +std::string cookie; +if (config->CheckAdmin(login, password, &cookie)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + structVal["cookie"] = xmlrpc_c::value_string(""); + } +else + { + structVal["result"] = xmlrpc_c::value_boolean(true); + structVal["cookie"] = xmlrpc_c::value_string(cookie); + } + +*retvalPtr = xmlrpc_c::value_struct(structVal); +} + +void METHOD_LOGOUT::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +paramList.verifyEnd(1); + +std::map structVal; + +if (config->LogoutAdmin(cookie)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + } +else + { + *retvalPtr = xmlrpc_c::value_boolean(true); + } +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/info_methods.h b/projects/stargazer/plugins/configuration/rpcconfig/info_methods.h new file mode 100644 index 00000000..37bbb40d --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/info_methods.h @@ -0,0 +1,62 @@ +#ifndef __INFO_METHODS_H__ +#define __INFO_METHODS_H__ + +#include +#include + +#include "../../../users.h" +#include "../../../tariffs.h" +#include "../../../settings.h" + +// Forward declaration +class RPC_CONFIG; + +class METHOD_INFO : public xmlrpc_c::method +{ +public: + METHOD_INFO(TARIFFS * t, + USERS * u, + const SETTINGS * s) + : tariffs(t), + users(u), + settings(s) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + TARIFFS * tariffs; + USERS * users; + const SETTINGS * settings; +}; + +class METHOD_LOGIN : public xmlrpc_c::method +{ +public: + METHOD_LOGIN(RPC_CONFIG * c) + : config(c) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; +}; + +class METHOD_LOGOUT : public xmlrpc_c::method +{ +public: + METHOD_LOGOUT(RPC_CONFIG * c) + : config(c) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; +}; + +#endif diff --git a/projects/stargazer/plugins/configuration/rpcconfig/messages_methods.cpp b/projects/stargazer/plugins/configuration/rpcconfig/messages_methods.cpp new file mode 100644 index 00000000..281041e7 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/messages_methods.cpp @@ -0,0 +1,93 @@ +#include "messages_methods.h" + +#include "rpcconfig.h" +#include "stg_message.h" +#include "utils.h" + +//------------------------------------------------------------------------------ + +void METHOD_MESSAGE_SEND::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::vector logins(paramList.getArray(1)); +std::map msgInfo(paramList.getStruct(2)); +paramList.verifyEnd(3); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +STG_MSG message; + +std::map::iterator it; + +if ((it = msgInfo.find("version")) == msgInfo.end()) + { + message.header.ver = 1; // Default value + } +else + { + message.header.ver = xmlrpc_c::value_int(it->second); + } + +if ((it = msgInfo.find("type")) == msgInfo.end()) + { + message.header.type = 1; // default value + } +else + { + message.header.type = xmlrpc_c::value_int(it->second); + } + +if ((it = msgInfo.find("repeat")) == msgInfo.end()) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } +message.header.repeat = xmlrpc_c::value_int(it->second); + +if ((it = msgInfo.find("repeat_period")) == msgInfo.end()) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } +message.header.repeatPeriod = xmlrpc_c::value_int(it->second); + +if ((it = msgInfo.find("show_time")) == msgInfo.end()) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } +message.header.showTime = xmlrpc_c::value_int(it->second); + +if ((it = msgInfo.find("text")) == msgInfo.end()) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } +message.text = IconvString(xmlrpc_c::value_string(it->second), "UTF-8", "CP1251"); + +message.header.creationTime = stgTime; +message.header.lastSendTime = 0; + +std::vector::iterator lit; +for (lit = logins.begin(); lit != logins.end(); ++lit) + { + user_iter ui; + if (users->FindByName(xmlrpc_c::value_string(*lit), &ui)) + { + printfd(__FILE__, "METHOD_MESSAGE_SEND::execute(): 'User '%s' not found'\n", std::string(xmlrpc_c::value_string(*lit)).c_str()); + } + else + { + ui->AddMessage(&message); + } + } + +*retvalPtr = xmlrpc_c::value_boolean(true); +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/messages_methods.h b/projects/stargazer/plugins/configuration/rpcconfig/messages_methods.h new file mode 100644 index 00000000..c82ad13e --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/messages_methods.h @@ -0,0 +1,27 @@ +#ifndef __MESSAGES_METHODS_H__ +#define __MESSAGES_METHODS_H__ + +#include +#include + +#include "../../../users.h" + +class RPC_CONFIG; + +class METHOD_MESSAGE_SEND : public xmlrpc_c::method { +public: + METHOD_MESSAGE_SEND(RPC_CONFIG * c, + USERS * u) + : config(c), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + USERS * users; +}; + +#endif diff --git a/projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.cpp b/projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.cpp new file mode 100644 index 00000000..c059ba61 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.cpp @@ -0,0 +1,420 @@ +#include "rpcconfig.h" + +#include +#include +#include "info_methods.h" +#include "users_methods.h" +#include "tariffs_methods.h" +#include "admins_methods.h" +#include "messages_methods.h" + +class RPC_CONFIG_CREATOR +{ +private: + RPC_CONFIG * rpcconfig; + +public: + RPC_CONFIG_CREATOR() + : rpcconfig(new RPC_CONFIG()) + { + }; + ~RPC_CONFIG_CREATOR() + { + delete rpcconfig; + }; + + RPC_CONFIG * GetPlugin() + { + return rpcconfig; + }; +}; + +RPC_CONFIG_CREATOR rpcc; + +RPC_CONFIG_SETTINGS::RPC_CONFIG_SETTINGS() + : errorStr(), + port(0), + cookieTimeout(0) +{ +} + +int RPC_CONFIG_SETTINGS::ParseIntInRange(const string & str, + int min, + int max, + int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} + +int RPC_CONFIG_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +int p; +PARAM_VALUE pv; +vector::const_iterator pvi; + +pv.param = "Port"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Port\' not found."; + printfd(__FILE__, "Parameter 'Port' not found\n"); + return -1; + } +if (ParseIntInRange(pvi->value[0], 2, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'Port'\n"); + return -1; + } +port = p; + +pv.param = "CookieTimeout"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + cookieTimeout = 1800; // 30 * 60 + } +else + { + if (str2x(pvi->value[0], cookieTimeout)) + { + errorStr = "Incorrect value of CookieTimeout: \'" + pvi->value[0] + "\'"; + printfd(__FILE__, "Incorrect value of 'CookieTimeout'\n"); + return -1; + } + } + +return 0; +} + +BASE_PLUGIN * GetPlugin() +{ +return rpcc.GetPlugin(); +} + +RPC_CONFIG::RPC_CONFIG() + : rpcServer(NULL) +{ + +} + +RPC_CONFIG::~RPC_CONFIG() +{ +// delete server +delete rpcServer; +} + +int RPC_CONFIG::ParseSettings() +{ +int ret = rpcConfigSettings.ParseSettings(settings); + +if (ret) + errorStr = rpcConfigSettings.GetStrError(); + +return ret; +} + +int RPC_CONFIG::Start() +{ +InitiateRegistry(); +running = true; +rpcServer = new xmlrpc_c::serverAbyss( + rpcRegistry, + rpcConfigSettings.GetPort(), + "/var/log/stargazer_rpc.log" + ); +if (pthread_create(&tid, NULL, Run, this)) + { + errorStr = "Failed to create RPC thread"; + printfd(__FILE__, "Failed to crate RPC thread\n"); + return -1; + } +return 0; +} + +int RPC_CONFIG::Stop() +{ +running = false; +for (int i = 0; i < 5 && !stopped; ++i) + usleep(200000); +//rpcServer->terminate(); +if (!stopped) + { + if (pthread_kill(tid, SIGTERM)) + { + errorStr = "Failed to kill thread"; + printfd(__FILE__, "Failed to kill thread\n"); + } + for (int i = 0; i < 25 && !stopped; ++i) + usleep(200000); + if (!stopped) + { + printfd(__FILE__, "Failed to stop RPC thread\n"); + errorStr = "Failed to stop RPC thread"; + return -1; + } + else + { + pthread_join(tid, NULL); + } + } +return 0; +} + +void * RPC_CONFIG::Run(void * rc) +{ +RPC_CONFIG * config = static_cast(rc); + +config->stopped = false; +while (config->running) + { + config->rpcServer->runOnce(); + } +config->stopped = true; + +return NULL; +} + +bool RPC_CONFIG::GetAdminInfo(const std::string & cookie, + ADMIN_INFO * info) +{ +std::map::iterator it; + +it = cookies.find(cookie); + +if (it == cookies.end()) + { + return true; + } + +if (difftime(it->second.accessTime, time(NULL)) > + rpcConfigSettings.GetCookieTimeout()) + { + cookies.erase(it); + return true; + } + +// Update access time +time(&it->second.accessTime); +*info = it->second; +return false; +} + +bool RPC_CONFIG::CheckAdmin(const std::string & login, + const std::string & password, + std::string * cookie) +{ +ADMIN admin; + +if (!admins->AdminCorrect(login, password, &admin)) + { + return true; + } + +ADMIN_INFO info; +time(&info.accessTime); +info.admin = login; +info.priviledges = *admin.GetPriv(); +*cookie = GetCookie(); +cookies[*cookie] = info; + +return false; +} + +bool RPC_CONFIG::LogoutAdmin(const std::string & cookie) +{ +std::map::iterator it; + +it = cookies.find(cookie); + +if (it == cookies.end()) + { + return true; + } + +cookies.erase(it); + +return false; +} + +std::string RPC_CONFIG::GetCookie() const +{ +std::string charset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); +std::string cookie; + +for (int i = 0; i < 64; ++i) + { + cookie += charset[rand() % charset.length()]; + }; + +return cookie; +} + +void RPC_CONFIG::InitiateRegistry() +{ +// manage registry +xmlrpc_c::methodPtr const methodInfoPtr(new METHOD_INFO( + tariffs, + users, + stgSettings + )); +rpcRegistry.addMethod("stargazer.info", methodInfoPtr); + +xmlrpc_c::methodPtr const methodLoginPtr(new METHOD_LOGIN( + this + )); +rpcRegistry.addMethod("stargazer.login", methodLoginPtr); + +xmlrpc_c::methodPtr const methodLogoutPtr(new METHOD_LOGOUT( + this + )); +rpcRegistry.addMethod("stargazer.logout", methodLogoutPtr); + +xmlrpc_c::methodPtr const methodGetUserPtr(new METHOD_USER_GET( + this, + users + )); +rpcRegistry.addMethod("stargazer.get_user", methodGetUserPtr); + +xmlrpc_c::methodPtr const methodAddUserPtr(new METHOD_USER_ADD( + this, + admins, + users + )); +rpcRegistry.addMethod("stargazer.add_user", methodAddUserPtr); + +xmlrpc_c::methodPtr const methodDelUserPtr(new METHOD_USER_DEL( + this, + admins, + users + )); +rpcRegistry.addMethod("stargazer.del_user", methodDelUserPtr); + +xmlrpc_c::methodPtr const methodGetUsersPtr(new METHOD_USERS_GET( + this, + users + )); +rpcRegistry.addMethod("stargazer.get_users", methodGetUsersPtr); + +xmlrpc_c::methodPtr const methodChgUserPtr(new METHOD_USER_CHG( + this, + admins, + store, + users + )); +rpcRegistry.addMethod("stargazer.chg_user", methodChgUserPtr); + +xmlrpc_c::methodPtr const methodAddCashPtr(new METHOD_USER_CASH_ADD( + this, + admins, + store, + users + )); +rpcRegistry.addMethod("stargazer.add_cash", methodAddCashPtr); + +xmlrpc_c::methodPtr const methodSetCashPtr(new METHOD_USER_CASH_SET( + this, + admins, + store, + users + )); +rpcRegistry.addMethod("stargazer.set_cash", methodSetCashPtr); + +xmlrpc_c::methodPtr const methodTariffChangePtr(new METHOD_USER_TARIFF_CHANGE( + this, + admins, + tariffs, + store, + users + )); +rpcRegistry.addMethod("stargazer.tariff_change", methodTariffChangePtr); + +xmlrpc_c::methodPtr const methodGetTariffPtr(new METHOD_TARIFF_GET( + this, + tariffs + )); +rpcRegistry.addMethod("stargazer.get_tariff", methodGetTariffPtr); + +xmlrpc_c::methodPtr const methodChgTariffPtr(new METHOD_TARIFF_CHG( + this, + admins, + tariffs + )); +rpcRegistry.addMethod("stargazer.chg_tariff", methodChgTariffPtr); + +xmlrpc_c::methodPtr const methodGetTariffsPtr(new METHOD_TARIFFS_GET( + this, + tariffs + )); +rpcRegistry.addMethod("stargazer.get_tariffs", methodGetTariffsPtr); + +xmlrpc_c::methodPtr const methodAddTariffPtr(new METHOD_TARIFF_ADD( + this, + admins, + tariffs + )); +rpcRegistry.addMethod("stargazer.add_tariff", methodAddTariffPtr); + +xmlrpc_c::methodPtr const methodDelTariffPtr(new METHOD_TARIFF_DEL( + this, + admins, + tariffs, + users + )); +rpcRegistry.addMethod("stargazer.del_tariff", methodDelTariffPtr); + +xmlrpc_c::methodPtr const methodGetAdminPtr(new METHOD_ADMIN_GET( + this, + admins + )); +rpcRegistry.addMethod("stargazer.get_admin", methodGetAdminPtr); + +xmlrpc_c::methodPtr const methodAddAdminPtr(new METHOD_ADMIN_ADD( + this, + admins + )); +rpcRegistry.addMethod("stargazer.add_admin", methodAddAdminPtr); + +xmlrpc_c::methodPtr const methodDelAdminPtr(new METHOD_ADMIN_DEL( + this, + admins + )); +rpcRegistry.addMethod("stargazer.del_admin", methodDelAdminPtr); + +xmlrpc_c::methodPtr const methodChgAdminPtr(new METHOD_ADMIN_CHG( + this, + admins + )); +rpcRegistry.addMethod("stargazer.chg_admin", methodChgAdminPtr); + +xmlrpc_c::methodPtr const methodGetAdminsPtr(new METHOD_ADMINS_GET( + this, + admins + )); +rpcRegistry.addMethod("stargazer.get_admins", methodGetAdminsPtr); + +xmlrpc_c::methodPtr const methodSendMessagePtr(new METHOD_MESSAGE_SEND( + this, + users + )); +rpcRegistry.addMethod("stargazer.send_message", methodSendMessagePtr); + +xmlrpc_c::methodPtr const methodGetOnlinIPsPtr(new METHOD_GET_ONLINE_IPS( + this, + users + )); +rpcRegistry.addMethod("stargazer.get_online_ips", methodGetOnlinIPsPtr); +} + diff --git a/projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.h b/projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.h new file mode 100644 index 00000000..64f1182e --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/rpcconfig.h @@ -0,0 +1,107 @@ +#ifndef __RPC_CONFIG_H__ +#define __RPC_CONFIG_H__ + +#include + +#include +#include +#include + +#include + +#include "base_plugin.h" +#include "base_store.h" +#include "base_settings.h" +#include "admin_conf.h" +#include "../../../admin.h" +#include "../../../admins.h" +#include "../../../users.h" +#include "../../../tariffs.h" +#include "../../../traffcounter.h" +#include "../../../settings.h" + +#define RPC_CONFIG_VERSION "Stargazer RPC v. 0.2" + +extern "C" BASE_PLUGIN * GetPlugin(); + +class RPC_CONFIG_SETTINGS +{ +public: + RPC_CONFIG_SETTINGS(); + virtual ~RPC_CONFIG_SETTINGS() {}; + const std::string & GetStrError() const { return errorStr; }; + int ParseSettings(const MODULE_SETTINGS & s); + uint16_t GetPort() const { return port; }; + double GetCookieTimeout() const { return cookieTimeout; }; +private: + int ParseIntInRange(const std::string & str, + int min, + int max, + int * val); + std::string errorStr; + int port; + double cookieTimeout; +}; + +struct ADMIN_INFO +{ + std::string admin; + time_t accessTime; + PRIV priviledges; +}; + +class RPC_CONFIG :public BASE_PLUGIN +{ +public: + RPC_CONFIG(); + virtual ~RPC_CONFIG(); + + void SetUsers(USERS * u) { users = u; }; + void SetTariffs(TARIFFS * t) { tariffs = t; }; + void SetAdmins(ADMINS * a) { admins = a; }; + void SetStore(BASE_STORE * s) { store = s; }; + void SetTraffcounter(TRAFFCOUNTER *) {}; + void SetStgSettings(const SETTINGS * s) { stgSettings = s; }; + void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning() { return running && !stopped; }; + + const string & GetStrError() const { return errorStr; }; + const string GetVersion() const { return RPC_CONFIG_VERSION; }; + uint16_t GetStartPosition() const { return 220; }; + uint16_t GetStopPosition() const { return 220; }; + + bool GetAdminInfo(const std::string & cookie, + ADMIN_INFO * info); + bool CheckAdmin(const std::string & login, + const std::string & password, + std::string * cookie); + bool LogoutAdmin(const std::string & cookie); + +private: + mutable string errorStr; + RPC_CONFIG_SETTINGS rpcConfigSettings; + USERS * users; + ADMINS * admins; + TARIFFS * tariffs; + BASE_STORE * store; + MODULE_SETTINGS settings; + const SETTINGS * stgSettings; + xmlrpc_c::registry rpcRegistry; + xmlrpc_c::serverAbyss * rpcServer; + bool running; + bool stopped; + pthread_t tid; + std::map cookies; + + static void * Run(void *); + std::string GetCookie() const; + void InitiateRegistry(); +}; + +#endif diff --git a/projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.cpp b/projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.cpp new file mode 100644 index 00000000..91f60062 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.cpp @@ -0,0 +1,92 @@ +#include "tariff_helper.h" + +void TARIFF_HELPER::GetTariffInfo(xmlrpc_c::value * info) const +{ +std::map structVal; + +structVal["result"] = xmlrpc_c::value_boolean(true); +structVal["name"] = xmlrpc_c::value_string(data.tariffConf.name); +structVal["fee"] = xmlrpc_c::value_double(data.tariffConf.fee); +structVal["freemb"] = xmlrpc_c::value_double(data.tariffConf.free); +structVal["passivecost"] = xmlrpc_c::value_double(data.tariffConf.passiveCost); +structVal["traffType"] = xmlrpc_c::value_int(data.tariffConf.traffType); + +std::vector prices(DIR_NUM); + +for (unsigned i = 0; i < DIR_NUM; ++i) + { + std::map dirPrice; + dirPrice["hday"] = xmlrpc_c::value_int(data.dirPrice[i].hDay); + dirPrice["mday"] = xmlrpc_c::value_int(data.dirPrice[i].mDay); + dirPrice["hnight"] = xmlrpc_c::value_int(data.dirPrice[i].hNight); + dirPrice["mnight"] = xmlrpc_c::value_int(data.dirPrice[i].mNight); + dirPrice["pricedaya"] = xmlrpc_c::value_double(data.dirPrice[i].priceDayA * 1024 * 1024); + dirPrice["pricedayb"] = xmlrpc_c::value_double(data.dirPrice[i].priceDayB * 1024 * 1024); + dirPrice["pricenighta"] = xmlrpc_c::value_double(data.dirPrice[i].priceNightA * 1024 * 1024); + dirPrice["pricenightb"] = xmlrpc_c::value_double(data.dirPrice[i].priceNightB * 1024 * 1024); + dirPrice["threshold"] = xmlrpc_c::value_int(data.dirPrice[i].threshold); + dirPrice["singleprice"] = xmlrpc_c::value_boolean(data.dirPrice[i].singlePrice); + dirPrice["nodiscount"] = xmlrpc_c::value_boolean(data.dirPrice[i].noDiscount); + prices[i] = xmlrpc_c::value_struct(dirPrice); + } + +structVal["dirprices"] = xmlrpc_c::value_array(prices); + +*info = xmlrpc_c::value_struct(structVal); +} + +bool TARIFF_HELPER::SetTariffInfo(const xmlrpc_c::value & info) +{ +std::map structVal( + static_cast >(xmlrpc_c::value_struct(info)) + ); + +std::map::iterator it; + +if ((it = structVal.find("fee")) != structVal.end()) + { + data.tariffConf.fee = xmlrpc_c::value_double(it->second); + } + +if ((it = structVal.find("freemb")) != structVal.end()) + { + data.tariffConf.free = xmlrpc_c::value_double(it->second); + } + +if ((it = structVal.find("passivecost")) != structVal.end()) + { + data.tariffConf.passiveCost = xmlrpc_c::value_double(it->second); + } + +if ((it = structVal.find("traffType")) != structVal.end()) + { + data.tariffConf.traffType = xmlrpc_c::value_int(it->second); + } + +if ((it = structVal.find("dirprices")) != structVal.end()) + { + std::vector prices( + xmlrpc_c::value_array(it->second).vectorValueValue() + ); + + for (unsigned i = 0; i < DIR_NUM; ++i) + { + std::map dirPrice( + static_cast >(xmlrpc_c::value_struct(prices[i])) + ); + data.dirPrice[i].mDay = xmlrpc_c::value_int(dirPrice["mday"]); + data.dirPrice[i].hDay = xmlrpc_c::value_int(dirPrice["hday"]); + data.dirPrice[i].mNight = xmlrpc_c::value_int(dirPrice["mnight"]); + data.dirPrice[i].hNight = xmlrpc_c::value_int(dirPrice["hnight"]); + data.dirPrice[i].priceDayA = xmlrpc_c::value_double(dirPrice["pricedaya"]) / 1024 / 1024; + data.dirPrice[i].priceDayB = xmlrpc_c::value_double(dirPrice["pricedayb"]) / 1024 / 1024; + data.dirPrice[i].priceNightA = xmlrpc_c::value_double(dirPrice["pricenighta"]) / 1024 / 1024; + data.dirPrice[i].priceNightB = xmlrpc_c::value_double(dirPrice["pricenightb"]) / 1024 / 1024; + data.dirPrice[i].threshold = xmlrpc_c::value_int(dirPrice["threshold"]); + data.dirPrice[i].singlePrice = xmlrpc_c::value_boolean(dirPrice["singleprice"]); + data.dirPrice[i].noDiscount = xmlrpc_c::value_boolean(dirPrice["nodiscount"]); + } + } + +return false; +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.h b/projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.h new file mode 100644 index 00000000..e7b1c6df --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/tariff_helper.h @@ -0,0 +1,23 @@ +#ifndef __TARIFF_HELPER_H__ +#define __TARIFF_HELPER_H__ + +#include +#include "../../../tariff.h" +#include "tariff_conf.h" + +class TARIFF_HELPER +{ +public: + TARIFF_HELPER(TARIFF_DATA & td) + : data(td) + {} + + void GetTariffInfo(xmlrpc_c::value * info) const; + bool SetTariffInfo(const xmlrpc_c::value & info); +private: + TARIFF_DATA & data; +}; + +#endif + + diff --git a/projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.cpp b/projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.cpp new file mode 100644 index 00000000..36194e36 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.cpp @@ -0,0 +1,192 @@ +#include "rpcconfig.h" +#include "tariffs_methods.h" +#include "tariff_helper.h" + +void METHOD_TARIFF_GET::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string name = paramList.getString(1); +paramList.verifyEnd(2); + +std::map structVal; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +const TARIFF * tariff = tariffs->FindByName(name); + +if (!tariff) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +TARIFF_DATA td; + +tariff->GetTariffData(&td); + +TARIFF_HELPER helper(td); + +helper.GetTariffInfo(retvalPtr); +} + +void METHOD_TARIFF_CHG::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string name = paramList.getString(1); +xmlrpc_c::value_struct info(paramList.getStruct(2)); +paramList.verifyEnd(3); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +const TARIFF * tariff = tariffs->FindByName(name); + +if (!tariff) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +TARIFF_DATA td; + +tariff->GetTariffData(&td); + +TARIFF_HELPER helper(td); + +helper.SetTariffInfo(info); + +if (tariffs->Chg(td, admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +void METHOD_TARIFFS_GET::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +paramList.verifyEnd(1); + +std::map structVal; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +std::vector tariffsInfo; + + +std::list dataList; +tariffs->GetTariffsData(&dataList); +std::list::const_iterator it = dataList.begin(); +for (; it != dataList.end(); ++it) + { + xmlrpc_c::value info; + TARIFF_DATA td(*it); // 'cause TARIFF_HELPER work in both ways and take not const referense + TARIFF_HELPER helper(td); + helper.GetTariffInfo(&info); + tariffsInfo.push_back(info); + } + +*retvalPtr = xmlrpc_c::value_array(tariffsInfo); +} + +void METHOD_TARIFF_ADD::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string tariff = paramList.getString(1); +std::string enc; +paramList.verifyEnd(2); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +if (tariffs->Add(tariff, admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +void METHOD_TARIFF_DEL::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string tariff = paramList.getString(1); +paramList.verifyEnd(2); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +if (users->TariffInUse(tariff)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +if (tariffs->Del(tariff, admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +*retvalPtr = xmlrpc_c::value_boolean(true); +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.h b/projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.h new file mode 100644 index 00000000..fdd788d4 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/tariffs_methods.h @@ -0,0 +1,103 @@ +#ifndef __TARIFFS_METHODS_H__ +#define __TARIFFS_METHODS_H__ + +#include +#include + +#include "../../../tariffs.h" + +class RPC_CONFIG; + +class METHOD_TARIFF_GET : public xmlrpc_c::method { +public: + METHOD_TARIFF_GET(RPC_CONFIG * c, + TARIFFS * t) + : config(c), + tariffs(t) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + TARIFFS * tariffs; +}; + +class METHOD_TARIFF_CHG : public xmlrpc_c::method { +public: + METHOD_TARIFF_CHG(RPC_CONFIG * c, + ADMINS * a, + TARIFFS * t) + : config(c), + admins(a), + tariffs(t) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + ADMINS * admins; + TARIFFS * tariffs; +}; + +class METHOD_TARIFFS_GET : public xmlrpc_c::method { +public: + METHOD_TARIFFS_GET(RPC_CONFIG * c, + TARIFFS * t) + : config(c), + tariffs(t) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr); +private: + RPC_CONFIG * config; + TARIFFS * tariffs; +}; + +class METHOD_TARIFF_ADD : public xmlrpc_c::method { +public: + METHOD_TARIFF_ADD(RPC_CONFIG * c, + ADMINS * a, + TARIFFS * t) + : config(c), + admins(a), + tariffs(t) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + TARIFFS * tariffs; +}; + +class METHOD_TARIFF_DEL : public xmlrpc_c::method { +public: + METHOD_TARIFF_DEL(RPC_CONFIG * c, + ADMINS * a, + TARIFFS * t, + USERS * u) + : config(c), + admins(a), + tariffs(t), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + TARIFFS * tariffs; + USERS * users; +}; + +#endif diff --git a/projects/stargazer/plugins/configuration/rpcconfig/user_helper.cpp b/projects/stargazer/plugins/configuration/rpcconfig/user_helper.cpp new file mode 100644 index 00000000..62dcd1af --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/user_helper.cpp @@ -0,0 +1,412 @@ +#include "user_helper.h" + +#include "user_ips.h" +#include "utils.h" + +//------------------------------------------------------------------------------ + +void USER_HELPER::GetUserInfo(xmlrpc_c::value * info, + bool hidePassword) +{ +std::string enc; + +std::map structVal; + +structVal["result"] = xmlrpc_c::value_boolean(true); +structVal["login"] = xmlrpc_c::value_string(iter->GetLogin()); + +if (!hidePassword) + { + structVal["password"] = xmlrpc_c::value_string(iter->property.password.Get()); + } +else + { + structVal["password"] = xmlrpc_c::value_string("++++++++"); + } + +structVal["cash"] = xmlrpc_c::value_double(iter->property.cash.Get()); +structVal["freemb"] = xmlrpc_c::value_double(iter->property.freeMb.Get()); +structVal["credit"] = xmlrpc_c::value_double(iter->property.credit.Get()); + +if (iter->property.nextTariff.Get() != "") + { + structVal["tariff"] = xmlrpc_c::value_string( + iter->property.tariffName.Get() + + "/" + + iter->property.nextTariff.Get() + ); + } +else + { + structVal["tariff"] = xmlrpc_c::value_string(iter->property.tariffName.Get()); + } + +structVal["note"] = xmlrpc_c::value_string(IconvString(iter->property.note, "KOI8-R", "UTF-8")); + +structVal["phone"] = xmlrpc_c::value_string(IconvString(iter->property.phone, "KOI8-R", "UTF-8")); + +structVal["address"] = xmlrpc_c::value_string(IconvString(iter->property.address, "KOI8-R", "UTF-8")); + +structVal["email"] = xmlrpc_c::value_string(IconvString(iter->property.email, "KOI8-R", "UTF-8")); + +std::vector userdata; + +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata0.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata1.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata2.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata3.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata4.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata5.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata6.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata7.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata8.Get(), "KOI8-R", "UTF-8"))); +userdata.push_back(xmlrpc_c::value_string(IconvString(iter->property.userdata9.Get(), "KOI8-R", "UTF-8"))); + +structVal["userdata"] = xmlrpc_c::value_array(userdata); + +structVal["name"] = xmlrpc_c::value_string(IconvString(iter->property.realName, "KOI8-R", "UTF-8")); + +structVal["group"] = xmlrpc_c::value_string(IconvString(iter->property.group, "KOI8-R", "UTF-8")); + +structVal["status"] = xmlrpc_c::value_boolean(iter->GetConnected()); +structVal["aonline"] = xmlrpc_c::value_boolean(iter->property.alwaysOnline.Get()); +structVal["currip"] = xmlrpc_c::value_string(inet_ntostring(iter->GetCurrIP())); +structVal["pingtime"] = xmlrpc_c::value_int(iter->GetPingTime()); +structVal["ips"] = xmlrpc_c::value_string(iter->property.ips.Get().GetIpStr()); + +std::map traffInfo; +std::vector mu(DIR_NUM); +std::vector md(DIR_NUM); +std::vector su(DIR_NUM); +std::vector sd(DIR_NUM); + +DIR_TRAFF upload; +DIR_TRAFF download; +DIR_TRAFF supload; +DIR_TRAFF sdownload; +download = iter->property.down.Get(); +upload = iter->property.up.Get(); +sdownload = iter->GetSessionUpload(); +supload = iter->GetSessionDownload(); + +for (int j = 0; j < DIR_NUM; j++) + { + std::string value; + x2str(upload[j], value); + mu[j] = xmlrpc_c::value_string(value); + x2str(download[j], value); + md[j] = xmlrpc_c::value_string(value); + x2str(supload[j], value); + su[j] = xmlrpc_c::value_string(value); + x2str(sdownload[j], value); + sd[j] = xmlrpc_c::value_string(value); + } + +traffInfo["mu"] = xmlrpc_c::value_array(mu); +traffInfo["md"] = xmlrpc_c::value_array(md); +traffInfo["su"] = xmlrpc_c::value_array(su); +traffInfo["sd"] = xmlrpc_c::value_array(sd); + +structVal["traff"] = xmlrpc_c::value_struct(traffInfo); + +structVal["down"] = xmlrpc_c::value_boolean(iter->property.disabled.Get()); +structVal["disableddetailstat"] = xmlrpc_c::value_boolean(iter->property.disabledDetailStat.Get()); +structVal["passive"] = xmlrpc_c::value_boolean(iter->property.passive.Get()); +structVal["lastcash"] = xmlrpc_c::value_double(iter->property.lastCashAdd.Get()); +structVal["lasttimecash"] = xmlrpc_c::value_int(iter->property.lastCashAddTime.Get()); +structVal["lastactivitytime"] = xmlrpc_c::value_int(iter->property.lastActivityTime.Get()); +structVal["creditexpire"] = xmlrpc_c::value_int(iter->property.creditExpire.Get()); + +*info = xmlrpc_c::value_struct(structVal); +} + +//------------------------------------------------------------------------------ + +bool USER_HELPER::SetUserInfo(const xmlrpc_c::value & info, + const ADMIN & admin, + const std::string & login, + const BASE_STORE & store) +{ +std::map structVal( + static_cast >(xmlrpc_c::value_struct(info)) + ); + +std::map::iterator it; + +if ((it = structVal.find("password")) != structVal.end()) + { + bool res = iter->property.password.Set(xmlrpc_c::value_string(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("ips")) != structVal.end()) + { + USER_IPS ips; + ips = StrToIPS(xmlrpc_c::value_string(it->second)); + bool res = iter->property.ips.Set(ips, + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("address")) != structVal.end()) + { + bool res = iter->property.address.Set(IconvString(xmlrpc_c::value_string(it->second), "UTF-8", "KOI8-R"), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("phone")) != structVal.end()) + { + bool res = iter->property.phone.Set(IconvString(xmlrpc_c::value_string(it->second), "UTF-8", "KOI8-R"), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("email")) != structVal.end()) + { + bool res = iter->property.email.Set(IconvString(xmlrpc_c::value_string(it->second), "UTF-8", "KOI8-R"), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("creditexpire")) != structVal.end()) + { + bool res = iter->property.creditExpire.Set(xmlrpc_c::value_int(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("credit")) != structVal.end()) + { + bool res = iter->property.credit.Set(xmlrpc_c::value_double(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("freemb")) != structVal.end()) + { + bool res = iter->property.freeMb.Set(xmlrpc_c::value_double(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("disabled")) != structVal.end()) + { + bool res = iter->property.disabled.Set(xmlrpc_c::value_boolean(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("passive")) != structVal.end()) + { + bool res = iter->property.passive.Set(xmlrpc_c::value_boolean(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("aonline")) != structVal.end()) + { + bool res = iter->property.alwaysOnline.Set(xmlrpc_c::value_boolean(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("disableddetailstat")) != structVal.end()) + { + bool res = iter->property.disabledDetailStat.Set(xmlrpc_c::value_boolean(it->second), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("name")) != structVal.end()) + { + bool res = iter->property.realName.Set(IconvString(xmlrpc_c::value_string(it->second), "UTF-8", "KOI8-R"), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("group")) != structVal.end()) + { + bool res = iter->property.group.Set(IconvString(xmlrpc_c::value_string(it->second), "UTF-8", "KOI8-R"), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("note")) != structVal.end()) + { + bool res = iter->property.note.Set(IconvString(xmlrpc_c::value_string(it->second), "UTF-8", "KOI8-R"), + admin, + login, + &store); + if (!res) + { + return true; + } + } + +if ((it = structVal.find("userdata")) != structVal.end()) + { + std::vector *> userdata; + userdata.push_back(iter->property.userdata0.GetPointer()); + userdata.push_back(iter->property.userdata1.GetPointer()); + userdata.push_back(iter->property.userdata2.GetPointer()); + userdata.push_back(iter->property.userdata3.GetPointer()); + userdata.push_back(iter->property.userdata4.GetPointer()); + userdata.push_back(iter->property.userdata5.GetPointer()); + userdata.push_back(iter->property.userdata6.GetPointer()); + userdata.push_back(iter->property.userdata7.GetPointer()); + userdata.push_back(iter->property.userdata8.GetPointer()); + userdata.push_back(iter->property.userdata9.GetPointer()); + + std::vector udata( + xmlrpc_c::value_array(it->second).vectorValueValue() + ); + + for (unsigned i = 0; i < userdata.size(); ++i) + { + bool res = userdata[i]->Set(IconvString(xmlrpc_c::value_string(udata[i]), "UTF-8", "KOI8-R"), + admin, + login, + &store); + if (!res) + { + return true; + } + } + } + +if ((it = structVal.find("traff")) != structVal.end()) + { + std::map traff( + static_cast >(xmlrpc_c::value_struct(it->second)) + ); + + std::vector data; + DIR_TRAFF dtData; + dtData = iter->property.up.Get(); + if ((it = traff.find("mu")) != traff.end()) + { + data = xmlrpc_c::value_array(it->second).vectorValueValue(); + + for (int i = 0; i < std::min(DIR_NUM, static_cast(data.size())); ++i) + { + int64_t value; + if (str2x(xmlrpc_c::value_string(data[i]), value)) + { + printfd(__FILE__, "USER_HELPER::SetUserInfo(): 'Invalid month upload value'\n"); + } + else + { + dtData[i] = value; + } + } + bool res = iter->property.up.Set(dtData, + admin, + login, + &store); + if (!res) + { + return true; + } + } + dtData = iter->property.down.Get(); + if ((it = traff.find("md")) != traff.end()) + { + data = xmlrpc_c::value_array(it->second).vectorValueValue(); + + for (int i = 0; i < std::min(DIR_NUM, static_cast(data.size())); ++i) + { + int64_t value; + if (str2x(xmlrpc_c::value_string(data[i]), value)) + { + printfd(__FILE__, "USER_HELPER::SetUserInfo(): 'Invalid month download value'\n"); + } + else + { + dtData[i] = value; + } + } + bool res = iter->property.down.Set(dtData, + admin, + login, + &store); + if (!res) + { + return true; + } + } + } + +return false; +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/user_helper.h b/projects/stargazer/plugins/configuration/rpcconfig/user_helper.h new file mode 100644 index 00000000..6421a467 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/user_helper.h @@ -0,0 +1,29 @@ +#ifndef __USER_HELPER_H__ +#define __USER_HELPER_H__ + +#include + +#include +#include "../../../users.h" +#include "../../../admin.h" +#include "base_store.h" + +class USER_HELPER +{ +public: + USER_HELPER(user_iter & it) + : iter(it) + { + } + + void GetUserInfo(xmlrpc_c::value * info, + bool hidePassword = false); + bool SetUserInfo(const xmlrpc_c::value & info, + const ADMIN & admin, + const std::string & login, + const BASE_STORE & store); +private: + user_iter & iter; +}; + +#endif diff --git a/projects/stargazer/plugins/configuration/rpcconfig/users_methods.cpp b/projects/stargazer/plugins/configuration/rpcconfig/users_methods.cpp new file mode 100644 index 00000000..d6812f55 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/users_methods.cpp @@ -0,0 +1,511 @@ +#include "users_methods.h" + +#include "rpcconfig.h" +#include "user_helper.h" +#include "user_ips.h" +#include "utils.h" + +#include "common.h" + +//------------------------------------------------------------------------------ + +void METHOD_USER_GET::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +std::string enc; +paramList.verifyEnd(2); + +std::map structVal; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +user_iter u; + +if (users->FindByName(login, &u)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +USER_HELPER uhelper(u); + +if (!adminInfo.priviledges.userConf || !adminInfo.priviledges.userPasswd) + { + uhelper.GetUserInfo(retvalPtr, true); + return; + } + +uhelper.GetUserInfo(retvalPtr); +} + +//------------------------------------------------------------------------------ + +void METHOD_USER_ADD::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +std::string enc; +paramList.verifyEnd(2); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +user_iter u; + +if (users->FindByName(login, &u)) + { + if (users->Add(login, admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + + *retvalPtr = xmlrpc_c::value_boolean(true); + return; + } + +*retvalPtr = xmlrpc_c::value_boolean(false); +return; +} + +//------------------------------------------------------------------------------ + +void METHOD_USER_DEL::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +std::string enc; +paramList.verifyEnd(2); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +user_iter u; + +if (users->FindByName(login, &u)) + { + users->Del(login, admin); + *retvalPtr = xmlrpc_c::value_boolean(true); + return; + } + +*retvalPtr = xmlrpc_c::value_boolean(false); +return; +} + +//------------------------------------------------------------------------------ + +void METHOD_USERS_GET::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string enc; +paramList.verifyEnd(1); + +std::map structVal; +std::vector retval; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +bool hidePassword = !adminInfo.priviledges.userConf || + !adminInfo.priviledges.userPasswd; + +user_iter u; + +int h = users->OpenSearch(); +if (!h) + { + printfd(__FILE__, "users->OpenSearch() error\n"); + users->CloseSearch(h); + return; + } + +while (1) + { + if (users->SearchNext(h, &u)) + { + break; + } + + xmlrpc_c::value info; + + USER_HELPER uhelper(u); + + uhelper.GetUserInfo(&info, hidePassword); + + retval.push_back(info); + } + +*retvalPtr = xmlrpc_c::value_array(retval); +} + +//------------------------------------------------------------------------------ + +void METHOD_USER_CHG::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +xmlrpc_c::value_struct info(paramList.getStruct(2)); +std::string enc; +paramList.verifyEnd(3); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +user_iter u; + +if (users->FindByName(login, &u)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +USER_HELPER uhelper(u); + +if (!adminInfo.priviledges.userConf || !adminInfo.priviledges.userPasswd) + { + uhelper.SetUserInfo(info, admin, login, *store); + } +else + { + uhelper.SetUserInfo(info, admin, login, *store); + } + +u->WriteConf(); +u->WriteStat(); + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +//------------------------------------------------------------------------------ + +void METHOD_USER_CASH_ADD::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +double amount = paramList.getDouble(2); +std::string comment = IconvString(paramList.getString(3), "UTF-8", "KOI8-R"); +std::string enc; +paramList.verifyEnd(4); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +user_iter u; + +if (users->FindByName(login, &u)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +double cash = u->property.cash.Get(); +cash += amount; + +if (!u->property.cash.Set(cash, admin, login, store, comment)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +u->WriteStat(); + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +//------------------------------------------------------------------------------ + +void METHOD_USER_CASH_SET::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +double cash = paramList.getDouble(2); +std::string comment = IconvString(paramList.getString(3), "UTF-8", "KOI8-R"); +std::string enc; +paramList.verifyEnd(4); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +user_iter u; + +if (users->FindByName(login, &u)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +if (!u->property.cash.Set(cash, admin, login, store, comment)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +u->WriteStat(); + +*retvalPtr = xmlrpc_c::value_boolean(true); +} + +//------------------------------------------------------------------------------ + +void METHOD_USER_TARIFF_CHANGE::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::string login = paramList.getString(1); +std::string tariff = paramList.getString(2); +bool delayed = paramList.getBoolean(3); +std::string comment = IconvString(paramList.getString(4), "UTF-8", "KOI8-R"); +std::string enc; +paramList.verifyEnd(5); + +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +ADMIN admin; + +if (admins->FindAdmin(adminInfo.admin, &admin)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +user_iter u; + +if (users->FindByName(login, &u)) + { + *retvalPtr = xmlrpc_c::value_boolean(false); + return; + } + +if (tariffs->FindByName(tariff)) + { + if (delayed) + { + if (u->property.nextTariff.Set(tariff, + admin, + login, + store)) + { + u->WriteConf(); + *retvalPtr = xmlrpc_c::value_boolean(true); + return; + } + } + if (u->property.tariffName.Set(tariff, + admin, + login, + store)) + { + u->WriteConf(); + *retvalPtr = xmlrpc_c::value_boolean(true); + return; + } + } + +*retvalPtr = xmlrpc_c::value_boolean(false); +} + +//------------------------------------------------------------------------------ + +void METHOD_GET_ONLINE_IPS::execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalPtr) +{ +std::string cookie = paramList.getString(0); +std::vector subnetsStr = paramList.getArray(1); +paramList.verifyEnd(2); + +std::vector subnets; + +std::vector::iterator it; + +for (it = subnetsStr.begin(); it != subnetsStr.end(); ++it) + { + IP_MASK ipm; + if (ParseNet(xmlrpc_c::value_string(*it), ipm)) + { + printfd(__FILE__, "METHOD_GET_ONLINE_IPS::execute(): Failed to parse subnet ('%s')\n", std::string(xmlrpc_c::value_string(*it)).c_str()); + } + else + { + subnets.push_back(ipm); + } + } + +std::map structVal; +ADMIN_INFO adminInfo; + +if (config->GetAdminInfo(cookie, &adminInfo)) + { + structVal["result"] = xmlrpc_c::value_boolean(false); + *retvalPtr = xmlrpc_c::value_struct(structVal); + return; + } + +std::vector ips; + +user_iter u; + +int handle = users->OpenSearch(); +if (!handle) + { + printfd(__FILE__, "users->OpenSearch() error\n"); + users->CloseSearch(handle); + return; + } + +while (1) + { + if (users->SearchNext(handle, &u)) + { + break; + } + + if (u->GetAuthorized()) + { + uint32_t ip = u->GetCurrIP(); + + std::vector::iterator it; + for (it = subnets.begin(); it != subnets.end(); ++it) + { + if ((it->ip & it->mask) == (ip & it->mask)) + { + ips.push_back(xmlrpc_c::value_string(inet_ntostring(u->GetCurrIP()))); + break; + } + } + } + } + +structVal["ips"] = xmlrpc_c::value_array(ips); + +*retvalPtr = xmlrpc_c::value_struct(structVal); +} + +bool METHOD_GET_ONLINE_IPS::ParseNet(const std::string & net, IP_MASK & ipm) const +{ +size_t pos = net.find_first_of('/'); + +if (pos == std::string::npos) + { + printfd(__FILE__, "METHOD_GET_ONLINE_IPS::ParseNet(): Network address is not in CIDR-notation\n"); + return true; + } + +int res = inet_pton(AF_INET, net.substr(0, pos).c_str(), &ipm.ip); + +if (res < 0) + { + printfd(__FILE__, "METHOD_GET_ONLINE_IPS::ParseNet(): '%s'\n", strerror(errno)); + return true; + } +else if (res == 0) + { + printfd(__FILE__, "METHOD_GET_ONLINE_IPS::ParseNet(): Invalid network address\n", strerror(errno)); + return true; + } + +if (str2x(net.substr(pos + 1, net.length() - pos - 1), ipm.mask)) + { + printfd(__FILE__, "METHOD_GET_ONLINE_IPS::ParseNet(): Invalid network mask\n"); + return true; + } +if (ipm.mask > 32) + { + printfd(__FILE__, "METHOD_GET_ONLINE_IPS::ParseNet(): Network mask is out of range\n"); + return true; + } +ipm.mask = htonl(0xffFFffFF << (32 - ipm.mask)); + +return false; +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/users_methods.h b/projects/stargazer/plugins/configuration/rpcconfig/users_methods.h new file mode 100644 index 00000000..ee8e656e --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/users_methods.h @@ -0,0 +1,191 @@ +#ifndef __USERS_METHODS_H__ +#define __USERS_METHODS_H__ + +#include +#include + +#include "../../../users.h" +#include "../../../user.h" + +class RPC_CONFIG; + +class METHOD_USER_GET : public xmlrpc_c::method { +public: + METHOD_USER_GET(RPC_CONFIG * c, + USERS * u) + : config(c), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + USERS * users; +}; + +class METHOD_USER_ADD : public xmlrpc_c::method { +public: + METHOD_USER_ADD(RPC_CONFIG * c, + ADMINS * a, + USERS * u) + : config(c), + admins(a), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + USERS * users; +}; + +class METHOD_USER_DEL : public xmlrpc_c::method { +public: + METHOD_USER_DEL(RPC_CONFIG * c, + ADMINS * a, + USERS * u) + : config(c), + admins(a), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + USERS * users; +}; + +class METHOD_USERS_GET : public xmlrpc_c::method { +public: + METHOD_USERS_GET(RPC_CONFIG * c, + USERS * u) + : config(c), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + USERS * users; +}; + +class METHOD_USER_CHG : public xmlrpc_c::method { +public: + METHOD_USER_CHG(RPC_CONFIG * c, + ADMINS * a, + BASE_STORE * s, + USERS * u) + : config(c), + admins(a), + store(s), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + BASE_STORE * store; + USERS * users; +}; + +class METHOD_USER_CASH_ADD : public xmlrpc_c::method { +public: + METHOD_USER_CASH_ADD(RPC_CONFIG * c, + ADMINS * a, + BASE_STORE * s, + USERS * u) + : config(c), + admins(a), + store(s), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + BASE_STORE * store; + USERS * users; +}; + +class METHOD_USER_CASH_SET : public xmlrpc_c::method { +public: + METHOD_USER_CASH_SET(RPC_CONFIG * c, + ADMINS * a, + BASE_STORE * s, + USERS * u) + : config(c), + admins(a), + store(s), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + BASE_STORE * store; + USERS * users; +}; + +class METHOD_USER_TARIFF_CHANGE : public xmlrpc_c::method { +public: + METHOD_USER_TARIFF_CHANGE(RPC_CONFIG * c, + ADMINS * a, + TARIFFS * t, + BASE_STORE * s, + USERS * u) + : config(c), + admins(a), + tariffs(t), + store(s), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + ADMINS * admins; + TARIFFS * tariffs; + BASE_STORE * store; + USERS * users; +}; + +class METHOD_GET_ONLINE_IPS : public xmlrpc_c::method { +public: + METHOD_GET_ONLINE_IPS(RPC_CONFIG * c, + USERS * u) + : config(c), + users(u) + { + } + + void execute(xmlrpc_c::paramList const & paramList, + xmlrpc_c::value * const retvalP); +private: + RPC_CONFIG * config; + USERS * users; + + bool ParseNet(const std::string & net, IP_MASK & ipm) const; +}; + +#endif diff --git a/projects/stargazer/plugins/configuration/rpcconfig/utils.cpp b/projects/stargazer/plugins/configuration/rpcconfig/utils.cpp new file mode 100644 index 00000000..18999aa9 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/utils.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#include "utils.h" +#include "common.h" + +//----------------------------------------------------------------------------- +std::string IconvString(const std::string & src, + const std::string & from, + const std::string & to) +{ +if (src.empty()) + return std::string(); + +size_t inBytesLeft = src.length() + 1; +size_t outBytesLeft = src.length() * 2 + 1; + +char * inBuf = new char[inBytesLeft]; +char * outBuf = new char[outBytesLeft]; + +strncpy(inBuf, src.c_str(), src.length()); + +inBuf[src.length()] = 0; + +#if defined(FREE_BSD) || defined(FREE_BSD5) +const char * srcPos = inBuf; +#else +char * srcPos = inBuf; +#endif +char * dstPos = outBuf; + +iconv_t handle = iconv_open(to.c_str(), + from.c_str()); + +if (handle == iconv_t(-1)) + { + if (errno == EINVAL) + { + printfd(__FILE__, "IconvString(): iconv from %s to %s failed\n", from.c_str(), to.c_str()); + delete[] outBuf; + delete[] inBuf; + return src; + } + else + printfd(__FILE__, "IconvString(): iconv_open error\n"); + + delete[] outBuf; + delete[] inBuf; + return src; + } + +size_t res = iconv(handle, + &srcPos, &inBytesLeft, + &dstPos, &outBytesLeft); + +if (res == size_t(-1)) + { + printfd(__FILE__, "IconvString(): '%s'\n", strerror(errno)); + + iconv_close(handle); + delete[] outBuf; + delete[] inBuf; + return src; + } + +dstPos = 0; + +std::string dst(outBuf); + +iconv_close(handle); + +delete[] outBuf; +delete[] inBuf; + +return dst; +} diff --git a/projects/stargazer/plugins/configuration/rpcconfig/utils.h b/projects/stargazer/plugins/configuration/rpcconfig/utils.h new file mode 100644 index 00000000..d273e3d2 --- /dev/null +++ b/projects/stargazer/plugins/configuration/rpcconfig/utils.h @@ -0,0 +1,10 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include + +std::string IconvString(const std::string & src, + const std::string & from = "UTF-8", + const std::string & to = "KOI8-R"); + +#endif diff --git a/projects/stargazer/plugins/configuration/sgconfig/Makefile b/projects/stargazer/plugins/configuration/sgconfig/Makefile new file mode 100644 index 00000000..8f065d16 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/Makefile @@ -0,0 +1,22 @@ +############################################################################### +# $Id: Makefile,v 1.11 2010/03/04 10:47:45 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_conf_sg.so + +SRCS = ./stgconfig.cpp \ + ./rsconf.cpp \ + ./configproto.cpp \ + ./parser.cpp \ + ./parser_tariff.cpp \ + ./parser_admin.cpp + +LIBS += -lexpat \ + $(LIB_THREAD) + +STGLIBS = -lstg_common -lstg_logger -lstg_crypto + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/configuration/sgconfig/configproto.cpp b/projects/stargazer/plugins/configuration/sgconfig/configproto.cpp new file mode 100644 index 00000000..38b423fb --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/configproto.cpp @@ -0,0 +1,299 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.22 $ + $Date: 2010/10/04 20:24:14 $ + $Author: faust $ + */ + + +#include + +#include "configproto.h" + +//----------------------------------------------------------------------------- +void ParseXMLStart(void *data, const char *el, const char **attr) +{ +CONFIGPROTO * cp = static_cast(data); + +if (cp->currParser) + { + cp->currParser->SetAnswerList(&cp->answerList); + cp->currParser->SetCurrAdmin(cp->currAdmin); + cp->currParser->ParseStart(data, el, attr); + } +else + { + for (unsigned int i = 0; i < cp->dataParser.size(); i++) + { + cp->dataParser[i]->SetAnswerList(&cp->answerList); + //cp->currAdmin->SetAdminIP(cp->GetAdminIP()); + cp->dataParser[i]->SetCurrAdmin(cp->currAdmin); + cp->dataParser[i]->Reset(); + if (cp->dataParser[i]->ParseStart(data, el, attr) == 0) + { + cp->currParser = cp->dataParser[i]; + break; + } + else + { + cp->dataParser[i]->Reset(); + } + } + } +} +//----------------------------------------------------------------------------- +void ParseXMLEnd(void *data, const char *el) +{ +CONFIGPROTO * cp = static_cast(data); +if (cp->currParser) + { + if (cp->currParser->ParseEnd(data, el) == 0) + { + cp->currParser = NULL; + } + } +else + { + for (unsigned int i = 0; i < cp->dataParser.size(); i++) + { + if (cp->dataParser[i]->ParseEnd(data, el) == 0) + { + break; + } + } + } +} +//----------------------------------------------------------------------------- +CONFIGPROTO::CONFIGPROTO() + : adminIP(0), + port(0), + nonstop(1), + state(0), + currAdmin(), + WriteServLog(GetStgLogger()), + outerSocket(0), + listenSocket(0), + admins(NULL), + //users(NULL), + //tariffs(NULL), + //store(NULL), + //settings(NULL), + currParser(NULL) +{ +dataParser.push_back(&parserGetServInfo); + +dataParser.push_back(&parserGetUsers); +dataParser.push_back(&parserGetUser); +dataParser.push_back(&parserChgUser); +dataParser.push_back(&parserAddUser); +dataParser.push_back(&parserDelUser); +dataParser.push_back(&parserCheckUser); +dataParser.push_back(&parserSendMessage); + +dataParser.push_back(&parserGetTariffs); +dataParser.push_back(&parserAddTariff); +dataParser.push_back(&parserDelTariff); +dataParser.push_back(&parserChgTariff); + +dataParser.push_back(&parserGetAdmins); +dataParser.push_back(&parserChgAdmin); +dataParser.push_back(&parserDelAdmin); +dataParser.push_back(&parserAddAdmin); + +xmlParser = XML_ParserCreate(NULL); + +if (!xmlParser) + { + WriteServLog("Couldn't allocate memory for parser."); + exit(1); + } + +//XML_SetElementHandler(parser, ParseXMLStart, ParseXMLEnd); +} +//----------------------------------------------------------------------------- +CONFIGPROTO::~CONFIGPROTO() +{ +XML_ParserFree(xmlParser); +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::ParseCommand() +{ +list::iterator n; +int done = 0; +char str[9]; +int len; + +if (requestList.empty()) + return 0; + +n = requestList.begin(); + +strncpy(str, (*n).c_str(), 8); +str[8] = 0; + +XML_ParserReset(xmlParser, NULL); +XML_SetElementHandler(xmlParser, ParseXMLStart, ParseXMLEnd); +XML_SetUserData(xmlParser, this); + +while(nonstop) + { + strncpy(str, (*n).c_str(), 8); + str[8] = 0; + len = strlen(str); + + n++; + if (n == requestList.end()) + done = 1; + n--; + + if (XML_Parse(xmlParser, (*n).c_str(), len, done) == XML_STATUS_ERROR) + { + WriteServLog("Invalid configuration request"); + printfd(__FILE__, "Parse error at line %d:\n%s\n", + XML_GetCurrentLineNumber(xmlParser), + XML_ErrorString(XML_GetErrorCode(xmlParser))); + if (currParser) + { + printfd(__FILE__, "Parser reset\n"); + currParser->Reset(); + currParser = NULL; + } + + return -1; + } + + if (done) + return 0; + + n++; + } + +return 0; +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetPort(uint16_t p) +{ +port = p; +} +//----------------------------------------------------------------------------- +/*void CONFIGPROTO::SetHostAllow(HOSTALLOW *) +{ +//hostAllow = ha; +}*/ +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetAdmins(ADMINS * a) +{ +admins = a; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetAdmins(a); + } + +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetUsers(USERS * u) +{ +//users = u; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetUsers(u); + } + +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetTariffs(TARIFFS * t) +{ +//tariffs = t; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetTariffs(t); + } +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetStore(BASE_STORE * s) +{ +//store = s; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetStore(s); + } +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetStgSettings(const SETTINGS * s) +{ +//settings = s; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetStgSettings(s); + } +} +//----------------------------------------------------------------------------- +/*void CONFIGPROTO::Start() +{ +finished = false; +threadExited = false; +status = starting; + +xmlParser = XML_ParserCreate(NULL); + +if (!xmlParser) + { + WriteServLog("Couldn't allocate memory for parser."); + } + +pthread_create(&thrReciveSendConf, NULL, ReciveSendConf, this); +status = started; +}*/ +//----------------------------------------------------------------------------- +/*int CONFIGPROTO::Stop() +{ +nonstop = true; +close(outerSocket); +return 0; +}*/ +//----------------------------------------------------------------------------- +/*void CONFIGPROTO::Restart() +{ +//Stop(); +//Start(); +}*/ +//----------------------------------------------------------------------------- +/*CONF_STATUS CONFIGPROTO::Status() +{ +//return status; +} +//----------------------------------------------------------------------------- +*/ +const string & CONFIGPROTO::GetStrError() +{ +return errorStr; +} +//----------------------------------------------------------------------------- +uint32_t CONFIGPROTO::GetAdminIP() +{ +return adminIP; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/configuration/sgconfig/configproto.h b/projects/stargazer/plugins/configuration/sgconfig/configproto.h new file mode 100644 index 00000000..29369867 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/configproto.h @@ -0,0 +1,143 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.14 $ + $Date: 2010/10/04 20:24:14 $ + $Author: faust $ + */ + + +#ifndef CONFIGPROTO_H +#define CONFIGPROTO_H + +#include +#include +#include + +#include + +#include "parser.h" +#include "../../../users.h" +#include "../../../admins.h" +#include "../../../tariffs.h" +#include "stg_logger.h" + +using namespace std; + +#define STG_HEADER "SG04" +#define OK_HEADER "OKHD" +#define ERR_HEADER "ERHD" +#define OK_LOGIN "OKLG" +#define ERR_LOGIN "ERLG" +#define OK_LOGINS "OKLS" +#define ERR_LOGINS "ERLS" + +//----------------------------------------------------------------------------- +class CONFIGPROTO +{ +public: + CONFIGPROTO(); + ~CONFIGPROTO(); + + void SetPort(uint16_t port); + //void SetHostAllow(HOSTALLOW * ha); + void SetAdmins(ADMINS * a); + void SetUsers(USERS * u); + void SetTariffs(TARIFFS * t); + void SetStore(BASE_STORE * s); + void SetStgSettings(const SETTINGS * s); + const string & GetAdminLogin(); + uint32_t GetAdminIP(); + int Prepare(); + int Stop(); + const string & GetStrError(); + static void * Run(void * a); + +private: + int RecvHdr(int sock); + int RecvLogin(int sock); + int SendLoginAnswer(int sock, int err); + int SendHdrAnswer(int sock, int err); + int RecvLoginS(int sock); + int SendLoginSAnswer(int sock, int err); + int RecvData(int sock); + int SendDataAnswer(int sock); + void SendError(const char * text); + void WriteLogAccessFailed(uint32_t ip); + + int ParseCommand(); + + list answerList; + list requestList; + uint32_t adminIP; + string adminLogin; + uint16_t port; + pthread_t thrReciveSendConf; + bool nonstop; + int state; + //HOSTALLOW * hostAllow; + ADMIN currAdmin; + STG_LOGGER & WriteServLog; + + int outerSocket; + int listenSocket; + struct sockaddr_in outerAddr; + socklen_t outerAddrLen; + + PARSER_GET_SERVER_INFO parserGetServInfo; + + PARSER_GET_USERS parserGetUsers; + PARSER_GET_USER parserGetUser; + PARSER_CHG_USER parserChgUser; + PARSER_ADD_USER parserAddUser; + PARSER_DEL_USER parserDelUser; + PARSER_CHECK_USER parserCheckUser; + PARSER_SEND_MESSAGE parserSendMessage; + + PARSER_GET_ADMINS parserGetAdmins; + PARSER_ADD_ADMIN parserAddAdmin; + PARSER_DEL_ADMIN parserDelAdmin; + PARSER_CHG_ADMIN parserChgAdmin; + + PARSER_GET_TARIFFS parserGetTariffs; + PARSER_ADD_TARIFF parserAddTariff; + PARSER_DEL_TARIFF parserDelTariff; + PARSER_CHG_TARIFF parserChgTariff; + + ADMINS * admins; + //USERS * users; + //TARIFFS * tariffs; + //BASE_STORE * store; + //const SETTINGS * settings; + + BASE_PARSER * currParser; + vector dataParser; + + XML_Parser xmlParser; + + string errorStr; + + friend void ParseXMLStart(void *data, const char *el, const char **attr); + friend void ParseXMLEnd(void *data, const char *el); +}; +//----------------------------------------------------------------------------- +#endif //CONFIGPROTO_H + diff --git a/projects/stargazer/plugins/configuration/sgconfig/net_configurator.cpp b/projects/stargazer/plugins/configuration/sgconfig/net_configurator.cpp new file mode 100644 index 00000000..6b8bc845 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/net_configurator.cpp @@ -0,0 +1,133 @@ +#include +#include "net_configurator.h" +#include "../../internal_configurator.h" + +//----------------------------------------------------------------------------- +const string & NET_CONFIGURATOR_SETTINGS::GetStrError() +{ +return strError; +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR_SETTINGS::ReadSettings(const CONFIGFILE &cf) +{ +if (cf.ReadUShortInt("AdminPort", &port, 5555) != 0) + { + strError = "Cannot read parameter AdminPort."; + return -1; + } +if (port < 1 || port > 65535) + { + strError = "Incorrect value AdminPort."; + return -1; + } + +string strOrder; +cf.ReadString("AdminOrder", &strOrder, "allow,deny"); +if (hostAllow.ParseOrder(strOrder.c_str()) < 0) + { + strError = string("Error in parameter AdminOrder. ") + hostAllow.GetStrError(); + return -1; + } + +string strAllow; +cf.ReadString("AdminAllowFrom", &strAllow, "all"); +if (hostAllow.ParseHosts(strAllow.c_str(), hostsAllow) != 0) + { + strError = string("Error in parameter AdminAllowFrom. ") + hostAllow.GetStrError(); + return -1; + } + +string strDeny; +cf.ReadString("AdminDenyFrom", &strDeny, ""); +if (hostAllow.ParseHosts(strDeny.c_str(), hostsDeny) != 0) + { + strError = string("Error in parameter AdminDenyFrom. ") + hostAllow.GetStrError(); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +uint16_t NET_CONFIGURATOR_SETTINGS::GetPort() +{ +return port; +} +//----------------------------------------------------------------------------- +HOSTALLOW * NET_CONFIGURATOR_SETTINGS::GetHostAllow() +{ +return &hostAllow; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +NET_CONFIGURATOR::NET_CONFIGURATOR() +{ +hostAllow = settings.GetHostAllow(); +} +//----------------------------------------------------------------------------- +NET_CONFIGURATOR::~NET_CONFIGURATOR() +{ + +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::SetStgConfigurator(BASE_INT_CONFIGURATOR * bsc) +{ +stgConfigurator = bsc; +cp.SetStgConfigurator(stgConfigurator); +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR::UserGetAll(string * login, + USER_CONF_RES * conf, + USER_STAT_RES * stat, + time_t lastUpdate) +{ +return 0; +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR::TatiffGetAll(TARIFF_CONF * conf) +{ +return 0; +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR::AdminGetAll(ADMIN_CONF * conf) +{ +return 0; +} +//----------------------------------------------------------------------------- +const string & NET_CONFIGURATOR::GetStrError() +{ +return strError; +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::Start() +{ +cp.SetPort(settings.GetPort()); +cp.SetHostAllow(settings.GetHostAllow()); +cp.Start(); +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::Stop() +{ +cp.Stop(); +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::Restart() +{ +cp.Restart(); +} +//----------------------------------------------------------------------------- +CONF_STATUS NET_CONFIGURATOR::Status() +{ +return cp.Status(); +} +//----------------------------------------------------------------------------- +BASE_SETTINGS * NET_CONFIGURATOR::GetConfiguratorSettings() +{ +return &settings; +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::SetAdmins(const ADMINS * a) +{ +cp.SetAdmins(a); +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/configuration/sgconfig/net_configurator.h b/projects/stargazer/plugins/configuration/sgconfig/net_configurator.h new file mode 100644 index 00000000..ec1348f0 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/net_configurator.h @@ -0,0 +1,65 @@ + /* + $Revision: 1.2 $ + $Date: 2005/10/30 21:34:28 $ + */ + +#ifndef NET_CONFIGURATOR_H +#define NET_CONFIGURATOR_H + +#include +#include + +#include "../../base_ext_configurator.h" +#include "../../base_int_configurator.h" +#include "../../base_settings.h" +#include "hostallow.h" +#include "conffiles.h" +#include "configproto.h" + +using namespace std; +//----------------------------------------------------------------------------- +class NET_CONFIGURATOR_SETTINGS: public BASE_SETTINGS +{ +public: + virtual ~NET_CONFIGURATOR_SETTINGS(){}; +virtual const string & GetStrError(); + virtual int ReadSettings(const CONFIGFILE & cf); + uint16_t GetPort(); + HOSTALLOW * GetHostAllow(); + +private: + string strError; + uint16_t port; + HOSTALLOW hostAllow; +}; +//----------------------------------------------------------------------------- +class NET_CONFIGURATOR: public BASE_EXT_CONFIGURATOR +{ +public: + NET_CONFIGURATOR(); + virtual ~NET_CONFIGURATOR(); + virtual void SetStgConfigurator(BASE_INT_CONFIGURATOR *); + virtual int UserGetAll(string * login, + USER_CONF_RES * conf, + USER_STAT_RES * stat, + time_t lastUpdate); + virtual int TatiffGetAll(TARIFF_CONF * conf); + virtual int AdminGetAll(ADMIN_CONF * conf); + virtual const string & GetStrError(); + virtual void Start(); + virtual void Stop(); + virtual void Restart(); + virtual CONF_STATUS Status(); + virtual BASE_SETTINGS * GetConfiguratorSettings(); + virtual void SetAdmins(const ADMINS * a); + +private: + HOSTALLOW * hostAllow; + BASE_INT_CONFIGURATOR * stgConfigurator; + NET_CONFIGURATOR_SETTINGS settings; + string strError; + CONFIGPROTO cp; +}; +//----------------------------------------------------------------------------- +#endif //NET_CONFIGURATOR_H + diff --git a/projects/stargazer/plugins/configuration/sgconfig/parser.cpp b/projects/stargazer/plugins/configuration/sgconfig/parser.cpp new file mode 100644 index 00000000..b62b292e --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/parser.cpp @@ -0,0 +1,1466 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "parser.h" +#include "version.h" + +#define UNAME_LEN (256) +//----------------------------------------------------------------------------- +// GET SERVER INFO +//----------------------------------------------------------------------------- +int PARSER_GET_SERVER_INFO::ParseStart(void *, const char *el, const char **) +{ +answerList->erase(answerList->begin(), answerList->end()); +if (strcasecmp(el, "GetServerInfo") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_SERVER_INFO::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetServerInfo") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::CreateAnswer() +{ +char s[UNAME_LEN + 128]; +char un[UNAME_LEN]; +struct utsname utsn; + +int tariff_type; + +tariff_type = 2; + +uname(&utsn); +un[0] = 0; + +strcat(un, utsn.sysname); +strcat(un, " "); +strcat(un, utsn.release); +strcat(un, " "); +strcat(un, utsn.machine); +strcat(un, " "); +strcat(un, utsn.nodename); + +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); +answerList->push_back(""); + +sprintf(s, "", SERVER_VERSION); +answerList->push_back(s); + +sprintf(s, "", tariffs->GetTariffsNum()); +answerList->push_back(s); + +sprintf(s, "", 2); +answerList->push_back(s); + +sprintf(s, "", users->GetUserNum()); +answerList->push_back(s); + +sprintf(s, "", un); +answerList->push_back(s); + +sprintf(s, "", DIR_NUM); +answerList->push_back(s); + +sprintf(s, "", settings->GetDayFee()); +answerList->push_back(s); + +for (int i = 0; i< DIR_NUM; i++) + { + string dn2e; + Encode12str(dn2e, settings->GetDirName(i)); + sprintf(s, "", i, dn2e.c_str()); + answerList->push_back(s); + } + +answerList->push_back(""); +} +//----------------------------------------------------------------------------- +// GET USER +//----------------------------------------------------------------------------- +PARSER_GET_USER::PARSER_GET_USER() +{ + +} +//----------------------------------------------------------------------------- +int PARSER_GET_USER::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "GetUser") == 0) + { + if (attr[0] && attr[1]) + login = attr[1]; + else + { + //login.clear(); + login.erase(login.begin(), login.end()); + return -1; + } + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_USER::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetUser") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USER::CreateAnswer() +{ +string s; +string enc; + +user_iter u; + +answerList->erase(answerList->begin(), answerList->end()); + +if (users->FindByName(login, &u)) + { + s = ""; + answerList->push_back(s); + return; + } + +s = ""; +answerList->push_back(s); + +s = "GetLogin() + "\"/>"; +answerList->push_back(s); + +if (currAdmin.GetPriv()->userConf || currAdmin.GetPriv()->userPasswd) + s = "property.password.Get() + "\" />"; +else + s = ""; +answerList->push_back(s); + +strprintf(&s, "", u->property.cash.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.freeMb.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.credit.Get()); +answerList->push_back(s); + +if (u->property.nextTariff.Get() != "") + { + strprintf(&s, "", + u->property.tariffName.Get().c_str(), + u->property.nextTariff.Get().c_str()); + } +else + { + strprintf(&s, "", + u->property.tariffName.Get().c_str()); + } + +answerList->push_back(s); + +Encode12str(enc, u->property.note); +s = ""; +answerList->push_back(s); + +Encode12str(enc, u->property.phone); +s = ""; +answerList->push_back(s); + +Encode12str(enc, u->property.address); +s = "
"; +answerList->push_back(s); + +Encode12str(enc, u->property.email); +s = ""; +answerList->push_back(s); + + +vector *> userdata; +userdata.push_back(u->property.userdata0.GetPointer()); +userdata.push_back(u->property.userdata1.GetPointer()); +userdata.push_back(u->property.userdata2.GetPointer()); +userdata.push_back(u->property.userdata3.GetPointer()); +userdata.push_back(u->property.userdata4.GetPointer()); +userdata.push_back(u->property.userdata5.GetPointer()); +userdata.push_back(u->property.userdata6.GetPointer()); +userdata.push_back(u->property.userdata7.GetPointer()); +userdata.push_back(u->property.userdata8.GetPointer()); +userdata.push_back(u->property.userdata9.GetPointer()); + +string tmpI; +for (unsigned i = 0; i < userdata.size(); i++) + { + Encode12str(enc, userdata[i]->Get()); + s = ""; + answerList->push_back(s); + } + +Encode12str(enc, u->property.realName); +s = ""; +answerList->push_back(s); + +Encode12str(enc, u->property.group); +s = ""; +answerList->push_back(s); + +strprintf(&s, "", u->GetConnected()); +answerList->push_back(s); + +strprintf(&s, "", u->property.alwaysOnline.Get()); +answerList->push_back(s); + +strprintf(&s, "", inet_ntostring(u->GetCurrIP()).c_str()); +answerList->push_back(s); + +strprintf(&s, "", u->GetPingTime()); +answerList->push_back(s); + +stringstream sstr; +sstr << u->property.ips.Get(); +strprintf(&s, "", sstr.str().c_str()); +answerList->push_back(s); + +char * ss; +ss = new char[DIR_NUM*25*4 + 50]; +char st[50]; +sprintf(ss, "property.down.Get(); +upload = u->property.up.Get(); + +for (int j = 0; j < DIR_NUM; j++) + { + string s; + x2str(upload[j], s); + sprintf(st, " MU%d=\"%s\"", j, s.c_str()); + strcat(ss, st); + + x2str(download[j], s); + sprintf(st, " MD%d=\"%s\"", j, s.c_str()); + strcat(ss, st); + + sprintf(st, " SU%d=\"0\"", j); + strcat(ss, st); + + sprintf(st, " SD%d=\"0\"", j); + strcat(ss, st); + } +strcat(ss, " />"); +answerList->push_back(ss); +delete[] ss; + +strprintf(&s, "", u->property.disabled.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.disabledDetailStat.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.passive.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.lastCashAdd.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.lastCashAddTime.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.lastActivityTime.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.creditExpire.Get()); +answerList->push_back(s); + +strprintf(&s, ""); +answerList->push_back(s); +} +//----------------------------------------------------------------------------- +// GET USERS +//----------------------------------------------------------------------------- +PARSER_GET_USERS::PARSER_GET_USERS() +{ +lastUserUpdateTime = 0; +} +//----------------------------------------------------------------------------- +int PARSER_GET_USERS::ParseStart(void *, const char *el, const char ** attr) +{ +/*if (attr && *attr && *(attr+1)) + { + printfd(__FILE__, "attr=%s %s\n", *attr, *(attr+1)); + } +else + { + printfd(__FILE__, "attr = NULL\n"); + }*/ + +lastUpdateFound = false; +if (strcasecmp(el, "GetUsers") == 0) + { + while (attr && *attr && *(attr+1)) + { + if (strcasecmp(*attr, "LastUpdate") == 0) + { + if (str2x(*(attr+1), lastUserUpdateTime) == 0) + { + //printfd(__FILE__, "lastUserUpdateTime=%d\n", lastUserUpdateTime); + lastUpdateFound = true; + } + else + { + //printfd(__FILE__, "NO lastUserUpdateTime\n"); + } + } + ++attr; + } + + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_USERS::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetUsers") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::CreateAnswer() +{ +answerList->erase(answerList->begin(), answerList->end()); + +string s; +string userStart; +string traffStart; +string traffMiddle; +string traffFinish; +string middle; +string userFinish; + + +string enc; + +user_iter u; + +int h = users->OpenSearch(); +if (!h) + { + printfd(__FILE__, "users->OpenSearch() error\n"); + users->CloseSearch(h); + return; + } +string updateTime; +x2str(time(NULL), updateTime); + +if (lastUpdateFound) + answerList->push_back(""); +else + answerList->push_back(""); + +while (1) + { + if (users->SearchNext(h, &u)) + { + break; + } + userStart = "GetLogin() + "\">"; + middle = ""; + + if (u->property.password.ModificationTime() > lastUserUpdateTime) + { + if (currAdmin.GetPriv()->userConf || currAdmin.GetPriv()->userPasswd) + s = "property.password.Get() + "\" />"; + else + s = ""; + middle += s; + } + + + if (u->property.cash.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.cash.Get()); + middle += s; + //printfd(__FILE__, "cash value=\"%f\"\n", u->property.cash.Get()); + } + + + if (u->property.freeMb.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.freeMb.Get()); + middle += s; + } + + if (u->property.credit.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.credit.Get()); + middle += s; + } + + if (u->property.nextTariff.Get() != "") + { + if (u->property.tariffName.ModificationTime() > lastUserUpdateTime + || u->property.nextTariff.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", + u->property.tariffName.Get().c_str(), + u->property.nextTariff.Get().c_str()); + middle += s; + } + } + else + { + if (u->property.tariffName.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", + u->property.tariffName.Get().c_str()); + middle += s; + } + } + + if (u->property.note.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.note); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.phone.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.phone); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.address.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.address); + strprintf(&s, "
", enc.c_str()); + middle += s; + } + + if (u->property.email.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.email); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + vector *> userdata; + userdata.push_back(u->property.userdata0.GetPointer()); + userdata.push_back(u->property.userdata1.GetPointer()); + userdata.push_back(u->property.userdata2.GetPointer()); + userdata.push_back(u->property.userdata3.GetPointer()); + userdata.push_back(u->property.userdata4.GetPointer()); + userdata.push_back(u->property.userdata5.GetPointer()); + userdata.push_back(u->property.userdata6.GetPointer()); + userdata.push_back(u->property.userdata7.GetPointer()); + userdata.push_back(u->property.userdata8.GetPointer()); + userdata.push_back(u->property.userdata9.GetPointer()); + + string tmpI; + for (unsigned i = 0; i < userdata.size(); i++) + { + if (userdata[i]->ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, userdata[i]->Get()); + s = ""; + middle += s; + } + } + + if (u->property.realName.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.realName); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.group.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.group); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.alwaysOnline.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.alwaysOnline.Get()); + middle += s; + } + + if (u->GetCurrIPModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", inet_ntostring(u->GetCurrIP()).c_str()); + middle += s; + } + + + if (u->GetConnectedModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->GetConnected()); + middle += s; + } + + if (u->GetPingTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->GetPingTime()); + middle += s; + } + + if (u->property.ips.ModificationTime() > lastUserUpdateTime) + { + stringstream sstr; + sstr << u->property.ips.Get(); + strprintf(&s, "", sstr.str().c_str()); + middle += s; + } + + char st[50]; + traffStart = "property.down.Get(); + upload = u->property.up.Get(); + traffMiddle = ""; + + if (u->property.up.ModificationTime() > lastUserUpdateTime) + { + for (int j = 0; j < DIR_NUM; j++) + { + string s; + x2str(upload[j], s); + sprintf(st, " MU%d=\"%s\" ", j, s.c_str()); + traffMiddle += st; + } + } + + if (u->property.down.ModificationTime() > lastUserUpdateTime) + { + for (int j = 0; j < DIR_NUM; j++) + { + x2str(download[j], s); + sprintf(st, " MD%d=\"%s\" ", j, s.c_str()); + traffMiddle += st; + } + } + + traffFinish = " />"; + if (traffMiddle.length() > 0) + { + middle += traffStart; + middle += traffMiddle; + middle += traffFinish; + } + + if (u->property.disabled.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.disabled.Get()); + middle += s; + } + + if (u->property.disabledDetailStat.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.disabledDetailStat.Get()); + middle += s; + } + + //printfd(__FILE__, ">>>>> %s\n", s.c_str()); + + if (u->property.passive.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.passive.Get()); + middle += s; + } + + if (u->property.lastCashAdd.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.lastCashAdd.Get()); + middle += s; + } + + if (u->property.lastCashAddTime.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.lastCashAddTime.Get()); + middle += s; + } + + + if (u->property.lastActivityTime.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.lastActivityTime.Get()); + middle += s; + } + + if (u->property.creditExpire.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.creditExpire.Get()); + middle += s; + } + + + userFinish = ""; + + if (middle.length() > 0) + { + /*printfd(__FILE__, "login: %s\n", u->GetLogin().c_str()); + printfd(__FILE__, "middle: %s\n", middle.c_str());*/ + + answerList->push_back(userStart); + answerList->push_back(middle); + answerList->push_back(userFinish); + } + } + +users->CloseSearch(h); + +//answerList->push_back(""); + +answerList->push_back(""); +} +//----------------------------------------------------------------------------- +// ADD USER +//----------------------------------------------------------------------------- +PARSER_ADD_USER::PARSER_ADD_USER() +{ +depth = 0; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_USER::ParseStart(void *, const char *el, const char **attr) +{ +depth++; + +if (depth == 1) + { + if (strcasecmp(el, "AddUser") == 0) + { + return 0; + } + } +else + { + if (strcasecmp(el, "login") == 0) + { + login = attr[1]; + return 0; + } + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_USER::ParseEnd(void *, const char *el) +{ +if (depth == 1) + { + if (strcasecmp(el, "AddUser") == 0) + { + CreateAnswer(); + depth--; + return 0; + } + } + +depth--; +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_USER::Reset() +{ +BASE_PARSER::Reset(); +depth = 0; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_USER::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (CheckUserData() == 0) + { + answerList->push_back(""); + } +else + { + answerList->push_back(""); + } +} +//----------------------------------------------------------------------------- +int PARSER_ADD_USER::CheckUserData() +{ +user_iter u; +if (users->FindByName(login, &u)) + { + return users->Add(login, currAdmin); + } +return -1; +} +//----------------------------------------------------------------------------- +// PARSER CHG USER +//----------------------------------------------------------------------------- +PARSER_CHG_USER::PARSER_CHG_USER() +{ +usr = NULL; +ucr = NULL; +upr = NULL; +downr = NULL; + +Reset(); +} +//----------------------------------------------------------------------------- +PARSER_CHG_USER::~PARSER_CHG_USER() +{ +delete usr; +delete ucr; +delete[] upr; +delete[] downr; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::Reset() +{ +depth = 0; +delete usr; + +delete ucr; + +delete[] upr; + +delete[] downr; + +usr = new USER_STAT_RES; +ucr = new USER_CONF_RES; + +upr = new RESETABLE[DIR_NUM]; +downr = new RESETABLE[DIR_NUM]; +}; +//----------------------------------------------------------------------------- +string PARSER_CHG_USER::EncChar2String(const char * strEnc) +{ +string str; +Decode21str(str, strEnc); +return str; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::ParseStart(void *, const char *el, const char **attr) +{ +depth++; + +if (depth == 1) + { + if (strcasecmp(el, "SetUser") == 0) + { + return 0; + } + } +else + { + //printfd(__FILE__, "el=%s\n", el); + if (strcasecmp(el, "login") == 0) + { + login = attr[1]; + return 0; + } + + if (strcasecmp(el, "ip") == 0) + { + try + { + ucr->ips = StrToIPS(attr[1]); + } + catch (...) + { + printfd(__FILE__, "StrToIPS Error!\n"); + } + } + + if (strcasecmp(el, "password") == 0) + { + ucr->password = attr[1]; + return 0; + } + + if (strcasecmp(el, "address") == 0) + { + ucr->address = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "aonline") == 0) + { + ucr->alwaysOnline = (*(attr[1]) != '0'); + return 0; + } + + if (strcasecmp(el, "cash") == 0) + { + if (attr[2] && (strcasecmp(attr[2], "msg") == 0)) + { + cashMsg = EncChar2String(attr[3]); + } + + double cash; + if (strtodouble2(attr[1], cash) == 0) + usr->cash = cash; + + if (strcasecmp(attr[0], "set") == 0) + cashMustBeAdded = false; + + if (strcasecmp(attr[0], "add") == 0) + cashMustBeAdded = true; + + return 0; + } + + if (strcasecmp(el, "CreditExpire") == 0) + { + long int creditExpire = 0; + if (str2x(attr[1], creditExpire) == 0) + ucr->creditExpire = (time_t)creditExpire; + + return 0; + } + + if (strcasecmp(el, "credit") == 0) + { + double credit; + if (strtodouble2(attr[1], credit) == 0) + ucr->credit = credit; + return 0; + } + + if (strcasecmp(el, "freemb") == 0) + { + double freeMb; + if (strtodouble2(attr[1], freeMb) == 0) + usr->freeMb = freeMb; + return 0; + } + + if (strcasecmp(el, "down") == 0) + { + int down = 0; + if (str2x(attr[1], down) == 0) + ucr->disabled = down; + return 0; + } + + if (strcasecmp(el, "DisableDetailStat") == 0) + { + int disabledDetailStat = 0; + if (str2x(attr[1], disabledDetailStat) == 0) + ucr->disabledDetailStat = disabledDetailStat; + return 0; + } + + if (strcasecmp(el, "email") == 0) + { + ucr->email = EncChar2String(attr[1]); + return 0; + } + + for (int i = 0; i < USERDATA_NUM; i++) + { + char name[15]; + sprintf(name, "userdata%d", i); + if (strcasecmp(el, name) == 0) + { + ucr->userdata[i] = EncChar2String(attr[1]); + return 0; + } + } + + if (strcasecmp(el, "group") == 0) + { + ucr->group = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "note") == 0) + { + ucr->note = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "passive") == 0) + { + int passive = 0; + if (str2x(attr[1], passive) == 0) + ucr->passive = passive; + return 0; + } + + if (strcasecmp(el, "phone") == 0) + { + ucr->phone = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "Name") == 0) + { + ucr->realName = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "traff") == 0) + { + int j = 0; + int dir; + DIR_TRAFF dtu; + DIR_TRAFF dtd; + unsigned long long t = 0; + while (attr[j]) + { + dir = attr[j][2] - '0'; + + if (strncasecmp(attr[j], "md", 2) == 0) + { + str2x(attr[j+1], t); + dtd[dir] = t; + downr[dir] = t; + } + if (strncasecmp(attr[j], "mu", 2) == 0) + { + str2x(attr[j+1], t); + dtu[dir] = t; + upr[dir] = t; + } + j+=2; + } + usr->down = dtd; + usr->up = dtu; + return 0; + } + + if (strcasecmp(el, "tariff") == 0) + { + if (strcasecmp(attr[0], "now") == 0) + ucr->tariffName = attr[1]; + + if (strcasecmp(attr[0], "delayed") == 0) + ucr->nextTariff = attr[1]; + + return 0; + } + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::ParseEnd(void *, const char *el) +{ +if (depth == 1) + { + if (strcasecmp(el, "SetUser") == 0) + { + AplayChanges(); + CreateAnswer(); + depth--; + return 0; + } + } + +depth--; +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +switch (res) + { + case 0: + answerList->push_back(""); + break; + case -1: + answerList->push_back(""); + break; + case -2: + answerList->push_back(""); + break; + default: + answerList->push_back(""); + break; + } + +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::CheckUserData() +{ +return true; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::AplayChanges() +{ +user_iter u; + +res = 0; +if (users->FindByName(login, &u)) + { + res = -1; + return -1; + } + +if (!ucr->ips.res_empty()) + if (!u->property.ips.Set(ucr->ips.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->address.res_empty()) + if (!u->property.address.Set(ucr->address.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->alwaysOnline.res_empty()) + if (!u->property.alwaysOnline.Set(ucr->alwaysOnline.const_data(), + currAdmin, login, store)) + res = -1; + +if (!ucr->creditExpire.res_empty()) + if (!u->property.creditExpire.Set(ucr->creditExpire.const_data(), + currAdmin, login, store)) + res = -1; + +if (!ucr->credit.res_empty()) + if (!u->property.credit.Set(ucr->credit.const_data(), currAdmin, login, store)) + res = -1; + +if (!usr->freeMb.res_empty()) + if (!u->property.freeMb.Set(usr->freeMb.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->disabled.res_empty()) + if (!u->property.disabled.Set(ucr->disabled.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->disabledDetailStat.res_empty()) + if (!u->property.disabledDetailStat.Set(ucr->disabledDetailStat.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->email.res_empty()) + if (!u->property.email.Set(ucr->email.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->group.res_empty()) + if (!u->property.group.Set(ucr->group.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->note.res_empty()) + if (!u->property.note.Set(ucr->note.const_data(), currAdmin, login, store)) + res = -1; + +vector *> userdata; +userdata.push_back(u->property.userdata0.GetPointer()); +userdata.push_back(u->property.userdata1.GetPointer()); +userdata.push_back(u->property.userdata2.GetPointer()); +userdata.push_back(u->property.userdata3.GetPointer()); +userdata.push_back(u->property.userdata4.GetPointer()); +userdata.push_back(u->property.userdata5.GetPointer()); +userdata.push_back(u->property.userdata6.GetPointer()); +userdata.push_back(u->property.userdata7.GetPointer()); +userdata.push_back(u->property.userdata8.GetPointer()); +userdata.push_back(u->property.userdata9.GetPointer()); + +for (int i = 0; i < (int)userdata.size(); i++) + { + if (!ucr->userdata[i].res_empty()) + { + if(!userdata[i]->Set(ucr->userdata[i].const_data(), currAdmin, login, store)) + res = -1; + } + } + +if (!ucr->passive.res_empty()) + if (!u->property.passive.Set(ucr->passive.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->password.res_empty()) + if (!u->property.password.Set(ucr->password.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->phone.res_empty()) + if (!u->property.phone.Set(ucr->phone.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->realName.res_empty()) + if (!u->property.realName.Set(ucr->realName.const_data(), currAdmin, login, store)) + res = -1; + + +if (!usr->cash.res_empty()) + { + //if (currAdmin->GetPriv()->userCash) + { + if (cashMustBeAdded) + { + if (!u->property.cash.Set(usr->cash.const_data() + u->property.cash, + currAdmin, + login, + store, + cashMsg)) + res = -1; + } + else + { + if (!u->property.cash.Set(usr->cash.const_data(), currAdmin, login, store, cashMsg)) + res = -1; + } + } + } + + +if (!ucr->tariffName.res_empty()) + { + if (tariffs->FindByName(ucr->tariffName.const_data())) + { + if (!u->property.tariffName.Set(ucr->tariffName.const_data(), currAdmin, login, store)) + res = -1; + u->ResetNextTariff(); + } + else + { + //WriteServLog("SetUser: Tariff %s not found", ud.conf.tariffName.c_str()); + res = -1; + } + } + +if (!ucr->nextTariff.res_empty()) + { + if (tariffs->FindByName(ucr->nextTariff.const_data())) + { + if (!u->property.nextTariff.Set(ucr->nextTariff.const_data(), currAdmin, login, store)) + res = -1; + } + else + { + //WriteServLog("SetUser: Tariff %s not found", ud.conf.tariffName.c_str()); + res = -1; + } + } + +DIR_TRAFF up = u->property.up; +DIR_TRAFF down = u->property.down; +int upCount = 0; +int downCount = 0; +for (int i = 0; i < DIR_NUM; i++) + { + if (!upr[i].res_empty()) + { + up[i] = upr[i]; + upCount++; + } + if (!downr[i].res_empty()) + { + down[i] = downr[i]; + downCount++; + } + } + +if (upCount) + if (!u->property.up.Set(up, currAdmin, login, store)) + res = -1; + +if (downCount) + if (!u->property.down.Set(down, currAdmin, login, store)) + res = -1; + +/*if (!usr->down.res_empty()) + { + u->property.down.Set(usr->down.const_data(), currAdmin, login, store); + } +if (!usr->up.res_empty()) + { + u->property.up.Set(usr->up.const_data(), currAdmin, login, store); + }*/ + +u->WriteConf(); +u->WriteStat(); + +return 0; +} +//----------------------------------------------------------------------------- +// SEND MESSAGE +//----------------------------------------------------------------------------- +int PARSER_SEND_MESSAGE::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "Message") == 0) + { + for (int i = 0; i < 14; i++) + { + if (attr[i] == NULL) + { + result = res_params_error; + CreateAnswer(); + printfd(__FILE__, "To few parameters\n"); + return 0; + } + } + + for (int i = 0; i < 14; i+=2) + { + if (strcasecmp(attr[i], "login") == 0) + { + ParseLogins(attr[i+1]); + /*if (users->FindByName(login, &u)) + { + result = res_unknown; + break; + }*/ + } + + if (strcasecmp(attr[i], "MsgVer") == 0) + { + str2x(attr[i+1], msg.header.ver); + if (msg.header.ver != 1) + result = res_params_error; + } + + if (strcasecmp(attr[i], "MsgType") == 0) + { + str2x(attr[i+1], msg.header.type); + if (msg.header.type != 1) + result = res_params_error; + } + + if (strcasecmp(attr[i], "Repeat") == 0) + { + str2x(attr[i+1], msg.header.repeat); + if (msg.header.repeat < 0) + result = res_params_error; + } + + if (strcasecmp(attr[i], "RepeatPeriod") == 0) + { + str2x(attr[i+1], msg.header.repeatPeriod); + } + + if (strcasecmp(attr[i], "ShowTime") == 0) + { + str2x(attr[i+1], msg.header.showTime); + } + + if (strcasecmp(attr[i], "Text") == 0) + { + Decode21str(msg.text, attr[i+1]); + result = res_ok; + } + } + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_SEND_MESSAGE::ParseEnd(void *, const char *el) +{ +//MSG msg; +if (strcasecmp(el, "Message") == 0) + { + result = res_unknown; + for (unsigned i = 0; i < logins.size(); i++) + { + if (users->FindByName(logins[i], &u)) + { + printfd(__FILE__, "User not found. %s\n", logins[i].c_str()); + continue; + } + msg.header.creationTime = stgTime; + u->AddMessage(&msg); + result = res_ok; + } + /*if (result == res_ok) + { + if (strcmp(login, "*") == 0) + { + msg.text = text; + msg.prio = pri; + printfd(__FILE__, "SendMsg text: %s\n", text); + users->GetAllUsers(SendMessageAllUsers, &msg); + } + else + { + u->AddMessage(pri, text); + } + }*/ + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_SEND_MESSAGE::ParseLogins(const char * login) +{ +char * p; +char * l = new char[strlen(login) + 1]; +strcpy(l, login); +p = strtok(l, ":"); +logins.clear(); +while(p) + { + logins.push_back(p); + p = strtok(NULL, ":"); + } + +delete[] l; +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_SEND_MESSAGE::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); +//answerList->push_back(""); +// +switch (result) + { + case res_ok: + answerList->push_back(""); + break; + case res_params_error: + printfd(__FILE__, "res_params_error\n"); + answerList->push_back(""); + break; + case res_unknown: + printfd(__FILE__, "res_unknown\n"); + answerList->push_back(""); + break; + default: + printfd(__FILE__, "res_default\n"); + } + +} +//----------------------------------------------------------------------------- +// DEL USER +//----------------------------------------------------------------------------- +int PARSER_DEL_USER::ParseStart(void *, const char *el, const char **attr) +{ +res = 0; +if (strcasecmp(el, "DelUser") == 0) + { + if (attr[0] == NULL || attr[1] == NULL) + { + //CreateAnswer("Parameters error!"); + CreateAnswer(); + return 0; + } + + if (users->FindByName(attr[1], &u)) + { + res = 1; + CreateAnswer(); + return 0; + } + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_DEL_USER::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "DelUser") == 0) + { + if (!res) + users->Del(u->GetLogin(), currAdmin); + + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_DEL_USER::CreateAnswer() +{ +if (res) + answerList->push_back(""); +else + answerList->push_back(""); +} +//----------------------------------------------------------------------------- +/*void PARSERDELUSER::CreateAnswer(char * mes) +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +char str[255]; +sprintf(str, "", mes); +answerList->push_back(str); +}*/ +//----------------------------------------------------------------------------- +// CHECK USER +// +//----------------------------------------------------------------------------- +int PARSER_CHECK_USER::ParseStart(void *, const char *el, const char **attr) +{ +result = false; + +if (strcasecmp(el, "CheckUser") == 0) + { + if (attr[0] == NULL || attr[1] == NULL + || attr[2] == NULL || attr[3] == NULL) + { + result = false; + CreateAnswer(); + printfd(__FILE__, "PARSER_CHECK_USER - attr err\n"); + return 0; + } + + user_iter user; + if (users->FindByName(attr[1], &user)) + { + result = false; + CreateAnswer(); + printfd(__FILE__, "PARSER_CHECK_USER - login err\n"); + return 0; + } + + if (strcmp(user->property.password.Get().c_str(), attr[3])) + { + result = false; + CreateAnswer(); + printfd(__FILE__, "PARSER_CHECK_USER - passwd err\n"); + return 0; + } + + result = true; + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHECK_USER::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "CheckUser") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHECK_USER::CreateAnswer() +{ +if (result) + answerList->push_back(""); +else + answerList->push_back(""); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/configuration/sgconfig/parser.h b/projects/stargazer/plugins/configuration/sgconfig/parser.h new file mode 100644 index 00000000..4930db0f --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/parser.h @@ -0,0 +1,257 @@ + /* + $Revision: 1.20 $ + $Date: 2010/10/04 20:26:10 $ + $Author: faust $ + */ + +#ifndef PARSER_H +#define PARSER_H + +#include +#include + +#include "resetable.h" +#include "stg_const.h" +#include "base_store.h" +#include "../../../admins.h" +#include "../../../users.h" +#include "stg_message.h" + +using namespace std; + +//----------------------------------------------------------------------------- +class BASE_PARSER +{ +public: + BASE_PARSER() + : admins(NULL), + users(NULL), + tariffs(NULL), + store(NULL), + settings(NULL), + currAdmin(), + depth(0) + { }; + virtual ~BASE_PARSER() {}; + virtual int ParseStart(void *data, const char *el, const char **attr) = 0; + virtual int ParseEnd(void *data, const char *el) = 0; + virtual void CreateAnswer() = 0; + virtual void SetAnswerList(list * ansList) { answerList = ansList; }; + + virtual void SetUsers(USERS * u) { users = u; }; + virtual void SetAdmins(ADMINS * a) { admins = a; }; + virtual void SetTariffs(TARIFFS * t) { tariffs = t; }; + virtual void SetStore(BASE_STORE * s) { store = s; }; + virtual void SetStgSettings(const SETTINGS * s) { settings = s; }; + + virtual void SetCurrAdmin(const ADMIN & cua) { currAdmin = cua; }; + virtual string & GetStrError(){return strError;}; + virtual void Reset(){ answerList->clear(); depth = 0; }; +protected: + string strError; + ADMINS * admins; + USERS * users; + TARIFFS * tariffs; + BASE_STORE * store; + const SETTINGS * settings; + ADMIN currAdmin; + int depth; + list * answerList; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_ADMINS: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +}; +//----------------------------------------------------------------------------- +class PARSER_ADD_ADMIN: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string adminToAdd; +}; +//----------------------------------------------------------------------------- +class PARSER_DEL_ADMIN: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + int CheckAttr(const char **attr); + string adminToDel; +}; +//----------------------------------------------------------------------------- +class PARSER_CHG_ADMIN: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + RESETABLE login; + RESETABLE password; + RESETABLE privAsString; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_SERVER_INFO: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +}; + +//----------------------------------------------------------------------------- +class PARSER_GET_USER: public BASE_PARSER +{ +public: + PARSER_GET_USER(); + ~PARSER_GET_USER(){}; + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string login; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_USERS: public BASE_PARSER +{ +public: + PARSER_GET_USERS(); + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + time_t lastUserUpdateTime; + bool lastUpdateFound; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_TARIFFS: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +}; +//----------------------------------------------------------------------------- +class PARSER_ADD_TARIFF: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string tariffToAdd; +}; +//----------------------------------------------------------------------------- +class PARSER_DEL_TARIFF: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string tariffToDel; +}; +//----------------------------------------------------------------------------- +class PARSER_CHG_TARIFF: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + int ParseSlashedIntParams(int paramsNum, const string & s, int * params); + int ParseSlashedDoubleParams(int paramsNum, const string & s, double * params); + int CheckTariffData(); + int AplayChanges(); + + TARIFF_DATA_RES td; +}; +//-----------------------------------------------------------------------------/ +class PARSER_ADD_USER: public BASE_PARSER +{ +public: + PARSER_ADD_USER(); + virtual ~PARSER_ADD_USER(){}; + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); + void Reset(); +private: + int CheckUserData(); + string login; +}; +//----------------------------------------------------------------------------- +class PARSER_CHG_USER: public BASE_PARSER +{ +public: + PARSER_CHG_USER(); + ~PARSER_CHG_USER(); + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); + void Reset(); +private: + string EncChar2String(const char *); + + USER_STAT_RES * usr; + USER_CONF_RES * ucr; + RESETABLE * upr; + RESETABLE * downr; + string cashMsg; + string login; + + int CheckUserData(); + int AplayChanges(); + bool cashMustBeAdded; + int res; +}; +//----------------------------------------------------------------------------- +class PARSER_DEL_USER: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); + +private: + int res; + user_iter u; +}; +//----------------------------------------------------------------------------- +class PARSER_CHECK_USER: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + bool result; +}; +//----------------------------------------------------------------------------- +class PARSER_SEND_MESSAGE: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + enum {res_ok, res_params_error, res_unknown}; + vector logins; + int ParseLogins(const char * logins); + int result; + STG_MSG msg; + user_iter u; +}; +//----------------------------------------------------------------------------- +#endif //PARSER_H + + diff --git a/projects/stargazer/plugins/configuration/sgconfig/parser_admin.cpp b/projects/stargazer/plugins/configuration/sgconfig/parser_admin.cpp new file mode 100644 index 00000000..160acc83 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/parser_admin.cpp @@ -0,0 +1,265 @@ +#include +#include + +#include "parser.h" + +//----------------------------------------------------------------------------- +// GET ADMINS +//----------------------------------------------------------------------------- +int PARSER_GET_ADMINS::ParseStart(void *, const char *el, const char **) +{ +if (strcasecmp(el, "GetAdmins") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_ADMINS::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetAdmins") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_ADMINS::CreateAnswer() +{ +const PRIV * priv = currAdmin.GetPriv(); +if (!priv->adminChg) + { + //answerList->clear(); + answerList->erase(answerList->begin(), answerList->end()); + + answerList->push_back(""); + return; + } + +string s; +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +answerList->push_back(""); +ADMIN_CONF ac; +int h = admins->OpenSearch(); + +unsigned int p; +while (admins->SearchNext(h, &ac) == 0) + { + //memcpy(&p, &ac.priv, sizeof(unsigned int)); + p = (ac.priv.userStat << 0) + + (ac.priv.userConf << 2) + + (ac.priv.userCash << 4) + + (ac.priv.userPasswd << 6) + + (ac.priv.userAddDel << 8) + + (ac.priv.adminChg << 10) + + (ac.priv.tariffChg << 12); + strprintf(&s, "", ac.login.c_str(), p); + answerList->push_back(s); + } +admins->CloseSearch(h); +answerList->push_back(""); +} +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// DEL ADMIN +//----------------------------------------------------------------------------- +int PARSER_DEL_ADMIN::ParseStart(void *, const char *el, const char **attr) +{ +strError = ""; +if (strcasecmp(el, "DelAdmin") == 0) + { + adminToDel = attr[1]; + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_DEL_ADMIN::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "DelAdmin") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_DEL_ADMIN::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (admins->Del(adminToDel, currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", admins->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +int PARSER_DEL_ADMIN::CheckAttr(const char **attr) +{ +/* + * attr[0] = "login" (word login) + * attr[1] = login, value of login + * attr[2] = NULL */ + +if (strcasecmp(attr[0], "login") == 0 && attr[1] && !attr[2]) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +// ADD ADMIN +//----------------------------------------------------------------------------- +int PARSER_ADD_ADMIN::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "AddAdmin") == 0) + { + adminToAdd = attr[1]; + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_ADMIN::ParseEnd(void *, const char *el) +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (strcasecmp(el, "AddAdmin") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_ADMIN::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (admins->Add(adminToAdd, currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", admins->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// CHG ADMIN +//----------------------------------------------------------------------------- +int PARSER_CHG_ADMIN::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "ChgAdmin") == 0) + { + for (int i = 0; i < 6; i+=2) + { + printfd(__FILE__, "PARSER_CHG_ADMIN::attr[%d] = %s\n", i, attr[i]); + if (attr[i] == NULL) + break; + + if (strcasecmp(attr[i], "Login") == 0) + { + login = attr[i + 1]; + continue; + } + + if (strcasecmp(attr[i], "Priv") == 0) + { + privAsString = attr[i + 1]; + continue; + } + + if (strcasecmp(attr[i], "Password") == 0) + { + password = attr[i + 1]; + continue; + } + } + + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_ADMIN::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "ChgAdmin") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_ADMIN::CreateAnswer() +{ +answerList->erase(answerList->begin(), answerList->end()); + +ADMIN_CONF conf; +conf.login = login; +if (!login.res_empty()) + { + string s; + //if (admins->FindAdmin(login.data()) != NULL) + // { + if (!password.res_empty()) + conf.password = password.data(); + + if (!privAsString.res_empty()) + { + int p = 0; + if (str2x(privAsString.data().c_str(), p) < 0) + { + strprintf(&s, "" ); + answerList->push_back(s); + return; + } + //memcpy(&conf.priv, &p, sizeof(conf.priv)); + conf.priv.userStat = (p & 0x0003) >> 0x00; // 1+2 + conf.priv.userConf = (p & 0x000C) >> 0x02; // 4+8 + conf.priv.userCash = (p & 0x0030) >> 0x04; // 10+20 + conf.priv.userPasswd = (p & 0x00C0) >> 0x06; // 40+80 + conf.priv.userAddDel = (p & 0x0300) >> 0x08; // 100+200 + conf.priv.adminChg = (p & 0x0C00) >> 0x0A; // 400+800 + conf.priv.tariffChg = (p & 0x3000) >> 0x0C; // 1000+2000 + } + + if (admins->Change(conf, currAdmin) != 0) + { + strprintf(&s, "", admins->GetStrError().c_str()); + answerList->push_back(s); + } + else + { + answerList->push_back(""); + } + return; + // } + //strprintf(&s, "", admins->GetStrError().c_str()); + //answerList->push_back(s); + //return; + } +else + { + answerList->push_back(""); + } +} +//-----------------------------------------------------------------------------*/ + diff --git a/projects/stargazer/plugins/configuration/sgconfig/parser_tariff.cpp b/projects/stargazer/plugins/configuration/sgconfig/parser_tariff.cpp new file mode 100644 index 00000000..aeb1c783 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/parser_tariff.cpp @@ -0,0 +1,492 @@ +//#include +#include + +#include "parser.h" + +const int pt_mega = 1024 * 1024; +//----------------------------------------------------------------------------- +// GET TARIFFS +//----------------------------------------------------------------------------- +int PARSER_GET_TARIFFS::ParseStart(void *, const char * el, const char **) +{ +if (strcasecmp(el, "GetTariffs") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_TARIFFS::ParseEnd(void *, const char * el) +{ +if (strcasecmp(el, "GetTariffs") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_TARIFFS::CreateAnswer() +{ +string s; +char vs[100]; +int hd, hn, md, mn; + +answerList->erase(answerList->begin(), answerList->end()); + +answerList->push_back(""); + +std::list dataList; +tariffs->GetTariffsData(&dataList); +std::list::const_iterator it = dataList.begin(); +for (; it != dataList.end(); ++it) + { + s = "tariffConf.name + "\">"; + answerList->push_back(s); + + for (int j = 0; j < DIR_NUM; j++) + { + hd = it->dirPrice[j].hDay; + md = it->dirPrice[j].mDay; + + hn = it->dirPrice[j].hNight; + mn = it->dirPrice[j].mNight; + + strprintf(&s, "", j, hd, md, hn, mn); + answerList->push_back(s); + } + + strprintf(&s, " dirPrice[i].priceDayA * pt_mega, i+1 == DIR_NUM?"":"/"); + s += vs; + } + s += "\"/>"; + answerList->push_back(s); + + strprintf(&s, " dirPrice[i].priceDayB * pt_mega, i+1 == DIR_NUM?"":"/"); + s += vs; + } + s += "\"/>"; + answerList->push_back(s); + + strprintf(&s, " dirPrice[i].priceNightA * pt_mega, i+1 == DIR_NUM?"":"/"); + s += vs; + } + s += "\"/>"; + answerList->push_back(s); + + strprintf(&s, " dirPrice[i].priceNightB * pt_mega, i+1 == DIR_NUM?"":"/"); + s += vs; + } + s += "\"/>"; + answerList->push_back(s); + + strprintf(&s, " dirPrice[i].threshold, i+1 == DIR_NUM?"":"/"); + s += vs; + } + s += "\"/>"; + answerList->push_back(s); + + strprintf(&s, " dirPrice[i].singlePrice, i+1 == DIR_NUM?"":"/"); + s += vs; + } + s += "\"/>"; + answerList->push_back(s); + + strprintf(&s, " dirPrice[i].noDiscount, i+1 == DIR_NUM?"":"/"); + s += vs; + } + s += "\"/>"; + answerList->push_back(s); + + strprintf(&s, " ", it->tariffConf.fee); + answerList->push_back(s); + + strprintf(&s, " ", it->tariffConf.passiveCost); + answerList->push_back(s); + + strprintf(&s, " ", it->tariffConf.free); + answerList->push_back(s); + + switch (it->tariffConf.traffType) + { + case TRAFF_UP: + answerList->push_back(""); + break; + case TRAFF_DOWN: + answerList->push_back(""); + break; + case TRAFF_UP_DOWN: + answerList->push_back(""); + break; + case TRAFF_MAX: + answerList->push_back(""); + break; + } + + answerList->push_back(""); + } +answerList->push_back(""); +} +//----------------------------------------------------------------------------- +// ADD TARIFF +//----------------------------------------------------------------------------- +int PARSER_ADD_TARIFF::ParseStart(void *, const char * el, const char ** attr) +{ +if (strcasecmp(el, "AddTariff") == 0) + { + if (attr[1]) + { + tariffToAdd = attr[1]; + } + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_TARIFF::ParseEnd(void *, const char * el) +{ +if (strcasecmp(el, "AddTariff") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_TARIFF::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (tariffs->Add(tariffToAdd, currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", tariffs->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +// DEL TARIFF +//----------------------------------------------------------------------------- +int PARSER_DEL_TARIFF::ParseStart(void *, const char * el, const char ** attr) +{ +strError = ""; +if (strcasecmp(el, "DelTariff") == 0) + { + tariffToDel = attr[1]; + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_DEL_TARIFF::ParseEnd(void *, const char * el) +{ +if (strcasecmp(el, "DelTariff") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_DEL_TARIFF::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (users->TariffInUse(tariffToDel)) + { + string s; + strprintf(&s, "", tariffToDel.c_str()); + answerList->push_back(s); + return; + } + +if (tariffs->Del(tariffToDel, currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", tariffs->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// CHG TARIFF +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseSlashedIntParams(int paramsNum, const string & s, int * params) +{ +char * str = new char[s.size() + 1]; +char * p; +strcpy(str, s.c_str()); +p = strtok(str, "/"); + +for (int i = 0; i < paramsNum; i++) + { + if (p == NULL) + { + delete[] str; + return -1; + } + + if (str2x(p, params[i]) != 0) + { + delete[] str; + return -1; + } + + p = strtok(NULL, "/"); + } + +delete[] str; +return 0; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseSlashedDoubleParams(int paramsNum, const string & s, double * params) +{ +char * str = new char[s.size() + 1]; +char * p; +strcpy(str, s.c_str()); +p = strtok(str, "/"); + +for (int i = 0; i < paramsNum; i++) + { + if (p == NULL) + { + delete[] str; + return -1; + } + + if (strtodouble2(p, params[i]) != 0) + { + delete[] str; + return -1; + } + + p = strtok(NULL, "/"); + } + +delete[] str; +return 0; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseStart(void *, const char * el, const char ** attr) +{ +char st[50]; +double price[DIR_NUM]; +int t[DIR_NUM]; +depth++; + +if (depth == 1) + { + if (strcasecmp(el, "SetTariff") == 0) + { + td.tariffConf.name = attr[1]; + return 0; + } + } +else + { + string s; + + if (strcasecmp(el, "PriceDayA") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceDayA = price[j] / pt_mega; + return 0; + } + + if (strcasecmp(el, "PriceDayB") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceDayB = price[j] / pt_mega; + return 0; + } + + + if (strcasecmp(el, "PriceNightA") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceNightA = price[j] / pt_mega; + return 0; + } + + if (strcasecmp(el, "PriceNightB") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceNightB = price[j] / pt_mega; + return 0; + } + + if (strcasecmp(el, "Threshold") == 0) + { + s = attr[1]; + if (ParseSlashedIntParams(DIR_NUM, s, t) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].threshold = t[j]; + return 0; + } + + if (strcasecmp(el, "SinglePrice") == 0) + { + s = attr[1]; + if (ParseSlashedIntParams(DIR_NUM, s, t) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].singlePrice = t[j]; + return 0; + } + + if (strcasecmp(el, "NoDiscount") == 0) + { + s = attr[1]; + if (ParseSlashedIntParams(DIR_NUM, s, t) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].noDiscount = t[j]; + return 0; + } + + for (int j = 0; j < DIR_NUM; j++) + { + snprintf(st, 50, "Time%d", j); + if (strcasecmp(el, st) == 0) + { + int h1, m1, h2, m2; + if (ParseTariffTimeStr(attr[1], h1, m1, h2, m2) == 0) + { + td.dirPrice[j].hDay = h1; + td.dirPrice[j].mDay = m1; + td.dirPrice[j].hNight = h2; + td.dirPrice[j].mNight = m2; + } + return 0; + } + } + + if (strcasecmp(el, "Fee") == 0) + { + double fee; + if (strtodouble2(attr[1], fee) == 0) + td.tariffConf.fee = fee; + return 0; + } + + if (strcasecmp(el, "PassiveCost") == 0) + { + double pc; + if (strtodouble2(attr[1], pc) == 0) + td.tariffConf.passiveCost = pc; + return 0; + } + if (strcasecmp(el, "Free") == 0) + { + double free; + if (strtodouble2(attr[1], free) == 0) + td.tariffConf.free = free; + return 0; + } + + if (strcasecmp(el, "TraffType") == 0) + { + if (strcasecmp(attr[1], "up") == 0) + { + td.tariffConf.traffType = TRAFF_UP; + return 0; + } + + if (strcasecmp(attr[1], "down") == 0) + { + td.tariffConf.traffType = TRAFF_DOWN; + return 0; + } + if (strcasecmp(attr[1], "up+down") == 0) + { + td.tariffConf.traffType = TRAFF_UP_DOWN; + return 0; + } + if (strcasecmp(attr[1], "max") == 0) + { + td.tariffConf.traffType = TRAFF_MAX; + return 0; + } + return 0; + } + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseEnd(void *, const char * el) +{ +if (depth == 1) + { + if (strcasecmp(el, "SetTariff") == 0) + { + CreateAnswer(); + depth--; + return 0; + } + } + +depth--; +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_TARIFF::CreateAnswer() +{ +answerList->erase(answerList->begin(), answerList->end()); + +if (!td.tariffConf.name.data().empty()) + { + TARIFF_DATA tariffData = td.GetData(); + if (tariffs->Chg(tariffData, currAdmin) == 0) + { + answerList->push_back(""); + return; + } + else + { + string s; + strprintf(&s, "", tariffs->GetStrError().c_str()); + answerList->push_back(s); + return; + } + } +answerList->push_back(""); +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/configuration/sgconfig/rsconf.cpp b/projects/stargazer/plugins/configuration/sgconfig/rsconf.cpp new file mode 100644 index 00000000..8ceb7e70 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/rsconf.cpp @@ -0,0 +1,647 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/******************************************************************* +* +* DESCRIPTION: æÁÊÌ Ó ÏÓÎÏ×ÎÙÍÉ ÆÕÎËÃÉÑÍÉ ÄÌÑ ÓÅÔÅ×ÏÇÏ ÏÂÍÅÎÁ ÄÁÎÎÙÍÉ +* Ó ÍÅÎÅÄÖÅÒÏÍ ËÌÉÅÎÔÏ×. ðÒÉÅÍ, ÐÅÒÅÄÁÞÁ É ÛÉÆÒÏ×ÁÎÉÅ ÓÏÏÂÝÅÎÉÊ. +* +* AUTHOR: Boris Mikhailenko +* +* $Revision: 1.24 $ +* $Date: 2010/10/04 20:24:54 $ +* +*******************************************************************/ + +/*#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include */ + +#include +#include + +#include "configproto.h" +#include "blowfish.h" + +enum CONF_STATE + { + confHdr, + confLogin, + confLoginCipher, + confData + }; + +enum + { + ans_ok = 0, + ans_err + }; + +//----------------------------------------------------------------------------- +int CONFIGPROTO::Prepare() +{ +list ansList; //óÀÄÁ ÂÕÄÅÔ ÐÏÍÅÝÅÎ ÏÔ×ÅÔ ÄÌÑ ÍÅÎÅÄÖÅÒÁ ËÌÉÅÎÔÏ× +int res; +struct sockaddr_in listenAddr; + +sigset_t sigmask, oldmask; +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +sigaddset(&sigmask, SIGTERM); +sigaddset(&sigmask, SIGUSR1); +sigaddset(&sigmask, SIGHUP); +pthread_sigmask(SIG_BLOCK, &sigmask, &oldmask); + +listenSocket = socket(PF_INET, SOCK_STREAM, 0); + +if (listenSocket < 0) + { + errorStr = "Create NET_CONFIGURATOR socket failed."; + return -1; + } + +listenAddr.sin_family = PF_INET; +listenAddr.sin_port = htons(port); +listenAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); + +int lng = 1; + +if (0 != setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &lng, 4)) + { + errorStr = "Setsockopt failed. " + string(strerror(errno)); + return -1; + } + +res = bind(listenSocket, (struct sockaddr*)&listenAddr, sizeof(listenAddr)); + +if (res == -1) + { + errorStr = "Bind admin socket failed"; + return -1; + } + +res = listen(listenSocket, 0); +if (res == -1) + { + errorStr = "Listen admin socket failed"; + return -1; + } +outerAddrLen = sizeof(outerAddr); + +/*if (0 != fcntl(listenSocket, F_SETFL, O_NONBLOCK)) + { + errorStr = "fcntl error!"; + return -1; + }*/ + +errorStr = ""; +nonstop = true; +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::Stop() +{ +nonstop = false; +close(listenSocket); +//TODO: Idiotism +int sock; +struct sockaddr_in addr; +socklen_t addrLen; +addr.sin_family = PF_INET; +addr.sin_port = htons(port); +addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + +addrLen = sizeof(outerAddr); +sock = socket(PF_INET, SOCK_STREAM, 0); +connect(sock, (sockaddr*)&addr, addrLen); +close(sock); +//Idiotism end +return 0; +} +//----------------------------------------------------------------------------- +// æÕÎËÃÉÑ ÏÂÝÅÎÉÑ Ó ËÏÎÆÉÇÕÒÁÔÏÒÏÍ +void * CONFIGPROTO::Run(void * a) +{ +/* + * Function Name:ReciveSendConf + * Parameters: void * a ÕËÁÚÁÔÅÌØ ÎÁ ÜËÚÅÍÐÌÑÒ ËÌÁÓÓÁ CONFIGPROTO + * Description: üÔÁ ÆÕÎËÃÉÑ ÏÂÅÓÐÅÞÉ×ÁÅÔ ÓÅÔÅ×ÏÅ ×ÚÁÉÍÏÄÅÊÓÔ×ÉÅ + * Ó íÅÎÅÄÖÅÒÏÍ ëÌÉÅÎÔÏ×. ÷ ÅÅ ÚÁÄÁÞÉ ×ÈÏÄÉÔ: ÐÒÉÅÍ ÚÁÐÒÏÓÏ× ÐÏ TCP/IP + * ÉÈ ÒÁÓÛÉÆÒÏ×ËÁ, ÐÅÒÅÄÁÞÁ ÐÒÉÎÑÔÙÈ ÄÁÎÎÙÈ ÁÎÁÌÉÚÁÔÏÒÕ É ÏÔÐÒÁ×ËÁ ÏÔ×ÅÔÁ ÎÁÚÁÄ. + * Returns: ×ÏÚ×ÒÁÝÁÅÔ NULL + */ + +CONFIGPROTO * cp = (CONFIGPROTO*)a; +cp->state = confHdr; + +while (cp->nonstop) + { + cp->state = confHdr; + cp->outerSocket = accept(cp->listenSocket, + (struct sockaddr*)(&cp->outerAddr), + &cp->outerAddrLen); + + if (!cp->nonstop) + { + continue; + } + + if (cp->outerSocket == -1) + { + printfd(__FILE__, "accept failed\n"); + usleep(100000); + continue; + } + + cp->adminIP = *(unsigned int*)&(cp->outerAddr.sin_addr); + + /* TODO + if (!cp->hostAllow->HostAllowed(cp->adminIP)) + { + close(outerSocket); + continue; + }*/ + + printfd(__FILE__, "Connection accepted from %s\n", inet_ntostring(cp->outerAddr.sin_addr.s_addr).c_str()); + + if (cp->state == confHdr) + { + if (cp->RecvHdr(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + if (cp->state == confLogin) + { + if (cp->SendHdrAnswer(cp->outerSocket, ans_ok) < 0) + { + close(cp->outerSocket); + continue; + } + + if (cp->RecvLogin(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + + if (cp->state == confLoginCipher) + { + if (cp->SendLoginAnswer(cp->outerSocket, ans_ok) < 0) + { + close(cp->outerSocket); + continue; + } + if (cp->RecvLoginS(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + + if (cp->state == confData) + { + if (cp->SendLoginSAnswer(cp->outerSocket, ans_ok) < 0) + { + close(cp->outerSocket); + continue; + } + if (cp->RecvData(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + cp->state = confHdr; + } + else + { + if (cp->SendLoginSAnswer(cp->outerSocket, ans_err) < 0) + { + close(cp->outerSocket); + continue; + } + cp->WriteLogAccessFailed(cp->adminIP); + } + } + else + { + cp->WriteLogAccessFailed(cp->adminIP); + } + } + else + { + cp->WriteLogAccessFailed(cp->adminIP); + if (cp->SendHdrAnswer(cp->outerSocket, ans_err) < 0) + { + close(cp->outerSocket); + continue; + } + } + } + else + { + cp->WriteLogAccessFailed(cp->adminIP); + } + close(cp->outerSocket); + } + +return NULL; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvHdr(int sock) +{ +char buf[sizeof(STG_HEADER)]; +memset(buf, 0, sizeof(STG_HEADER)); +int ret; +int stgHdrLen = strlen(STG_HEADER); +for (int i = 0; i < stgHdrLen; i++) + { + ret = recv(sock, &buf[i], 1, 0); + if (ret <= 0) + { + state = confHdr; + return -1; + } + } + +if (0 == strncmp(buf, STG_HEADER, strlen(STG_HEADER))) + { + state = confLogin; + return 0; + } +else + { + SendError("Bad request"); + } + +state = confHdr; +return -1; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendHdrAnswer(int sock, int err) +{ +int ret; + +if (err) + { + ret = send(sock, ERR_HEADER, sizeof(ERR_HEADER)-1, 0); + if (ret < 0) + { + WriteServLog("send ERR_HEADER error in SendHdrAnswer."); + return -1; + } + } +else + { + ret = send(sock, OK_HEADER, sizeof(OK_HEADER)-1, 0); + if (ret < 0) + { + WriteServLog("send OK_HEADER error in SendHdrAnswer."); + return -1; + } + } + +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvLogin(int sock) +{ +char login[ADM_LOGIN_LEN+1]; +int ret; + +memset(login, 0, ADM_LOGIN_LEN + 1); + +//printfd(__FILE__, "RecvLogin\n"); + +/*for (int i = 0; i < ADM_LOGIN_LEN; i++) + { + ret = recv(sock, &login[i], 1, 0); + + if (ret <= 0) + { + close(sock); + state = confHdr; + return ENODATA; + } + }*/ + +ret = recv(sock, login, ADM_LOGIN_LEN, 0); + +if (ret < 0) + { + // Error in network + close(sock); + state = confHdr; + return ENODATA; + } + +if (ret < ADM_LOGIN_LEN) + { + // Error in protocol + close(sock); + state = confHdr; + return ENODATA; + } + +if (admins->FindAdmin(login, &currAdmin)) + { + // Admin not found + close(sock); + state = confHdr; + return ENODATA; + } +currAdmin.SetAdminIP(adminIP); +adminLogin = login; +state = confLoginCipher; +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendLoginAnswer(int sock, int) +{ +int ret; + +ret = send(sock, OK_LOGIN, sizeof(OK_LOGIN)-1, 0); +if (ret < 0) + { + WriteServLog("Send OK_LOGIN error in SendLoginAnswer."); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvLoginS(int sock) +{ +char loginS[ADM_LOGIN_LEN + 1]; +char login[ADM_LOGIN_LEN + 1]; +int ret; +BLOWFISH_CTX ctx; +memset(loginS, 0, ADM_LOGIN_LEN + 1); + +//printfd(__FILE__, "RecvLoginS\n"); + +/*for (int i = 0; i < ADM_LOGIN_LEN; i++) + { + ret = recv(sock, &loginS[i], 1, 0); + + if (ret <= 0) + { + //printfd(__FILE__, "RecvLoginS close\n"); + close(sock); + state = confHdr; + return ENODATA; + } + }*/ + +int total = 0; + +while (total < ADM_LOGIN_LEN) + { + ret = recv(sock, &loginS[total], ADM_LOGIN_LEN - total, 0); + + if (ret < 0) + { + // Network error + printfd(__FILE__, "recv error: '%s'\n", strerror(errno)); + close(sock); + state = confHdr; + return ENODATA; + } + + total += ret; + } + +// TODO: implement select on socket +/*if (total < ADM_LOGIN_LEN) + { + // Protocol error + printfd(__FILE__, "Protocol error. Need %d bytes of cryptologin. Got %d bytes.\n", ADM_LOGIN_LEN, ret); + close(sock); + state = confHdr; + return ENODATA; + }*/ + +if (currAdmin.GetLogin() == "") + { + state = confHdr; + return ENODATA; + } + +EnDecodeInit(currAdmin.GetPassword().c_str(), ADM_PASSWD_LEN, &ctx); + +for (int i = 0; i < ADM_LOGIN_LEN/8; i++) + { + DecodeString(login + i*8, loginS + i*8, &ctx); + } + +if (currAdmin == admins->GetNoAdmin()) + { + // If there are no admins registered in the system - give access with any password + state = confData; + return 0; + } + +if (strncmp(currAdmin.GetLogin().c_str(), login, ADM_LOGIN_LEN) != 0) + { + state = confHdr; + return ENODATA; + } + +state = confData; +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendLoginSAnswer(int sock, int err) +{ +int ret; + +if (err) + { + ret = send(sock, ERR_LOGINS, sizeof(ERR_LOGINS)-1, 0); + if (ret < 0) + { + WriteServLog("send ERR_LOGIN error in SendLoginAnswer."); + return -1; + } + } +else + { + ret = send(sock, OK_LOGINS, sizeof(OK_LOGINS)-1, 0); + if (ret < 0) + { + WriteServLog("send OK_LOGINS error in SendLoginSAnswer."); + return -1; + } + } +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvData(int sock) +{ +//printfd(__FILE__, "RecvData\n"); +//int n = 0; +int ret; +char bufferS[8]; +char buffer[9]; + +buffer[8] = 0; + +requestList.clear(); +BLOWFISH_CTX ctx; + +EnDecodeInit(currAdmin.GetPassword().c_str(), ADM_PASSWD_LEN, &ctx); + +while (1) + { + /*ret = recv(sock, &bufferS[n++], 1, 0); + if (ret <= 0) + { + //printfd(__FILE__, "RecvData close\n"); + close(sock); + return 0; + }*/ + int total = 0; + bool done = false; + while (total < 8) + { + ret = recv(sock, &bufferS[total], 8 - total, 0); + if (ret < 0) + { + // Network error + close(sock); + return 0; + } + + if (ret < 8) + { + if (memchr(buffer, 0, ret) != NULL) + { + done = true; + break; + } + } + + total += ret; + } + + DecodeString(buffer, bufferS, &ctx); + requestList.push_back(std::string(buffer, total)); + + if (done || memchr(buffer, 0, total) != NULL) + { + // ëÏÎÅà ÐÏÓÙÌËÉ + if (ParseCommand()) + { + SendError("Bad command"); + } + return SendDataAnswer(sock); + } + + /*if (n == 8) + { + n = 0; + DecodeString(buffer, bufferS, &ctx); + requestList.push_back(std::string(buffer, 8)); + for (int j = 0; j < 8; j++) + { + if (buffer[j] == 0) + { + // ëÏÎÅà ÐÏÓÙÌËÉ + if (ParseCommand()) + { + SendError("Bad command"); + } + return SendDataAnswer(sock); + } + } + }*/ + } +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendDataAnswer(int sock) +{ +list::iterator li; +li = answerList.begin(); + +BLOWFISH_CTX ctx; + +char buff[8]; +char buffS[8]; +int n = 0; +int k = 0; +int ret = 0; + +EnDecodeInit(currAdmin.GetPassword().c_str(), ADM_PASSWD_LEN, &ctx); + +while (li != answerList.end()) + { + while ((*li).c_str()[k]) + { + buff[n%8] = (*li).c_str()[k]; + n++; + k++; + + if (n%8 == 0) + { + EncodeString(buffS, buff, &ctx); + ret = send(sock, buffS, 8, 0); + if (ret < 0) + { + return -1; + } + } + } + k = 0;// new node + li++; + } + +if (answerList.empty()) { + return 0; +} + +buff[n%8] = 0; +EncodeString(buffS, buff, &ctx); + +answerList.clear(); + +return send(sock, buffS, 8, 0); +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SendError(const char * text) +{ +char s[255]; +answerList.clear(); +sprintf(s, "", text); +answerList.push_back(s); +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::WriteLogAccessFailed(uint32_t ip) +{ +WriteServLog("Admin's connect failed. IP %s", inet_ntostring(ip).c_str()); +} +//----------------------------------------------------------------------------- + + + diff --git a/projects/stargazer/plugins/configuration/sgconfig/stgconfig.cpp b/projects/stargazer/plugins/configuration/sgconfig/stgconfig.cpp new file mode 100644 index 00000000..8ba4b0ff --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/stgconfig.cpp @@ -0,0 +1,245 @@ +#include +#include +#include + +#include "stgconfig.h" +#include "../../../tariffs.h" +#include "../../../admins.h" +#include "../../../users.h" + +class STGCONFIG_CREATOR +{ +private: + STG_CONFIG * stgconfig; + +public: + STGCONFIG_CREATOR() + : stgconfig(new STG_CONFIG()) + { + }; + ~STGCONFIG_CREATOR() + { + delete stgconfig; + }; + + STG_CONFIG * GetPlugin() + { + return stgconfig; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +STGCONFIG_CREATOR stgc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +STG_CONFIG_SETTINGS::STG_CONFIG_SETTINGS() + : port(0) +{ +} +//----------------------------------------------------------------------------- +const string& STG_CONFIG_SETTINGS::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int STG_CONFIG_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int STG_CONFIG_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +int p; +PARAM_VALUE pv; +vector::const_iterator pvi; +/////////////////////////// +pv.param = "Port"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Port\' not found."; + printfd(__FILE__, "Parameter 'Port' not found\n"); + return -1; + } +if (ParseIntInRange(pvi->value[0], 2, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +port = p; + +return 0; +} +//----------------------------------------------------------------------------- +uint16_t STG_CONFIG_SETTINGS::GetPort() +{ +return port; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return stgc.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string STG_CONFIG::GetVersion() const +{ +return "Stg configurator v.0.08"; +} +//----------------------------------------------------------------------------- +STG_CONFIG::STG_CONFIG() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void STG_CONFIG::SetUsers(USERS * u) +{ +users = u; +} +//----------------------------------------------------------------------------- +void STG_CONFIG::SetTariffs(TARIFFS * t) +{ +tariffs = t; +} +//----------------------------------------------------------------------------- +void STG_CONFIG::SetAdmins(ADMINS * a) +{ +admins = a; +} +//----------------------------------------------------------------------------- +void STG_CONFIG::SetStore(BASE_STORE * s) +{ +store = s; +} +//----------------------------------------------------------------------------- +void STG_CONFIG::SetStgSettings(const SETTINGS * s) +{ +stgSettings = s; +} +//----------------------------------------------------------------------------- +void STG_CONFIG::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int STG_CONFIG::ParseSettings() +{ +int ret = stgConfigSettings.ParseSettings(settings); +if (ret) + errorStr = stgConfigSettings.GetStrError(); +return ret; +} +//----------------------------------------------------------------------------- +const string & STG_CONFIG::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int STG_CONFIG::Start() +{ +if (isRunning) + return 0; + +nonstop = true; + +config.SetPort(stgConfigSettings.GetPort()); +config.SetAdmins(admins); +config.SetUsers(users); +config.SetTariffs(tariffs); +config.SetStgSettings(stgSettings); +config.SetStore(store); + +if (config.Prepare()) + { + errorStr = config.GetStrError(); + return -1; + } + +if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + printfd(__FILE__, "Cannot create thread\n"); + return -1; + } +errorStr = ""; +return 0; +} +//----------------------------------------------------------------------------- +int STG_CONFIG::Stop() +{ +if (!isRunning) + return 0; + +config.Stop(); + +//5 seconds to thread stops itself +int i; +for (i = 0; i < 25; i++) + { + if (!isRunning) + break; + + usleep(200000); + } + +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + //TODO pthread_cancel() + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return -1; + } + printfd(__FILE__, "STG_CONFIG killed\n"); + } + +return 0; +} +//----------------------------------------------------------------------------- +bool STG_CONFIG::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * STG_CONFIG::Run(void * d) +{ +STG_CONFIG * stgConf = (STG_CONFIG *)d; +stgConf->isRunning = true; + +stgConf->config.Run(&stgConf->config); + +stgConf->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t STG_CONFIG::GetStartPosition() const +{ +return 220; +} +//----------------------------------------------------------------------------- +uint16_t STG_CONFIG::GetStopPosition() const +{ +return 220; +} +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/plugins/configuration/sgconfig/stgconfig.h b/projects/stargazer/plugins/configuration/sgconfig/stgconfig.h new file mode 100644 index 00000000..57241c63 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig/stgconfig.h @@ -0,0 +1,104 @@ +#include +#include +#include "base_plugin.h" +#include "base_store.h" +#include "configproto.h" +//#include "user_ips.h" +//#include "../../../users.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +class STG_CONFIG; + +//----------------------------------------------------------------------------- +/*template +class CHG_BEFORE_NOTIFIER: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const varParamType & oldValue, const varParamType & newValue) + { + auth->Unauthorize(user); + } + void SetUser(USER * u) { user = u; } + void SetAuthorizaror(const AUTH_AO * a) { auth = a; } + +private: + USER * user; + const AUTH_AO * auth; +}; +//----------------------------------------------------------------------------- +template +class CHG_AFTER_NOTIFIER: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const varParamType & oldValue, const varParamType & newValue) + { + auth->UpdateUserAuthorization(user); + } + void SetUser(USER * u) { user = u; } + void SetAuthorizaror(const AUTH_AO * a) { auth = a; } + +private: + USER * user; + const AUTH_AO * auth; +};*/ +//----------------------------------------------------------------------------- +class STG_CONFIG_SETTINGS +{ +public: + STG_CONFIG_SETTINGS(); + virtual ~STG_CONFIG_SETTINGS(){}; + const string & GetStrError() const; + int ParseSettings(const MODULE_SETTINGS & s); + uint16_t GetPort(); +private: + int ParseIntInRange(const string & str, int min, int max, int * val); + string errorStr; + int port; +}; +//----------------------------------------------------------------------------- +class STG_CONFIG :public BASE_PLUGIN +{ +public: + STG_CONFIG(); + virtual ~STG_CONFIG(){}; + + void SetUsers(USERS * u); + void SetTariffs(TARIFFS * t); + void SetAdmins(ADMINS * a); + void SetStore(BASE_STORE * s); + void SetTraffcounter(TRAFFCOUNTER *){}; + void SetStgSettings(const SETTINGS * s); + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + static void * Run(void *); + mutable string errorStr; + STG_CONFIG_SETTINGS stgConfigSettings; + pthread_t thread; + bool nonstop; + bool isRunning; + CONFIGPROTO config; + USERS * users; + ADMINS * admins; + TARIFFS * tariffs; + BASE_STORE * store; + MODULE_SETTINGS settings; + const SETTINGS * stgSettings; +}; +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/Makefile b/projects/stargazer/plugins/configuration/sgconfig2/Makefile new file mode 100644 index 00000000..ab02cdff --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/Makefile @@ -0,0 +1,22 @@ +############################################################################### +# $Id: Makefile,v 1.9 2008/12/04 17:09:40 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_conf_sg2.so + +SRCS = ./stgconfig.cpp \ + ./rsconf.cpp \ + ./configproto.cpp \ + ./parser.cpp \ + ./parser_tariff.cpp \ + ./parser_admin.cpp + +LIBS += -lexpat \ + $(LIB_THREAD) + +STGLIBS = -lstg_common -lstg_logger + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/configproto.cpp b/projects/stargazer/plugins/configuration/sgconfig2/configproto.cpp new file mode 100644 index 00000000..b0255e1d --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/configproto.cpp @@ -0,0 +1,301 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.20 $ + $Date: 2009/10/20 11:53:40 $ + $Author: faust $ + */ + + +//#include +//#include +#include + +#include "configproto.h" + +//----------------------------------------------------------------------------- +void ParseXMLStart(void *data, const char *el, const char **attr) +{ +CONFIGPROTO * cp = (CONFIGPROTO*)(data); + +if (cp->currParser) + { + cp->currParser->SetAnswerList(&cp->answerList); + cp->currParser->SetCurrAdmin(cp->currAdmin); + cp->currParser->ParseStart(data, el, attr); + } +else + { + for (unsigned int i = 0; i < cp->dataParser.size(); i++) + { + cp->dataParser[i]->SetAnswerList(&cp->answerList); + cp->currAdmin->SetAdminIP(cp->GetAdminIP()); + cp->dataParser[i]->SetCurrAdmin(cp->currAdmin); + cp->dataParser[i]->Reset(); + if (cp->dataParser[i]->ParseStart(data, el, attr) == 0) + { + cp->currParser = cp->dataParser[i]; + break; + } + else + { + cp->dataParser[i]->Reset(); + } + } + } +} +//----------------------------------------------------------------------------- +void ParseXMLEnd(void *data, const char *el) +{ +CONFIGPROTO * cp = (CONFIGPROTO*)(data); +if (cp->currParser) + { + if (cp->currParser->ParseEnd(data, el) == 0) + { + cp->currParser = NULL; + } + } +else + { + for (unsigned int i = 0; i < cp->dataParser.size(); i++) + { + if (cp->dataParser[i]->ParseEnd(data, el) == 0) + { + break; + } + } + } +} +//----------------------------------------------------------------------------- +CONFIGPROTO::CONFIGPROTO() + : adminIP(0), + port(0), + nonstop(1), + state(0), + currAdmin(NULL), + WriteServLog(GetStgLogger()), + outerSocket(0), + listenSocket(0), + admins(NULL), + users(NULL), + tariffs(NULL), + store(NULL), + settings(NULL), + currParser(NULL) +{ +dataParser.push_back(&parserGetServInfo); + +dataParser.push_back(&parserGetUsers); +dataParser.push_back(&parserGetUser); +dataParser.push_back(&parserChgUser); +dataParser.push_back(&parserAddUser); +dataParser.push_back(&parserDelUser); +dataParser.push_back(&parserCheckUser); +dataParser.push_back(&parserSendMessage); + +dataParser.push_back(&parserGetTariffs); +dataParser.push_back(&parserAddTariff); +dataParser.push_back(&parserDelTariff); +dataParser.push_back(&parserChgTariff); + +dataParser.push_back(&parserGetAdmins); +dataParser.push_back(&parserChgAdmin); +dataParser.push_back(&parserDelAdmin); +dataParser.push_back(&parserAddAdmin); + +xmlParser = XML_ParserCreate(NULL); + +if (!xmlParser) + { + WriteServLog("Couldn't allocate memory for parser."); + exit(1); + } + +//XML_SetElementHandler(parser, ParseXMLStart, ParseXMLEnd); +} +//----------------------------------------------------------------------------- +CONFIGPROTO::~CONFIGPROTO() +{ +XML_ParserFree(xmlParser); +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::ParseCommand() +{ +list::iterator n; +int done = 0; +char str[9]; +int len; + +if (requestList.empty()) + return 0; + +n = requestList.begin(); + +strncpy(str, (*n).c_str(), 8); +str[8] = 0; + +XML_ParserReset(xmlParser, NULL); +XML_SetElementHandler(xmlParser, ParseXMLStart, ParseXMLEnd); +XML_SetUserData(xmlParser, this); + +while(nonstop) + { + strncpy(str, (*n).c_str(), 8); + str[8] = 0; + len = strlen(str); + + n++; + if (n == requestList.end()) + done = 1; + n--; + + if (XML_Parse(xmlParser, (*n).c_str(), len, done) == XML_STATUS_ERROR) + { + WriteServLog("Invalid configuration request"); + printfd(__FILE__, "Parse error at line %d:\n%s\n", + XML_GetCurrentLineNumber(xmlParser), + XML_ErrorString(XML_GetErrorCode(xmlParser))); + if (currParser) + { + printfd(__FILE__, "Parser reset\n"); + currParser->Reset(); + currParser = NULL; + } + + return -1; + } + + if (done) + return 0; + + n++; + } + +return 0; +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetPort(uint16_t p) +{ +port = p; +} +//----------------------------------------------------------------------------- +/*void CONFIGPROTO::SetHostAllow(HOSTALLOW *) +{ +//hostAllow = ha; +}*/ +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetAdmins(ADMINS * a) +{ +admins = a; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetAdmins(admins); + } + +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetUsers(USERS * u) +{ +users = u; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetUsers(users); + } + +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetTariffs(TARIFFS * t) +{ +tariffs = t; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetTariffs(tariffs); + } +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetStore(BASE_STORE * s) +{ +store = s; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetStore(s); + } +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SetStgSettings(const SETTINGS * s) +{ +settings = s; +for (unsigned int i = 0; i < dataParser.size(); i++) + { + dataParser[i]->SetStgSettings(settings); + } +} +//----------------------------------------------------------------------------- +/*void CONFIGPROTO::Start() +{ +finished = false; +threadExited = false; +status = starting; + +xmlParser = XML_ParserCreate(NULL); + +if (!xmlParser) + { + WriteServLog("Couldn't allocate memory for parser."); + } + +pthread_create(&thrReciveSendConf, NULL, ReciveSendConf, this); +status = started; +}*/ +//----------------------------------------------------------------------------- +/*int CONFIGPROTO::Stop() +{ +nonstop = true; +close(outerSocket); +return 0; +}*/ +//----------------------------------------------------------------------------- +/*void CONFIGPROTO::Restart() +{ +//Stop(); +//Start(); +}*/ +//----------------------------------------------------------------------------- +/*CONF_STATUS CONFIGPROTO::Status() +{ +//return status; +} +//----------------------------------------------------------------------------- +*/ +const string & CONFIGPROTO::GetStrError() +{ +return errorStr; +} +//----------------------------------------------------------------------------- +uint32_t CONFIGPROTO::GetAdminIP() +{ +return adminIP; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/configuration/sgconfig2/configproto.h b/projects/stargazer/plugins/configuration/sgconfig2/configproto.h new file mode 100644 index 00000000..71e546b4 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/configproto.h @@ -0,0 +1,144 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.12 $ + $Date: 2009/10/15 14:46:17 $ + $Author: faust $ + */ + + +#ifndef CONFIGPROTO_H +#define CONFIGPROTO_H + +#include + +#include +#include + +#include + +#include "parser.h" +#include "../../../users.h" +#include "../../../admins.h" +#include "../../../tariffs.h" +#include "stg_logger.h" + +using namespace std; + +#define STG_HEADER "SG04" +#define OK_HEADER "OKHD" +#define ERR_HEADER "ERHD" +#define OK_LOGIN "OKLG" +#define ERR_LOGIN "ERLG" +#define OK_LOGINS "OKLS" +#define ERR_LOGINS "ERLS" + +//----------------------------------------------------------------------------- +class CONFIGPROTO +{ +public: + CONFIGPROTO(); + ~CONFIGPROTO(); + + void SetPort(uint16_t port); + //void SetHostAllow(HOSTALLOW * ha); + void SetAdmins(ADMINS * a); + void SetUsers(USERS * u); + void SetTariffs(TARIFFS * t); + void SetStore(BASE_STORE * s); + void SetStgSettings(const SETTINGS * s); + const string & GetAdminLogin(); + uint32_t GetAdminIP(); + int Prepare(); + int Stop(); + const string & GetStrError(); + static void * Run(void * a); + +private: + int RecvHdr(int sock); + int RecvLogin(int sock); + int SendLoginAnswer(int sock, int err); + int SendHdrAnswer(int sock, int err); + int RecvLoginS(int sock); + int SendLoginSAnswer(int sock, int err); + int RecvData(int sock); + int SendDataAnswer(int sock); + void SendError(const char * text); + void WriteLogAccessFailed(uint32_t ip); + + int ParseCommand(); + + list answerList; + list requestList; + uint32_t adminIP; + string adminLogin; + uint16_t port; + pthread_t thrReciveSendConf; + bool nonstop; + int state; + //HOSTALLOW * hostAllow; + const ADMIN * currAdmin; + STG_LOGGER & WriteServLog; + + int outerSocket; + int listenSocket; + struct sockaddr_in outerAddr; + socklen_t outerAddrLen; + + PARSER_GET_SERVER_INFO parserGetServInfo; + + PARSER_GET_USERS parserGetUsers; + PARSER_GET_USER parserGetUser; + PARSER_CHG_USER parserChgUser; + PARSER_ADD_USER parserAddUser; + PARSER_DEL_USER parserDelUser; + PARSER_CHECK_USER parserCheckUser; + PARSER_SEND_MESSAGE parserSendMessage; + + PARSER_GET_ADMINS parserGetAdmins; + PARSER_ADD_ADMIN parserAddAdmin; + PARSER_DEL_ADMIN parserDelAdmin; + PARSER_CHG_ADMIN parserChgAdmin; + + PARSER_GET_TARIFFS parserGetTariffs; + PARSER_ADD_TARIFF parserAddTariff; + PARSER_DEL_TARIFF parserDelTariff; + PARSER_CHG_TARIFF parserChgTariff; + + ADMINS * admins; + USERS * users; + TARIFFS * tariffs; + BASE_STORE * store; + const SETTINGS * settings; + + BASE_PARSER * currParser; + vector dataParser; + + XML_Parser xmlParser; + + string errorStr; + + friend void ParseXMLStart(void *data, const char *el, const char **attr); + friend void ParseXMLEnd(void *data, const char *el); +}; +//----------------------------------------------------------------------------- +#endif //CONFIGPROTO_H + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/net_configurator.cpp b/projects/stargazer/plugins/configuration/sgconfig2/net_configurator.cpp new file mode 100644 index 00000000..6b8bc845 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/net_configurator.cpp @@ -0,0 +1,133 @@ +#include +#include "net_configurator.h" +#include "../../internal_configurator.h" + +//----------------------------------------------------------------------------- +const string & NET_CONFIGURATOR_SETTINGS::GetStrError() +{ +return strError; +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR_SETTINGS::ReadSettings(const CONFIGFILE &cf) +{ +if (cf.ReadUShortInt("AdminPort", &port, 5555) != 0) + { + strError = "Cannot read parameter AdminPort."; + return -1; + } +if (port < 1 || port > 65535) + { + strError = "Incorrect value AdminPort."; + return -1; + } + +string strOrder; +cf.ReadString("AdminOrder", &strOrder, "allow,deny"); +if (hostAllow.ParseOrder(strOrder.c_str()) < 0) + { + strError = string("Error in parameter AdminOrder. ") + hostAllow.GetStrError(); + return -1; + } + +string strAllow; +cf.ReadString("AdminAllowFrom", &strAllow, "all"); +if (hostAllow.ParseHosts(strAllow.c_str(), hostsAllow) != 0) + { + strError = string("Error in parameter AdminAllowFrom. ") + hostAllow.GetStrError(); + return -1; + } + +string strDeny; +cf.ReadString("AdminDenyFrom", &strDeny, ""); +if (hostAllow.ParseHosts(strDeny.c_str(), hostsDeny) != 0) + { + strError = string("Error in parameter AdminDenyFrom. ") + hostAllow.GetStrError(); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +uint16_t NET_CONFIGURATOR_SETTINGS::GetPort() +{ +return port; +} +//----------------------------------------------------------------------------- +HOSTALLOW * NET_CONFIGURATOR_SETTINGS::GetHostAllow() +{ +return &hostAllow; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +NET_CONFIGURATOR::NET_CONFIGURATOR() +{ +hostAllow = settings.GetHostAllow(); +} +//----------------------------------------------------------------------------- +NET_CONFIGURATOR::~NET_CONFIGURATOR() +{ + +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::SetStgConfigurator(BASE_INT_CONFIGURATOR * bsc) +{ +stgConfigurator = bsc; +cp.SetStgConfigurator(stgConfigurator); +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR::UserGetAll(string * login, + USER_CONF_RES * conf, + USER_STAT_RES * stat, + time_t lastUpdate) +{ +return 0; +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR::TatiffGetAll(TARIFF_CONF * conf) +{ +return 0; +} +//----------------------------------------------------------------------------- +int NET_CONFIGURATOR::AdminGetAll(ADMIN_CONF * conf) +{ +return 0; +} +//----------------------------------------------------------------------------- +const string & NET_CONFIGURATOR::GetStrError() +{ +return strError; +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::Start() +{ +cp.SetPort(settings.GetPort()); +cp.SetHostAllow(settings.GetHostAllow()); +cp.Start(); +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::Stop() +{ +cp.Stop(); +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::Restart() +{ +cp.Restart(); +} +//----------------------------------------------------------------------------- +CONF_STATUS NET_CONFIGURATOR::Status() +{ +return cp.Status(); +} +//----------------------------------------------------------------------------- +BASE_SETTINGS * NET_CONFIGURATOR::GetConfiguratorSettings() +{ +return &settings; +} +//----------------------------------------------------------------------------- +void NET_CONFIGURATOR::SetAdmins(const ADMINS * a) +{ +cp.SetAdmins(a); +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/net_configurator.h b/projects/stargazer/plugins/configuration/sgconfig2/net_configurator.h new file mode 100644 index 00000000..ec1348f0 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/net_configurator.h @@ -0,0 +1,65 @@ + /* + $Revision: 1.2 $ + $Date: 2005/10/30 21:34:28 $ + */ + +#ifndef NET_CONFIGURATOR_H +#define NET_CONFIGURATOR_H + +#include +#include + +#include "../../base_ext_configurator.h" +#include "../../base_int_configurator.h" +#include "../../base_settings.h" +#include "hostallow.h" +#include "conffiles.h" +#include "configproto.h" + +using namespace std; +//----------------------------------------------------------------------------- +class NET_CONFIGURATOR_SETTINGS: public BASE_SETTINGS +{ +public: + virtual ~NET_CONFIGURATOR_SETTINGS(){}; +virtual const string & GetStrError(); + virtual int ReadSettings(const CONFIGFILE & cf); + uint16_t GetPort(); + HOSTALLOW * GetHostAllow(); + +private: + string strError; + uint16_t port; + HOSTALLOW hostAllow; +}; +//----------------------------------------------------------------------------- +class NET_CONFIGURATOR: public BASE_EXT_CONFIGURATOR +{ +public: + NET_CONFIGURATOR(); + virtual ~NET_CONFIGURATOR(); + virtual void SetStgConfigurator(BASE_INT_CONFIGURATOR *); + virtual int UserGetAll(string * login, + USER_CONF_RES * conf, + USER_STAT_RES * stat, + time_t lastUpdate); + virtual int TatiffGetAll(TARIFF_CONF * conf); + virtual int AdminGetAll(ADMIN_CONF * conf); + virtual const string & GetStrError(); + virtual void Start(); + virtual void Stop(); + virtual void Restart(); + virtual CONF_STATUS Status(); + virtual BASE_SETTINGS * GetConfiguratorSettings(); + virtual void SetAdmins(const ADMINS * a); + +private: + HOSTALLOW * hostAllow; + BASE_INT_CONFIGURATOR * stgConfigurator; + NET_CONFIGURATOR_SETTINGS settings; + string strError; + CONFIGPROTO cp; +}; +//----------------------------------------------------------------------------- +#endif //NET_CONFIGURATOR_H + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/parser.cpp b/projects/stargazer/plugins/configuration/sgconfig2/parser.cpp new file mode 100644 index 00000000..95c20c0c --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/parser.cpp @@ -0,0 +1,1466 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "parser.h" +#include "version.h" + +#define UNAME_LEN (256) +//----------------------------------------------------------------------------- +// GET SERVER INFO +//----------------------------------------------------------------------------- +int PARSER_GET_SERVER_INFO::ParseStart(void *, const char *el, const char **) +{ +answerList->erase(answerList->begin(), answerList->end()); +if (strcasecmp(el, "GetServerInfo") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_SERVER_INFO::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetServerInfo") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::CreateAnswer() +{ +char s[UNAME_LEN + 128]; +char un[UNAME_LEN]; +struct utsname utsn; + +int tariff_type; + +tariff_type = 2; + +uname(&utsn); +un[0] = 0; + +strcat(un, utsn.sysname); +strcat(un, " "); +strcat(un, utsn.release); +strcat(un, " "); +strcat(un, utsn.machine); +strcat(un, " "); +strcat(un, utsn.nodename); + +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); +answerList->push_back(""); + +sprintf(s, "", SERVER_VERSION); +answerList->push_back(s); + +sprintf(s, "", tariffs->GetTariffsNum()); +answerList->push_back(s); + +sprintf(s, "", 2); +answerList->push_back(s); + +sprintf(s, "", users->GetUserNum()); +answerList->push_back(s); + +sprintf(s, "", un); +answerList->push_back(s); + +sprintf(s, "", DIR_NUM); +answerList->push_back(s); + +sprintf(s, "", settings->GetDayFee()); +answerList->push_back(s); + +for (int i = 0; i< DIR_NUM; i++) + { + string dn2e; + Encode12str(dn2e, settings->GetDirName(i)); + sprintf(s, "", i, dn2e.c_str()); + answerList->push_back(s); + } + +answerList->push_back(""); +} +//----------------------------------------------------------------------------- +// GET USER +//----------------------------------------------------------------------------- +PARSER_GET_USER::PARSER_GET_USER() +{ + +} +//----------------------------------------------------------------------------- +int PARSER_GET_USER::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "GetUser") == 0) + { + if (attr[0] && attr[1]) + login = attr[1]; + else + { + //login.clear(); + login.erase(login.begin(), login.end()); + return -1; + } + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_USER::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetUser") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USER::CreateAnswer() +{ +string s; +string enc; + +user_iter u; + +answerList->erase(answerList->begin(), answerList->end()); + +if (users->FindByName(login, &u)) + { + s = ""; + answerList->push_back(s); + return; + } + +s = ""; +answerList->push_back(s); + +s = "GetLogin() + "\"/>"; +answerList->push_back(s); + +if (currAdmin->GetPriv()->userConf || currAdmin->GetPriv()->userPasswd) + s = "property.password.Get() + "\" />"; +else + s = ""; +answerList->push_back(s); + +strprintf(&s, "", u->property.cash.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.freeMb.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.credit.Get()); +answerList->push_back(s); + +if (u->property.nextTariff.Get() != "") + { + strprintf(&s, "", + u->property.tariffName.Get().c_str(), + u->property.nextTariff.Get().c_str()); + } +else + { + strprintf(&s, "", + u->property.tariffName.Get().c_str()); + } + +answerList->push_back(s); + +Encode12str(enc, u->property.note); +s = ""; +answerList->push_back(s); + +Encode12str(enc, u->property.phone); +s = ""; +answerList->push_back(s); + +Encode12str(enc, u->property.address); +s = "
"; +answerList->push_back(s); + +Encode12str(enc, u->property.email); +s = ""; +answerList->push_back(s); + + +vector *> userdata; +userdata.push_back(u->property.userdata0.GetPointer()); +userdata.push_back(u->property.userdata1.GetPointer()); +userdata.push_back(u->property.userdata2.GetPointer()); +userdata.push_back(u->property.userdata3.GetPointer()); +userdata.push_back(u->property.userdata4.GetPointer()); +userdata.push_back(u->property.userdata5.GetPointer()); +userdata.push_back(u->property.userdata6.GetPointer()); +userdata.push_back(u->property.userdata7.GetPointer()); +userdata.push_back(u->property.userdata8.GetPointer()); +userdata.push_back(u->property.userdata9.GetPointer()); + +string tmpI; +for (unsigned i = 0; i < userdata.size(); i++) + { + Encode12str(enc, userdata[i]->Get()); + s = ""; + answerList->push_back(s); + } + +Encode12str(enc, u->property.realName); +s = ""; +answerList->push_back(s); + +Encode12str(enc, u->property.group); +s = ""; +answerList->push_back(s); + +strprintf(&s, "", u->GetConnected()); +answerList->push_back(s); + +strprintf(&s, "", u->property.alwaysOnline.Get()); +answerList->push_back(s); + +strprintf(&s, "", inet_ntostring(u->GetCurrIP()).c_str()); +answerList->push_back(s); + +strprintf(&s, "", u->GetPingTime()); +answerList->push_back(s); + +stringstream sstr; +sstr << u->property.ips.Get(); +strprintf(&s, "", sstr.str().c_str()); +answerList->push_back(s); + +char * ss; +ss = new char[DIR_NUM*25*4 + 50]; +char st[50]; +sprintf(ss, "property.down.Get(); +upload = u->property.up.Get(); + +for (int j = 0; j < DIR_NUM; j++) + { + string s; + x2str(upload[j], s); + sprintf(st, " MU%d=\"%s\"", j, s.c_str()); + strcat(ss, st); + + x2str(download[j], s); + sprintf(st, " MD%d=\"%s\"", j, s.c_str()); + strcat(ss, st); + + sprintf(st, " SU%d=\"0\"", j); + strcat(ss, st); + + sprintf(st, " SD%d=\"0\"", j); + strcat(ss, st); + } +strcat(ss, " />"); +answerList->push_back(ss); +delete[] ss; + +strprintf(&s, "", u->property.disabled.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.disabledDetailStat.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.passive.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.lastCashAdd.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.lastCashAddTime.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.lastActivityTime.Get()); +answerList->push_back(s); + +strprintf(&s, "", u->property.creditExpire.Get()); +answerList->push_back(s); + +strprintf(&s, ""); +answerList->push_back(s); +} +//----------------------------------------------------------------------------- +// GET USERS +//----------------------------------------------------------------------------- +PARSER_GET_USERS::PARSER_GET_USERS() +{ +lastUserUpdateTime = 0; +} +//----------------------------------------------------------------------------- +int PARSER_GET_USERS::ParseStart(void *, const char *el, const char ** attr) +{ +/*if (attr && *attr && *(attr+1)) + { + printfd(__FILE__, "attr=%s %s\n", *attr, *(attr+1)); + } +else + { + printfd(__FILE__, "attr = NULL\n"); + }*/ + +lastUpdateFound = false; +if (strcasecmp(el, "GetUsers") == 0) + { + while (attr && *attr && *(attr+1)) + { + if (strcasecmp(*attr, "LastUpdate") == 0) + { + if (str2x(*(attr+1), lastUserUpdateTime) == 0) + { + //printfd(__FILE__, "lastUserUpdateTime=%d\n", lastUserUpdateTime); + lastUpdateFound = true; + } + else + { + //printfd(__FILE__, "NO lastUserUpdateTime\n"); + } + } + ++attr; + } + + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_USERS::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetUsers") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::CreateAnswer() +{ +answerList->erase(answerList->begin(), answerList->end()); + +string s; +string userStart; +string traffStart; +string traffMiddle; +string traffFinish; +string middle; +string userFinish; + + +string enc; + +user_iter u; + +int h = users->OpenSearch(); +if (!h) + { + printfd(__FILE__, "users->OpenSearch() error\n"); + users->CloseSearch(h); + return; + } +string updateTime; +x2str(time(NULL), updateTime); + +if (lastUpdateFound) + answerList->push_back(""); +else + answerList->push_back(""); + +while (1) + { + if (users->SearchNext(h, &u)) + { + break; + } + userStart = "GetLogin() + "\">"; + middle = ""; + + if (u->property.password.ModificationTime() > lastUserUpdateTime) + { + if (currAdmin->GetPriv()->userConf || currAdmin->GetPriv()->userPasswd) + s = "property.password.Get() + "\" />"; + else + s = ""; + middle += s; + } + + + if (u->property.cash.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.cash.Get()); + middle += s; + //printfd(__FILE__, "cash value=\"%f\"\n", u->property.cash.Get()); + } + + + if (u->property.freeMb.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.freeMb.Get()); + middle += s; + } + + if (u->property.credit.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.credit.Get()); + middle += s; + } + + if (u->property.nextTariff.Get() != "") + { + if (u->property.tariffName.ModificationTime() > lastUserUpdateTime + || u->property.nextTariff.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", + u->property.tariffName.Get().c_str(), + u->property.nextTariff.Get().c_str()); + middle += s; + } + } + else + { + if (u->property.tariffName.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", + u->property.tariffName.Get().c_str()); + middle += s; + } + } + + if (u->property.note.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.note); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.phone.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.phone); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.address.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.address); + strprintf(&s, "
", enc.c_str()); + middle += s; + } + + if (u->property.email.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.email); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + vector *> userdata; + userdata.push_back(u->property.userdata0.GetPointer()); + userdata.push_back(u->property.userdata1.GetPointer()); + userdata.push_back(u->property.userdata2.GetPointer()); + userdata.push_back(u->property.userdata3.GetPointer()); + userdata.push_back(u->property.userdata4.GetPointer()); + userdata.push_back(u->property.userdata5.GetPointer()); + userdata.push_back(u->property.userdata6.GetPointer()); + userdata.push_back(u->property.userdata7.GetPointer()); + userdata.push_back(u->property.userdata8.GetPointer()); + userdata.push_back(u->property.userdata9.GetPointer()); + + string tmpI; + for (unsigned i = 0; i < userdata.size(); i++) + { + if (userdata[i]->ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, userdata[i]->Get()); + s = ""; + middle += s; + } + } + + if (u->property.realName.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.realName); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.group.ModificationTime() > lastUserUpdateTime) + { + Encode12str(enc, u->property.group); + strprintf(&s, "", enc.c_str()); + middle += s; + } + + if (u->property.alwaysOnline.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.alwaysOnline.Get()); + middle += s; + } + + if (u->GetCurrIPModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", inet_ntostring(u->GetCurrIP()).c_str()); + middle += s; + } + + + if (u->GetConnectedModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->GetConnected()); + middle += s; + } + + if (u->GetPingTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->GetPingTime()); + middle += s; + } + + if (u->property.ips.ModificationTime() > lastUserUpdateTime) + { + stringstream sstr; + sstr << u->property.ips.Get(); + strprintf(&s, "", sstr.str().c_str()); + middle += s; + } + + char st[50]; + traffStart = "property.down.Get(); + upload = u->property.up.Get(); + traffMiddle = ""; + + if (u->property.up.ModificationTime() > lastUserUpdateTime) + { + for (int j = 0; j < DIR_NUM; j++) + { + string s; + x2str(upload[j], s); + sprintf(st, " MU%d=\"%s\" ", j, s.c_str()); + traffMiddle += st; + } + } + + if (u->property.down.ModificationTime() > lastUserUpdateTime) + { + for (int j = 0; j < DIR_NUM; j++) + { + x2str(download[j], s); + sprintf(st, " MD%d=\"%s\" ", j, s.c_str()); + traffMiddle += st; + } + } + + traffFinish = " />"; + if (traffMiddle.length() > 0) + { + middle += traffStart; + middle += traffMiddle; + middle += traffFinish; + } + + if (u->property.disabled.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.disabled.Get()); + middle += s; + } + + if (u->property.disabledDetailStat.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.disabledDetailStat.Get()); + middle += s; + } + + //printfd(__FILE__, ">>>>> %s\n", s.c_str()); + + if (u->property.passive.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.passive.Get()); + middle += s; + } + + if (u->property.lastCashAdd.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.lastCashAdd.Get()); + middle += s; + } + + if (u->property.lastCashAddTime.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.lastCashAddTime.Get()); + middle += s; + } + + + if (u->property.lastActivityTime.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.lastActivityTime.Get()); + middle += s; + } + + if (u->property.creditExpire.ModificationTime() > lastUserUpdateTime) + { + strprintf(&s, "", u->property.creditExpire.Get()); + middle += s; + } + + + userFinish = ""; + + if (middle.length() > 0) + { + /*printfd(__FILE__, "login: %s\n", u->GetLogin().c_str()); + printfd(__FILE__, "middle: %s\n", middle.c_str());*/ + + answerList->push_back(userStart); + answerList->push_back(middle); + answerList->push_back(userFinish); + } + } + +users->CloseSearch(h); + +//answerList->push_back(""); + +answerList->push_back(""); +} +//----------------------------------------------------------------------------- +// ADD USER +//----------------------------------------------------------------------------- +PARSER_ADD_USER::PARSER_ADD_USER() +{ +depth = 0; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_USER::ParseStart(void *, const char *el, const char **attr) +{ +depth++; + +if (depth == 1) + { + if (strcasecmp(el, "AddUser") == 0) + { + return 0; + } + } +else + { + if (strcasecmp(el, "login") == 0) + { + login = attr[1]; + return 0; + } + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_USER::ParseEnd(void *, const char *el) +{ +if (depth == 1) + { + if (strcasecmp(el, "AddUser") == 0) + { + CreateAnswer(); + depth--; + return 0; + } + } + +depth--; +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_USER::Reset() +{ +BASE_PARSER::Reset(); +depth = 0; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_USER::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (CheckUserData() == 0) + { + answerList->push_back(""); + } +else + { + answerList->push_back(""); + } +} +//----------------------------------------------------------------------------- +int PARSER_ADD_USER::CheckUserData() +{ +user_iter u; +if (users->FindByName(login, &u)) + { + return users->Add(login, *currAdmin); + } +return -1; +} +//----------------------------------------------------------------------------- +// PARSER CHG USER +//----------------------------------------------------------------------------- +PARSER_CHG_USER::PARSER_CHG_USER() +{ +usr = NULL; +ucr = NULL; +upr = NULL; +downr = NULL; + +Reset(); +} +//----------------------------------------------------------------------------- +PARSER_CHG_USER::~PARSER_CHG_USER() +{ +delete usr; +delete ucr; +delete[] upr; +delete[] downr; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::Reset() +{ +depth = 0; +delete usr; + +delete ucr; + +delete[] upr; + +delete[] downr; + +usr = new USER_STAT_RES; +ucr = new USER_CONF_RES; + +upr = new RESETABLE[DIR_NUM]; +downr = new RESETABLE[DIR_NUM]; +}; +//----------------------------------------------------------------------------- +string PARSER_CHG_USER::EncChar2String(const char * strEnc) +{ +string str; +Decode21str(str, strEnc); +return str; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::ParseStart(void *, const char *el, const char **attr) +{ +depth++; + +if (depth == 1) + { + if (strcasecmp(el, "SetUser") == 0) + { + return 0; + } + } +else + { + //printfd(__FILE__, "el=%s\n", el); + if (strcasecmp(el, "login") == 0) + { + login = attr[1]; + return 0; + } + + if (strcasecmp(el, "ip") == 0) + { + try + { + ucr->ips = StrToIPS(attr[1]); + } + catch (...) + { + printfd(__FILE__, "StrToIPS Error!\n"); + } + } + + if (strcasecmp(el, "password") == 0) + { + ucr->password = attr[1]; + return 0; + } + + if (strcasecmp(el, "address") == 0) + { + ucr->address = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "aonline") == 0) + { + ucr->alwaysOnline = (*(attr[1]) != '0'); + return 0; + } + + if (strcasecmp(el, "cash") == 0) + { + if (attr[2] && (strcasecmp(attr[2], "msg") == 0)) + { + cashMsg = EncChar2String(attr[3]); + } + + double cash; + if (strtodouble2(attr[1], cash) == 0) + usr->cash = cash; + + if (strcasecmp(attr[0], "set") == 0) + cashMustBeAdded = false; + + if (strcasecmp(attr[0], "add") == 0) + cashMustBeAdded = true; + + return 0; + } + + if (strcasecmp(el, "CreditExpire") == 0) + { + long int creditExpire = 0; + if (str2x(attr[1], creditExpire) == 0) + ucr->creditExpire = (time_t)creditExpire; + + return 0; + } + + if (strcasecmp(el, "credit") == 0) + { + double credit; + if (strtodouble2(attr[1], credit) == 0) + ucr->credit = credit; + return 0; + } + + if (strcasecmp(el, "freemb") == 0) + { + double freeMb; + if (strtodouble2(attr[1], freeMb) == 0) + usr->freeMb = freeMb; + return 0; + } + + if (strcasecmp(el, "down") == 0) + { + int down = 0; + if (str2x(attr[1], down) == 0) + ucr->disabled = down; + return 0; + } + + if (strcasecmp(el, "DisableDetailStat") == 0) + { + int disabledDetailStat = 0; + if (str2x(attr[1], disabledDetailStat) == 0) + ucr->disabledDetailStat = disabledDetailStat; + return 0; + } + + if (strcasecmp(el, "email") == 0) + { + ucr->email = EncChar2String(attr[1]); + return 0; + } + + for (int i = 0; i < USERDATA_NUM; i++) + { + char name[15]; + sprintf(name, "userdata%d", i); + if (strcasecmp(el, name) == 0) + { + ucr->userdata[i] = EncChar2String(attr[1]); + return 0; + } + } + + if (strcasecmp(el, "group") == 0) + { + ucr->group = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "note") == 0) + { + ucr->note = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "passive") == 0) + { + int passive = 0; + if (str2x(attr[1], passive) == 0) + ucr->passive = passive; + return 0; + } + + if (strcasecmp(el, "phone") == 0) + { + ucr->phone = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "Name") == 0) + { + ucr->realName = EncChar2String(attr[1]); + return 0; + } + + if (strcasecmp(el, "traff") == 0) + { + int j = 0; + int dir; + DIR_TRAFF dtu; + DIR_TRAFF dtd; + unsigned long long t = 0; + while (attr[j]) + { + dir = attr[j][2] - '0'; + + if (strncasecmp(attr[j], "md", 2) == 0) + { + str2x(attr[j+1], t); + dtd[dir] = t; + downr[dir] = t; + } + if (strncasecmp(attr[j], "mu", 2) == 0) + { + str2x(attr[j+1], t); + dtu[dir] = t; + upr[dir] = t; + } + j+=2; + } + usr->down = dtd; + usr->up = dtu; + return 0; + } + + if (strcasecmp(el, "tariff") == 0) + { + if (strcasecmp(attr[0], "now") == 0) + ucr->tariffName = attr[1]; + + if (strcasecmp(attr[0], "delayed") == 0) + ucr->nextTariff = attr[1]; + + return 0; + } + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::ParseEnd(void *, const char *el) +{ +if (depth == 1) + { + if (strcasecmp(el, "SetUser") == 0) + { + AplayChanges(); + CreateAnswer(); + depth--; + return 0; + } + } + +depth--; +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +switch (res) + { + case 0: + answerList->push_back(""); + break; + case -1: + answerList->push_back(""); + break; + case -2: + answerList->push_back(""); + break; + default: + answerList->push_back(""); + break; + } + +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::CheckUserData() +{ +return true; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::AplayChanges() +{ +user_iter u; + +res = 0; +if (users->FindByName(login, &u)) + { + res = -1; + return -1; + } + +if (!ucr->ips.res_empty()) + if (!u->property.ips.Set(ucr->ips.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->address.res_empty()) + if (!u->property.address.Set(ucr->address.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->alwaysOnline.res_empty()) + if (!u->property.alwaysOnline.Set(ucr->alwaysOnline.const_data(), + currAdmin, login, store)) + res = -1; + +if (!ucr->creditExpire.res_empty()) + if (!u->property.creditExpire.Set(ucr->creditExpire.const_data(), + currAdmin, login, store)) + res = -1; + +if (!ucr->credit.res_empty()) + if (!u->property.credit.Set(ucr->credit.const_data(), currAdmin, login, store)) + res = -1; + +if (!usr->freeMb.res_empty()) + if (!u->property.freeMb.Set(usr->freeMb.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->disabled.res_empty()) + if (!u->property.disabled.Set(ucr->disabled.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->disabledDetailStat.res_empty()) + if (!u->property.disabledDetailStat.Set(ucr->disabledDetailStat.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->email.res_empty()) + if (!u->property.email.Set(ucr->email.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->group.res_empty()) + if (!u->property.group.Set(ucr->group.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->note.res_empty()) + if (!u->property.note.Set(ucr->note.const_data(), currAdmin, login, store)) + res = -1; + +vector *> userdata; +userdata.push_back(u->property.userdata0.GetPointer()); +userdata.push_back(u->property.userdata1.GetPointer()); +userdata.push_back(u->property.userdata2.GetPointer()); +userdata.push_back(u->property.userdata3.GetPointer()); +userdata.push_back(u->property.userdata4.GetPointer()); +userdata.push_back(u->property.userdata5.GetPointer()); +userdata.push_back(u->property.userdata6.GetPointer()); +userdata.push_back(u->property.userdata7.GetPointer()); +userdata.push_back(u->property.userdata8.GetPointer()); +userdata.push_back(u->property.userdata9.GetPointer()); + +for (int i = 0; i < (int)userdata.size(); i++) + { + if (!ucr->userdata[i].res_empty()) + { + if(!userdata[i]->Set(ucr->userdata[i].const_data(), currAdmin, login, store)) + res = -1; + } + } + +if (!ucr->passive.res_empty()) + if (!u->property.passive.Set(ucr->passive.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->password.res_empty()) + if (!u->property.password.Set(ucr->password.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->phone.res_empty()) + if (!u->property.phone.Set(ucr->phone.const_data(), currAdmin, login, store)) + res = -1; + +if (!ucr->realName.res_empty()) + if (!u->property.realName.Set(ucr->realName.const_data(), currAdmin, login, store)) + res = -1; + + +if (!usr->cash.res_empty()) + { + //if (currAdmin->GetPriv()->userCash) + { + if (cashMustBeAdded) + { + if (!u->property.cash.Set(usr->cash.const_data() + u->property.cash, + currAdmin, + login, + store, + cashMsg)) + res = -1; + } + else + { + if (!u->property.cash.Set(usr->cash.const_data(), currAdmin, login, store, cashMsg)) + res = -1; + } + } + } + + +if (!ucr->tariffName.res_empty()) + { + if (tariffs->FindByName(ucr->tariffName.const_data())) + { + if (!u->property.tariffName.Set(ucr->tariffName.const_data(), currAdmin, login, store)) + res = -1; + u->ResetNextTariff(); + } + else + { + //WriteServLog("SetUser: Tariff %s not found", ud.conf.tariffName.c_str()); + res = -1; + } + } + +if (!ucr->nextTariff.res_empty()) + { + if (tariffs->FindByName(ucr->nextTariff.const_data())) + { + if (!u->property.nextTariff.Set(ucr->nextTariff.const_data(), currAdmin, login, store)) + res = -1; + } + else + { + //WriteServLog("SetUser: Tariff %s not found", ud.conf.tariffName.c_str()); + res = -1; + } + } + +DIR_TRAFF up = u->property.up; +DIR_TRAFF down = u->property.down; +int upCount = 0; +int downCount = 0; +for (int i = 0; i < DIR_NUM; i++) + { + if (!upr[i].res_empty()) + { + up[i] = upr[i]; + upCount++; + } + if (!downr[i].res_empty()) + { + down[i] = downr[i]; + downCount++; + } + } + +if (upCount) + if (!u->property.up.Set(up, currAdmin, login, store)) + res = -1; + +if (downCount) + if (!u->property.down.Set(down, currAdmin, login, store)) + res = -1; + +/*if (!usr->down.res_empty()) + { + u->property.down.Set(usr->down.const_data(), currAdmin, login, store); + } +if (!usr->up.res_empty()) + { + u->property.up.Set(usr->up.const_data(), currAdmin, login, store); + }*/ + +u->WriteConf(); +u->WriteStat(); + +return 0; +} +//----------------------------------------------------------------------------- +// SEND MESSAGE +//----------------------------------------------------------------------------- +int PARSER_SEND_MESSAGE::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "Message") == 0) + { + for (int i = 0; i < 14; i++) + { + if (attr[i] == NULL) + { + result = res_params_error; + CreateAnswer(); + printfd(__FILE__, "To few parameters\n"); + return 0; + } + } + + for (int i = 0; i < 14; i+=2) + { + if (strcasecmp(attr[i], "login") == 0) + { + ParseLogins(attr[i+1]); + /*if (users->FindByName(login, &u)) + { + result = res_unknown; + break; + }*/ + } + + if (strcasecmp(attr[i], "MsgVer") == 0) + { + str2x(attr[i+1], msg.header.ver); + if (msg.header.ver != 1) + result = res_params_error; + } + + if (strcasecmp(attr[i], "MsgType") == 0) + { + str2x(attr[i+1], msg.header.type); + if (msg.header.type != 1) + result = res_params_error; + } + + if (strcasecmp(attr[i], "Repeat") == 0) + { + str2x(attr[i+1], msg.header.repeat); + if (msg.header.repeat < 0) + result = res_params_error; + } + + if (strcasecmp(attr[i], "RepeatPeriod") == 0) + { + str2x(attr[i+1], msg.header.repeatPeriod); + } + + if (strcasecmp(attr[i], "ShowTime") == 0) + { + str2x(attr[i+1], msg.header.showTime); + } + + if (strcasecmp(attr[i], "Text") == 0) + { + Decode21str(msg.text, attr[i+1]); + result = res_ok; + } + } + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_SEND_MESSAGE::ParseEnd(void *, const char *el) +{ +//MSG msg; +if (strcasecmp(el, "Message") == 0) + { + result = res_unknown; + for (unsigned i = 0; i < logins.size(); i++) + { + if (users->FindByName(logins[i], &u)) + { + printfd(__FILE__, "User not found. %s\n", logins[i].c_str()); + continue; + } + msg.header.creationTime = stgTime; + u->AddMessage(&msg); + result = res_ok; + } + /*if (result == res_ok) + { + if (strcmp(login, "*") == 0) + { + msg.text = text; + msg.prio = pri; + printfd(__FILE__, "SendMsg text: %s\n", text); + users->GetAllUsers(SendMessageAllUsers, &msg); + } + else + { + u->AddMessage(pri, text); + } + }*/ + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_SEND_MESSAGE::ParseLogins(const char * login) +{ +char * p; +char * l = new char[strlen(login) + 1]; +strcpy(l, login); +p = strtok(l, ":"); +logins.clear(); +while(p) + { + logins.push_back(p); + p = strtok(NULL, ":"); + } + +delete[] l; +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_SEND_MESSAGE::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); +//answerList->push_back(""); +// +switch (result) + { + case res_ok: + answerList->push_back(""); + break; + case res_params_error: + printfd(__FILE__, "res_params_error\n"); + answerList->push_back(""); + break; + case res_unknown: + printfd(__FILE__, "res_unknown\n"); + answerList->push_back(""); + break; + default: + printfd(__FILE__, "res_default\n"); + } + +} +//----------------------------------------------------------------------------- +// DEL USER +//----------------------------------------------------------------------------- +int PARSER_DEL_USER::ParseStart(void *, const char *el, const char **attr) +{ +res = 0; +if (strcasecmp(el, "DelUser") == 0) + { + if (attr[0] == NULL || attr[1] == NULL) + { + //CreateAnswer("Parameters error!"); + CreateAnswer(); + return 0; + } + + if (users->FindByName(attr[1], &u)) + { + res = 1; + CreateAnswer(); + return 0; + } + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_DEL_USER::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "DelUser") == 0) + { + if (!res) + users->Del(u->GetLogin(), *currAdmin); + + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_DEL_USER::CreateAnswer() +{ +if (res) + answerList->push_back(""); +else + answerList->push_back(""); +} +//----------------------------------------------------------------------------- +/*void PARSERDELUSER::CreateAnswer(char * mes) +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +char str[255]; +sprintf(str, "", mes); +answerList->push_back(str); +}*/ +//----------------------------------------------------------------------------- +// CHECK USER +// +//----------------------------------------------------------------------------- +int PARSER_CHECK_USER::ParseStart(void *, const char *el, const char **attr) +{ +result = false; + +if (strcasecmp(el, "CheckUser") == 0) + { + if (attr[0] == NULL || attr[1] == NULL + || attr[2] == NULL || attr[3] == NULL) + { + result = false; + CreateAnswer(); + printfd(__FILE__, "PARSER_CHECK_USER - attr err\n"); + return 0; + } + + user_iter user; + if (users->FindByName(attr[1], &user)) + { + result = false; + CreateAnswer(); + printfd(__FILE__, "PARSER_CHECK_USER - login err\n"); + return 0; + } + + if (strcmp(user->property.password.Get().c_str(), attr[3])) + { + result = false; + CreateAnswer(); + printfd(__FILE__, "PARSER_CHECK_USER - passwd err\n"); + return 0; + } + + result = true; + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHECK_USER::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "CheckUser") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHECK_USER::CreateAnswer() +{ +if (result) + answerList->push_back(""); +else + answerList->push_back(""); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/configuration/sgconfig2/parser.h b/projects/stargazer/plugins/configuration/sgconfig2/parser.h new file mode 100644 index 00000000..a7b87db6 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/parser.h @@ -0,0 +1,257 @@ + /* + $Revision: 1.18 $ + $Date: 2009/07/30 18:57:37 $ + $Author: nobunaga $ + */ + +#ifndef PARSER_H +#define PARSER_H + +#include +#include + +using namespace std; + +#include "resetable.h" +#include "stg_const.h" +#include "base_store.h" +#include "../../../admins.h" +#include "../../../users.h" +#include "stg_message.h" + +//----------------------------------------------------------------------------- +class BASE_PARSER +{ +public: + BASE_PARSER() + : admins(NULL), + users(NULL), + tariffs(NULL), + store(NULL), + settings(NULL), + currAdmin(NULL), + depth(0) + { }; + virtual ~BASE_PARSER(){}; + virtual int ParseStart(void *data, const char *el, const char **attr) = 0; + virtual int ParseEnd(void *data, const char *el) = 0; + virtual void CreateAnswer() = 0; + virtual void SetAnswerList(list * ansList){answerList = ansList;}; + + virtual void SetUsers(USERS * u){users = u;}; + virtual void SetAdmins(ADMINS * a){admins = a;}; + virtual void SetTariffs(TARIFFS * t){tariffs = t;}; + virtual void SetStore(BASE_STORE * s){store = s;}; + virtual void SetStgSettings(const SETTINGS * s){settings = s;}; + + virtual void SetCurrAdmin(const ADMIN * cua){currAdmin = cua;}; + virtual string & GetStrError(){return strError;}; + virtual void Reset(){ answerList->clear(); depth = 0; }; +protected: + string strError; + ADMINS * admins; + USERS * users; + TARIFFS * tariffs; + BASE_STORE * store; + const SETTINGS * settings; + const ADMIN * currAdmin; + int depth; + list * answerList; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_ADMINS: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +}; +//----------------------------------------------------------------------------- +class PARSER_ADD_ADMIN: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string adminToAdd; +}; +//----------------------------------------------------------------------------- +class PARSER_DEL_ADMIN: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + int CheckAttr(const char **attr); + string adminToDel; +}; +//----------------------------------------------------------------------------- +class PARSER_CHG_ADMIN: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + RESETABLE login; + RESETABLE password; + RESETABLE privAsString; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_SERVER_INFO: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +}; + +//----------------------------------------------------------------------------- +class PARSER_GET_USER: public BASE_PARSER +{ +public: + PARSER_GET_USER(); + ~PARSER_GET_USER(){}; + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string login; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_USERS: public BASE_PARSER +{ +public: + PARSER_GET_USERS(); + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + time_t lastUserUpdateTime; + bool lastUpdateFound; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_TARIFFS: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +}; +//----------------------------------------------------------------------------- +class PARSER_ADD_TARIFF: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string tariffToAdd; +}; +//----------------------------------------------------------------------------- +class PARSER_DEL_TARIFF: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + string tariffToDel; +}; +//----------------------------------------------------------------------------- +class PARSER_CHG_TARIFF: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + int ParseSlashedIntParams(int paramsNum, const string & s, int * params); + int ParseSlashedDoubleParams(int paramsNum, const string & s, double * params); + int CheckTariffData(); + int AplayChanges(); + + TARIFF_DATA_RES td; +}; +//-----------------------------------------------------------------------------/ +class PARSER_ADD_USER: public BASE_PARSER +{ +public: + PARSER_ADD_USER(); + virtual ~PARSER_ADD_USER(){}; + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); + void Reset(); +private: + int CheckUserData(); + string login; +}; +//----------------------------------------------------------------------------- +class PARSER_CHG_USER: public BASE_PARSER +{ +public: + PARSER_CHG_USER(); + ~PARSER_CHG_USER(); + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); + void Reset(); +private: + string EncChar2String(const char *); + + USER_STAT_RES * usr; + USER_CONF_RES * ucr; + RESETABLE * upr; + RESETABLE * downr; + string cashMsg; + string login; + + int CheckUserData(); + int AplayChanges(); + bool cashMustBeAdded; + int res; +}; +//----------------------------------------------------------------------------- +class PARSER_DEL_USER: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); + +private: + int res; + user_iter u; +}; +//----------------------------------------------------------------------------- +class PARSER_CHECK_USER: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + bool result; +}; +//----------------------------------------------------------------------------- +class PARSER_SEND_MESSAGE: public BASE_PARSER +{ +public: + int ParseStart(void *data, const char *el, const char **attr); + int ParseEnd(void *data, const char *el); + void CreateAnswer(); +private: + enum {res_ok, res_params_error, res_unknown}; + vector logins; + int ParseLogins(const char * logins); + int result; + STG_MSG msg; + user_iter u; +}; +//----------------------------------------------------------------------------- +#endif //PARSER_H + + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/parser_admin.cpp b/projects/stargazer/plugins/configuration/sgconfig2/parser_admin.cpp new file mode 100644 index 00000000..f232fb23 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/parser_admin.cpp @@ -0,0 +1,265 @@ +#include +#include + +#include "parser.h" + +//----------------------------------------------------------------------------- +// GET ADMINS +//----------------------------------------------------------------------------- +int PARSER_GET_ADMINS::ParseStart(void *, const char *el, const char **) +{ +if (strcasecmp(el, "GetAdmins") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_ADMINS::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetAdmins") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_ADMINS::CreateAnswer() +{ +const PRIV * priv = currAdmin->GetPriv(); +if (!priv->adminChg) + { + //answerList->clear(); + answerList->erase(answerList->begin(), answerList->end()); + + answerList->push_back(""); + return; + } + +string s; +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +answerList->push_back(""); +ADMIN_CONF ac; +int h = admins->OpenSearch(); + +unsigned int p; +while (admins->SearchNext(h, &ac) == 0) + { + //memcpy(&p, &ac.priv, sizeof(unsigned int)); + p = (ac.priv.userStat << 0) + + (ac.priv.userConf << 2) + + (ac.priv.userCash << 4) + + (ac.priv.userPasswd << 6) + + (ac.priv.userAddDel << 8) + + (ac.priv.adminChg << 10) + + (ac.priv.tariffChg << 12); + strprintf(&s, "", ac.login.c_str(), p); + answerList->push_back(s); + } +admins->CloseSearch(h); +answerList->push_back(""); +} +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// DEL ADMIN +//----------------------------------------------------------------------------- +int PARSER_DEL_ADMIN::ParseStart(void *, const char *el, const char **attr) +{ +strError = ""; +if (strcasecmp(el, "DelAdmin") == 0) + { + adminToDel = attr[1]; + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_DEL_ADMIN::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "DelAdmin") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_DEL_ADMIN::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (admins->Del(adminToDel, *currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", admins->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +int PARSER_DEL_ADMIN::CheckAttr(const char **attr) +{ +/* + * attr[0] = "login" (word login) + * attr[1] = login, value of login + * attr[2] = NULL */ + +if (strcasecmp(attr[0], "login") == 0 && attr[1] && !attr[2]) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +// ADD ADMIN +//----------------------------------------------------------------------------- +int PARSER_ADD_ADMIN::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "AddAdmin") == 0) + { + adminToAdd = attr[1]; + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_ADMIN::ParseEnd(void *, const char *el) +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (strcasecmp(el, "AddAdmin") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_ADMIN::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (admins->Add(adminToAdd, *currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", admins->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// CHG ADMIN +//----------------------------------------------------------------------------- +int PARSER_CHG_ADMIN::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "ChgAdmin") == 0) + { + for (int i = 0; i < 6; i+=2) + { + printfd(__FILE__, "PARSER_CHG_ADMIN::attr[%d] = %s\n", i, attr[i]); + if (attr[i] == NULL) + break; + + if (strcasecmp(attr[i], "Login") == 0) + { + login = attr[i + 1]; + continue; + } + + if (strcasecmp(attr[i], "Priv") == 0) + { + privAsString = attr[i + 1]; + continue; + } + + if (strcasecmp(attr[i], "Password") == 0) + { + password = attr[i + 1]; + continue; + } + } + + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_ADMIN::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "ChgAdmin") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_ADMIN::CreateAnswer() +{ +answerList->erase(answerList->begin(), answerList->end()); + +ADMIN_CONF conf; +conf.login = login; +if (!login.res_empty()) + { + string s; + //if (admins->FindAdmin(login.data()) != NULL) + // { + if (!password.res_empty()) + conf.password = password.data(); + + if (!privAsString.res_empty()) + { + int p = 0; + if (str2x(privAsString.data().c_str(), p) < 0) + { + strprintf(&s, "" ); + answerList->push_back(s); + return; + } + //memcpy(&conf.priv, &p, sizeof(conf.priv)); + conf.priv.userStat = (p & 0x0003) >> 0x00; // 1+2 + conf.priv.userConf = (p & 0x000C) >> 0x02; // 4+8 + conf.priv.userCash = (p & 0x0030) >> 0x04; // 10+20 + conf.priv.userPasswd = (p & 0x00C0) >> 0x06; // 40+80 + conf.priv.userAddDel = (p & 0x0300) >> 0x08; // 100+200 + conf.priv.adminChg = (p & 0x0C00) >> 0x0A; // 400+800 + conf.priv.tariffChg = (p & 0x3000) >> 0x0C; // 1000+2000 + } + + if (admins->Change(conf, *currAdmin) != 0) + { + strprintf(&s, "", admins->GetStrError().c_str()); + answerList->push_back(s); + } + else + { + answerList->push_back(""); + } + return; + // } + //strprintf(&s, "", admins->GetStrError().c_str()); + //answerList->push_back(s); + //return; + } +else + { + answerList->push_back(""); + } +} +//-----------------------------------------------------------------------------*/ + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/parser_tariff.cpp b/projects/stargazer/plugins/configuration/sgconfig2/parser_tariff.cpp new file mode 100644 index 00000000..29b44534 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/parser_tariff.cpp @@ -0,0 +1,489 @@ +#include +#include + +#include "parser.h" +//#include "admins.h" +//#include "tariff2.h" +const int pt_mega = 1024 * 1024; +//----------------------------------------------------------------------------- +// GET TARIFFS +//----------------------------------------------------------------------------- +int PARSER_GET_TARIFFS::ParseStart(void *, const char *el, const char **) +{ +if (strcasecmp(el, "GetTariffs") == 0) + { + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_GET_TARIFFS::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "GetTariffs") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_GET_TARIFFS::CreateAnswer() +{ +string s; +char vs[100]; +int hd, hn, md, mn; + +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +answerList->push_back(""); +int h = tariffs->OpenSearch(); + +TARIFF_DATA td; +while (tariffs->SearchNext(h, &td) == 0) + { + s = ""; + //printfd(__FILE__, "%s\n", s.c_str()); + answerList->push_back(s); + + for (int j = 0; j < DIR_NUM; j++) + { + hd = td.dirPrice[j].hDay; + md = td.dirPrice[j].mDay; + + hn = td.dirPrice[j].hNight; + mn = td.dirPrice[j].mNight; + + strprintf(&s, "", j, hd, md, hn, mn); + answerList->push_back(s); + } + + strprintf(&s, " "; + answerList->push_back(s); + + strprintf(&s, " "; + answerList->push_back(s); + + strprintf(&s, " "; + answerList->push_back(s); + + strprintf(&s, " "; + answerList->push_back(s); + + strprintf(&s, " "; + answerList->push_back(s); + + strprintf(&s, " "; + answerList->push_back(s); + + strprintf(&s, " "; + answerList->push_back(s); + + strprintf(&s, " ", td.tariffConf.fee); + answerList->push_back(s); + //printfd(__FILE__, "%s\n", s.c_str()); + + strprintf(&s, " ", td.tariffConf.passiveCost); + answerList->push_back(s); + + strprintf(&s, " ", td.tariffConf.free); + answerList->push_back(s); + + switch (td.tariffConf.traffType) + { + case TRAFF_UP: + answerList->push_back(""); + break; + case TRAFF_DOWN: + answerList->push_back(""); + break; + case TRAFF_UP_DOWN: + answerList->push_back(""); + break; + case TRAFF_MAX: + answerList->push_back(""); + break; + } + + answerList->push_back(""); + } +tariffs->CloseSearch(h); +answerList->push_back(""); +} +//----------------------------------------------------------------------------- +// ADD TARIFF +//----------------------------------------------------------------------------- +int PARSER_ADD_TARIFF::ParseStart(void *, const char *el, const char **attr) +{ +if (strcasecmp(el, "AddTariff") == 0) + { + if (attr[1]) + { + tariffToAdd = attr[1]; + } + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_ADD_TARIFF::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "AddTariff") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_ADD_TARIFF::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (tariffs->Add(tariffToAdd, *currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", tariffs->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +// DEL TARIFF +//----------------------------------------------------------------------------- +int PARSER_DEL_TARIFF::ParseStart(void *, const char *el, const char **attr) +{ +strError = ""; +if (strcasecmp(el, "DelTariff") == 0) + { + tariffToDel = attr[1]; + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_DEL_TARIFF::ParseEnd(void *, const char *el) +{ +if (strcasecmp(el, "DelTariff") == 0) + { + CreateAnswer(); + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_DEL_TARIFF::CreateAnswer() +{ +//answerList->clear(); +answerList->erase(answerList->begin(), answerList->end()); + +if (tariffs->Del(tariffToDel, *currAdmin) == 0) + { + answerList->push_back(""); + } +else + { + string s; + strprintf(&s, "", tariffs->GetStrError().c_str()); + answerList->push_back(s); + } +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// CHG TARIFF +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseSlashedIntParams(int paramsNum, const string & s, int * params) +{ +char * str = new char[s.size() + 1]; +char * p; +strcpy(str, s.c_str()); +p = strtok(str, "/"); + +for (int i = 0; i < paramsNum; i++) + { + if (p == NULL) + { + delete[] str; + return -1; + } + + if (str2x(p, params[i]) != 0) + { + delete[] str; + return -1; + } + + p = strtok(NULL, "/"); + } + +delete[] str; +return 0; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseSlashedDoubleParams(int paramsNum, const string & s, double * params) +{ +char * str = new char[s.size() + 1]; +char * p; +strcpy(str, s.c_str()); +p = strtok(str, "/"); + +for (int i = 0; i < paramsNum; i++) + { + if (p == NULL) + { + delete[] str; + return -1; + } + + if (strtodouble2(p, params[i]) != 0) + { + delete[] str; + return -1; + } + + p = strtok(NULL, "/"); + } + +delete[] str; +return 0; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseStart(void *, const char *el, const char **attr) +{ +char st[50]; +double price[DIR_NUM]; +int t[DIR_NUM]; +depth++; + +if (depth == 1) + { + if (strcasecmp(el, "SetTariff") == 0) + { + td.tariffConf.name = attr[1]; + return 0; + } + } +else + { + string s; + + if (strcasecmp(el, "PriceDayA") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceDayA = price[j] / pt_mega; + return 0; + } + + if (strcasecmp(el, "PriceDayB") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceDayB = price[j] / pt_mega; + return 0; + } + + + if (strcasecmp(el, "PriceNightA") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceNightA = price[j] / pt_mega; + return 0; + } + + if (strcasecmp(el, "PriceNightB") == 0) + { + s = attr[1]; + if (ParseSlashedDoubleParams(DIR_NUM, s, price) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].priceNightB = price[j] / pt_mega; + return 0; + } + + if (strcasecmp(el, "Threshold") == 0) + { + s = attr[1]; + if (ParseSlashedIntParams(DIR_NUM, s, t) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].threshold = t[j]; + return 0; + } + + if (strcasecmp(el, "SinglePrice") == 0) + { + s = attr[1]; + if (ParseSlashedIntParams(DIR_NUM, s, t) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].singlePrice = t[j]; + return 0; + } + + if (strcasecmp(el, "NoDiscount") == 0) + { + s = attr[1]; + if (ParseSlashedIntParams(DIR_NUM, s, t) == 0) + for (int j = 0; j < DIR_NUM; j++) + td.dirPrice[j].noDiscount = t[j]; + return 0; + } + + for (int j = 0; j < DIR_NUM; j++) + { + snprintf(st, 50, "Time%d", j); + if (strcasecmp(el, st) == 0) + { + int h1, m1, h2, m2; + if (ParseTariffTimeStr(attr[1], h1, m1, h2, m2) == 0) + { + td.dirPrice[j].hDay = h1; + td.dirPrice[j].mDay = m1; + td.dirPrice[j].hNight = h2; + td.dirPrice[j].mNight = m2; + } + return 0; + } + } + + if (strcasecmp(el, "Fee") == 0) + { + double fee; + if (strtodouble2(attr[1], fee) == 0) + td.tariffConf.fee = fee; + return 0; + } + + if (strcasecmp(el, "PassiveCost") == 0) + { + double pc; + if (strtodouble2(attr[1], pc) == 0) + td.tariffConf.passiveCost = pc; + return 0; + } + if (strcasecmp(el, "Free") == 0) + { + double free; + if (strtodouble2(attr[1], free) == 0) + td.tariffConf.free = free; + return 0; + } + + if (strcasecmp(el, "TraffType") == 0) + { + if (strcasecmp(attr[1], "up") == 0) + { + td.tariffConf.traffType = TRAFF_UP; + return 0; + } + + if (strcasecmp(attr[1], "down") == 0) + { + td.tariffConf.traffType = TRAFF_DOWN; + return 0; + } + if (strcasecmp(attr[1], "up+down") == 0) + { + td.tariffConf.traffType = TRAFF_UP_DOWN; + return 0; + } + if (strcasecmp(attr[1], "max") == 0) + { + td.tariffConf.traffType = TRAFF_MAX; + return 0; + } + return 0; + } + } +return -1; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_TARIFF::ParseEnd(void *, const char *el) +{ +if (depth == 1) + { + if (strcasecmp(el, "SetTariff") == 0) + { + CreateAnswer(); + depth--; + return 0; + } + } + +depth--; +return -1; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_TARIFF::CreateAnswer() +{ +answerList->erase(answerList->begin(), answerList->end()); + +if (!td.tariffConf.name.data().empty()) + { + TARIFF_DATA tariffData = td.GetData(); + if (tariffs->Chg(tariffData, *currAdmin) == 0) + { + answerList->push_back(""); + return; + } + else + { + string s; + strprintf(&s, "", tariffs->GetStrError().c_str()); + answerList->push_back(s); + return; + } + } +answerList->push_back(""); +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/proto.h b/projects/stargazer/plugins/configuration/sgconfig2/proto.h new file mode 100644 index 00000000..7dc4b50f --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/proto.h @@ -0,0 +1,61 @@ +#ifndef __PROTO_H__ +#define __PROTO_H__ + +namespace SGCONF2 { + +/* + * --- Protocol structure (binary part) --- + * + * Request: + * |---------------| + * |PROTOHEADER | + * |REQUESTHEADER | + * |---------------| + * | cryptodata | + * ~~~~~~~~~~~~~~~~~ + * |---------------| + * + * Response: + * |---------------| + * |PROTOHEADER | + * |RESPONSEHEADER | + * | error message | + * | cryptodata | + * ~~~~~~~~~~~~~~~~~ + * |---------------| + * + */ + + static char magic[8] = "STGCONF2"; + + enum RESPONSECODES { + E_OK = 0, // No error + E_NET_ERROR, // Network error (i.e. - timeout) + E_PROTO_ERROR, // Protocol error (invalid magic, unsupported version, etc.) + E_INVALID_LOGIN,// Invalid login + E_PERMISSIONS // Operation not permitted + }; + + struct PROTOHEADER { + char magic[8]; + uint32_t version; + }; + + struct REQUESTHEADER { + char login[32]; + }; + + struct CRYPTOHEADER { + char login[32]; + uint32_t dataSize; // Can't be 0 + }; + + struct RESPONSEHEADER { + uint32_t code; + uint32_t errorMessageSize; // May be 0 + uint32_t dataSize; // May be 0 + }; + +} + +#endif diff --git a/projects/stargazer/plugins/configuration/sgconfig2/rsconf.cpp b/projects/stargazer/plugins/configuration/sgconfig2/rsconf.cpp new file mode 100644 index 00000000..48af0e79 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/rsconf.cpp @@ -0,0 +1,651 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/******************************************************************* +* +* DESCRIPTION: æÁÊÌ Ó ÏÓÎÏ×ÎÙÍÉ ÆÕÎËÃÉÑÍÉ ÄÌÑ ÓÅÔÅ×ÏÇÏ ÏÂÍÅÎÁ ÄÁÎÎÙÍÉ +* Ó ÍÅÎÅÄÖÅÒÏÍ ËÌÉÅÎÔÏ×. ðÒÉÅÍ, ÐÅÒÅÄÁÞÁ É ÛÉÆÒÏ×ÁÎÉÅ ÓÏÏÂÝÅÎÉÊ. +* +* AUTHOR: Boris Mikhailenko +* +* $Revision: 1.20 $ +* $Date: 2009/10/31 15:44:02 $ +* +*******************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LINUX +#include +#endif + +#ifdef FREE_BSD +#include +#endif + +#ifdef FREE_BSD5 +#include +#endif + +extern int errno; + +#include "configproto.h" + +enum CONF_STATE + { + confHdr, + confLogin, + confLoginCipher, + confData + }; + +enum + { + ans_ok = 0, + ans_err + }; + +//----------------------------------------------------------------------------- +int CONFIGPROTO::Prepare() +{ +list ansList; //óÀÄÁ ÂÕÄÅÔ ÐÏÍÅÝÅÎ ÏÔ×ÅÔ ÄÌÑ ÍÅÎÅÄÖÅÒÁ ËÌÉÅÎÔÏ× +int res; +struct sockaddr_in listenAddr; + +sigset_t sigmask, oldmask; +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +sigaddset(&sigmask, SIGTERM); +sigaddset(&sigmask, SIGUSR1); +sigaddset(&sigmask, SIGHUP); +pthread_sigmask(SIG_BLOCK, &sigmask, &oldmask); + +listenSocket = socket(PF_INET, SOCK_STREAM, 0); + +if (listenSocket < 0) + { + errorStr = "Create NET_CONFIGURATOR socket failed."; + return -1; + } + +listenAddr.sin_family = PF_INET; +listenAddr.sin_port = htons(port); +listenAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); + +int lng = 1; + +if (0 != setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &lng, 4)) + { + errorStr = "Setsockopt failed. " + string(strerror(errno)); + return -1; + } + +res = bind(listenSocket, (struct sockaddr*)&listenAddr, sizeof(listenAddr)); + +if (res == -1) + { + errorStr = "Bind admin socket failed"; + return -1; + } + +res = listen(listenSocket, 0); +if (res == -1) + { + errorStr = "Listen admin socket failed"; + return -1; + } +outerAddrLen = sizeof(outerAddr); + +/*if (0 != fcntl(listenSocket, F_SETFL, O_NONBLOCK)) + { + errorStr = "fcntl error!"; + return -1; + }*/ + +errorStr = ""; +nonstop = true; +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::Stop() +{ +nonstop = false; +close(listenSocket); +//TODO: Idiotism +int sock; +struct sockaddr_in addr; +socklen_t addrLen; +addr.sin_family = PF_INET; +addr.sin_port = htons(port); +addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + +addrLen = sizeof(outerAddr); +sock = socket(PF_INET, SOCK_STREAM, 0); +connect(sock, (sockaddr*)&addr, addrLen); +close(sock); +//Idiotism end +return 0; +} +//----------------------------------------------------------------------------- +// æÕÎËÃÉÑ ÏÂÝÅÎÉÑ Ó ËÏÎÆÉÇÕÒÁÔÏÒÏÍ +void * CONFIGPROTO::Run(void * a) +{ +/* + * Function Name:ReciveSendConf + * Parameters: void * a ÕËÁÚÁÔÅÌØ ÎÁ ÜËÚÅÍÐÌÑÒ ËÌÁÓÓÁ CONFIGPROTO + * Description: üÔÁ ÆÕÎËÃÉÑ ÏÂÅÓÐÅÞÉ×ÁÅÔ ÓÅÔÅ×ÏÅ ×ÚÁÉÍÏÄÅÊÓÔ×ÉÅ + * Ó íÅÎÅÄÖÅÒÏÍ ëÌÉÅÎÔÏ×. ÷ ÅÅ ÚÁÄÁÞÉ ×ÈÏÄÉÔ: ÐÒÉÅÍ ÚÁÐÒÏÓÏ× ÐÏ TCP/IP + * ÉÈ ÒÁÓÛÉÆÒÏ×ËÁ, ÐÅÒÅÄÁÞÁ ÐÒÉÎÑÔÙÈ ÄÁÎÎÙÈ ÁÎÁÌÉÚÁÔÏÒÕ É ÏÔÐÒÁ×ËÁ ÏÔ×ÅÔÁ ÎÁÚÁÄ. + * Returns: ×ÏÚ×ÒÁÝÁÅÔ NULL + */ + +CONFIGPROTO * cp = (CONFIGPROTO*)a; +cp->state = confHdr; + +while (cp->nonstop) + { + cp->state = confHdr; + cp->outerSocket = accept(cp->listenSocket, + (struct sockaddr*)(&cp->outerAddr), + &cp->outerAddrLen); + + if (!cp->nonstop) + { + continue; + } + + if (cp->outerSocket == -1) + { + printfd(__FILE__, "accept failed\n"); + usleep(100000); + continue; + } + + cp->adminIP = *(unsigned int*)&(cp->outerAddr.sin_addr); + + /* TODO + if (!cp->hostAllow->HostAllowed(cp->adminIP)) + { + close(outerSocket); + continue; + }*/ + + printfd(__FILE__, "Connection accepted from %s\n", inet_ntostring(cp->outerAddr.sin_addr.s_addr).c_str()); + + if (cp->state == confHdr) + { + if (cp->RecvHdr(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + if (cp->state == confLogin) + { + if (cp->SendHdrAnswer(cp->outerSocket, ans_ok) < 0) + { + close(cp->outerSocket); + continue; + } + + if (cp->RecvLogin(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + + if (cp->state == confLoginCipher) + { + if (cp->SendLoginAnswer(cp->outerSocket, ans_ok) < 0) + { + close(cp->outerSocket); + continue; + } + if (cp->RecvLoginS(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + + if (cp->state == confData) + { + if (cp->SendLoginSAnswer(cp->outerSocket, ans_ok) < 0) + { + close(cp->outerSocket); + continue; + } + if (cp->RecvData(cp->outerSocket) < 0) + { + close(cp->outerSocket); + continue; + } + cp->state = confHdr; + } + else + { + if (cp->SendLoginSAnswer(cp->outerSocket, ans_err) < 0) + { + close(cp->outerSocket); + continue; + } + cp->WriteLogAccessFailed(cp->adminIP); + } + } + else + { + cp->WriteLogAccessFailed(cp->adminIP); + } + } + else + { + cp->WriteLogAccessFailed(cp->adminIP); + if (cp->SendHdrAnswer(cp->outerSocket, ans_err) < 0) + { + close(cp->outerSocket); + continue; + } + } + } + else + { + cp->WriteLogAccessFailed(cp->adminIP); + } + close(cp->outerSocket); + } + +return NULL; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvHdr(int sock) +{ +char buf[sizeof(STG_HEADER)]; +memset(buf, 0, sizeof(STG_HEADER)); +int ret; +int stgHdrLen = strlen(STG_HEADER); +for (int i = 0; i < stgHdrLen; i++) + { + ret = recv(sock, &buf[i], 1, 0); + if (ret <= 0) + { + state = confHdr; + return -1; + } + } + +if (0 == strncmp(buf, STG_HEADER, strlen(STG_HEADER))) + { + state = confLogin; + return 0; + } +else + { + SendError("Bad request"); + } + +state = confHdr; +return -1; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendHdrAnswer(int sock, int err) +{ +int ret; + +if (err) + { + ret = send(sock, ERR_HEADER, sizeof(ERR_HEADER)-1, 0); + if (ret < 0) + { + WriteServLog("send ERR_HEADER error in SendHdrAnswer."); + return -1; + } + } +else + { + ret = send(sock, OK_HEADER, sizeof(OK_HEADER)-1, 0); + if (ret < 0) + { + WriteServLog("send OK_HEADER error in SendHdrAnswer."); + return -1; + } + } + +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvLogin(int sock) +{ +char login[ADM_LOGIN_LEN+1]; +int ret; + +memset(login, 0, ADM_LOGIN_LEN + 1); + +//printfd(__FILE__, "RecvLogin\n"); + +/*for (int i = 0; i < ADM_LOGIN_LEN; i++) + { + ret = recv(sock, &login[i], 1, 0); + + if (ret <= 0) + { + close(sock); + state = confHdr; + return ENODATA; + } + }*/ + +ret = recv(sock, login, ADM_LOGIN_LEN, 0); + +if (ret < 0) + { + // Error in network + close(sock); + state = confHdr; + return ENODATA; + } + +if (ret < ADM_LOGIN_LEN) + { + // Error in protocol + close(sock); + state = confHdr; + return ENODATA; + } + +currAdmin = admins->FindAdmin(login); +adminLogin = login; +state = confLoginCipher; +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendLoginAnswer(int sock, int) +{ +int ret; + +ret = send(sock, OK_LOGIN, sizeof(OK_LOGIN)-1, 0); +if (ret < 0) + { + WriteServLog("Send OK_LOGIN error in SendLoginAnswer."); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvLoginS(int sock) +{ +char loginS[ADM_LOGIN_LEN + 1]; +char login[ADM_LOGIN_LEN + 1]; +int ret; +BLOWFISH_CTX ctx; +memset(loginS, 0, ADM_LOGIN_LEN + 1); + +//printfd(__FILE__, "RecvLoginS\n"); + +/*for (int i = 0; i < ADM_LOGIN_LEN; i++) + { + ret = recv(sock, &loginS[i], 1, 0); + + if (ret <= 0) + { + //printfd(__FILE__, "RecvLoginS close\n"); + close(sock); + state = confHdr; + return ENODATA; + } + }*/ + +int total = 0; + +while (total < ADM_LOGIN_LEN) + { + ret = recv(sock, &loginS[total], ADM_LOGIN_LEN - total, 0); + + if (ret < 0) + { + // Network error + printfd(__FILE__, "recv error: '%s'\n", strerror(errno)); + close(sock); + state = confHdr; + return ENODATA; + } + + total += ret; + } + +// TODO: implement select on socket +/*if (total < ADM_LOGIN_LEN) + { + // Protocol error + printfd(__FILE__, "Protocol error. Need %d bytes of cryptologin. Got %d bytes.\n", ADM_LOGIN_LEN, ret); + close(sock); + state = confHdr; + return ENODATA; + }*/ + +if (currAdmin == NULL) + { + state = confHdr; + return ENODATA; + } + +EnDecodeInit(currAdmin->GetPassword().c_str(), ADM_PASSWD_LEN, &ctx); + +for (int i = 0; i < ADM_LOGIN_LEN/8; i++) + { + DecodeString(login + i*8, loginS + i*8, &ctx); + } + +if (currAdmin == admins->GetNoAdmin()) + { + // If there are no admins registered in the system - give access with any password + state = confData; + return 0; + } + +if (strncmp(currAdmin->GetLogin().c_str(), login, ADM_LOGIN_LEN) != 0) + { + state = confHdr; + return ENODATA; + } + +state = confData; +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendLoginSAnswer(int sock, int err) +{ +int ret; + +if (err) + { + ret = send(sock, ERR_LOGINS, sizeof(ERR_LOGINS)-1, 0); + if (ret < 0) + { + WriteServLog("send ERR_LOGIN error in SendLoginAnswer."); + return -1; + } + } +else + { + ret = send(sock, OK_LOGINS, sizeof(OK_LOGINS)-1, 0); + if (ret < 0) + { + WriteServLog("send OK_LOGINS error in SendLoginSAnswer."); + return -1; + } + } +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::RecvData(int sock) +{ +//printfd(__FILE__, "RecvData\n"); +int n = 0; +int ret; +char bufferS[8]; +char buffer[9]; + +buffer[8] = 0; + +requestList.clear(); +BLOWFISH_CTX ctx; + +EnDecodeInit(currAdmin->GetPassword().c_str(), ADM_PASSWD_LEN, &ctx); + +while (1) + { + /*ret = recv(sock, &bufferS[n++], 1, 0); + if (ret <= 0) + { + //printfd(__FILE__, "RecvData close\n"); + close(sock); + return 0; + }*/ + int total = 0; + bool done = false; + while (total < 8) + { + ret = recv(sock, &bufferS[total], 8 - total, 0); + if (ret < 0) + { + // Network error + close(sock); + return 0; + } + + if (ret < 8) + { + if (memchr(buffer, 0, ret) != NULL) + { + done = true; + break; + } + } + + total += ret; + } + + DecodeString(buffer, bufferS, &ctx); + requestList.push_back(std::string(buffer, total)); + + if (done || memchr(buffer, 0, total) != NULL) + { + // ëÏÎÅà ÐÏÓÙÌËÉ + if (ParseCommand()) + { + SendError("Bad command"); + } + return SendDataAnswer(sock); + } + + /*if (n == 8) + { + n = 0; + DecodeString(buffer, bufferS, &ctx); + requestList.push_back(std::string(buffer, 8)); + for (int j = 0; j < 8; j++) + { + if (buffer[j] == 0) + { + // ëÏÎÅà ÐÏÓÙÌËÉ + if (ParseCommand()) + { + SendError("Bad command"); + } + return SendDataAnswer(sock); + } + } + }*/ + } +return 0; +} +//----------------------------------------------------------------------------- +int CONFIGPROTO::SendDataAnswer(int sock) +{ +list::iterator li; +li = answerList.begin(); + +BLOWFISH_CTX ctx; + +char buff[8]; +char buffS[8]; +int n = 0; +int k = 0; +int ret = 0; + +EnDecodeInit(currAdmin->GetPassword().c_str(), ADM_PASSWD_LEN, &ctx); + +while (li != answerList.end()) + { + while ((*li).c_str()[k]) + { + buff[n%8] = (*li).c_str()[k]; + n++; + k++; + + if (n%8 == 0) + { + EncodeString(buffS, buff, &ctx); + ret = send(sock, buffS, 8, 0); + if (ret < 0) + { + return -1; + } + } + } + k = 0;// new node + li++; + } + +if (answerList.empty()) { + return 0; +} + +buff[n%8] = 0; +EncodeString(buffS, buff, &ctx); + +answerList.clear(); + +return send(sock, buffS, 8, 0); +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::SendError(const char * text) +{ +char s[255]; +answerList.clear(); +sprintf(s, "", text); +answerList.push_back(s); +} +//----------------------------------------------------------------------------- +void CONFIGPROTO::WriteLogAccessFailed(uint32_t ip) +{ +WriteServLog("Admin's connect failed. IP %s", inet_ntostring(ip).c_str()); +} +//----------------------------------------------------------------------------- + + + diff --git a/projects/stargazer/plugins/configuration/sgconfig2/stgconfig.cpp b/projects/stargazer/plugins/configuration/sgconfig2/stgconfig.cpp new file mode 100644 index 00000000..cec02d41 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/stgconfig.cpp @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include + +#include "stgconfig.h" +#include "../../../tariffs.h" +#include "../../../admins.h" +#include "../../../users.h" + +class STGCONFIG_CREATOR +{ +private: + STG_CONFIG * stgconfig; + +public: + STGCONFIG_CREATOR() + : stgconfig(new STG_CONFIG()) + { + }; + ~STGCONFIG_CREATOR() + { + delete stgconfig; + }; + + STG_CONFIG * GetPlugin() + { + return stgconfig; + }; +}; + +STGCONFIG_CREATOR stgc; + +BASE_PLUGIN * GetPlugin() +{ +return stgc.GetPlugin(); +} + +STG_CONFIG_SETTINGS::STG_CONFIG_SETTINGS() + : port(0) +{ +} + +const string& STG_CONFIG_SETTINGS::GetStrError() const +{ +return errorStr; +} + +int STG_CONFIG_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} + +int STG_CONFIG_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +int p; +PARAM_VALUE pv; +vector::const_iterator pvi; +/////////////////////////// +pv.param = "Port"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Port\' not found."; + printfd(__FILE__, "Parameter 'Port' not found\n"); + return -1; + } +if (ParseIntInRange(pvi->value[0], 2, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +port = p; + +return 0; +} + +uint16_t STG_CONFIG_SETTINGS::GetPort() +{ +return port; +} + +STG_CONFIG::STG_CONFIG() + : running(false), + stopped(true) +{ +} + +string STG_CONFIG::GetVersion() const +{ +return "Stg configurator v.2.00"; +} + +int STG_CONFIG::ParseSettings() +{ +int ret = stgConfigSettings.ParseSettings(settings); +if (ret) + errorStr = stgConfigSettings.GetStrError(); +return ret; +} + +int STG_CONFIG::Start() +{ +if (running) + return false; + +if (PrepareNetwork()) + return true; + +stopped = false; + +config.SetPort(stgConfigSettings.GetPort()); +config.SetAdmins(admins); +config.SetUsers(users); +config.SetTariffs(tariffs); +config.SetStgSettings(stgSettings); +config.SetStore(store); + +if (config.Prepare()) + { + errorStr = config.GetStrError(); + return true; + } + +if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + printfd(__FILE__, "Cannot create thread\n"); + return true; + } + +errorStr = ""; +return false; +} + +int STG_CONFIG::Stop() +{ +if (!running) + return false; + +running = false; + +config.Stop(); + +//5 seconds to thread stops itself +int i; +for (i = 0; i < 25 && !stopped; i++) + { + usleep(200000); + } + +//after 5 seconds waiting thread still running. now killing it +if (!stopped) + { + //TODO pthread_cancel() + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return FinalizeNetwork(); + } + printfd(__FILE__, "STG_CONFIG killed\n"); + } + +return FinalizeNetwork(); +} + +void * STG_CONFIG::Run(void * d) +{ +STG_CONFIG * stgConf = static_cast(d); +stgConf->running = true; + +stgConf->RealRun(); + +stgConf->stopped = true; +return NULL; +} + +uint16_t STG_CONFIG::GetStartPosition() const +{ +return 220; +} + +uint16_t STG_CONFIG::GetStopPosition() const +{ +return 220; +} + +bool PrepareNetwork() +{ +struct sockaddr_in local; + +local.sin_family = AF_INET; +local.sin_port = htons(port); +local.sin_addr.s_addr = INADDR_ANY; + +sd = socket(AF_INET, SOCK_STREAM, 0); +if (sd < 0) + { + errorStr = "Error creating socket: '"; + errorStr += strerror(errno); + errorStr += "'"; + return true; + } + +if (bind(sd, static_cast(&local), sizeof(local)) < 0) + { + errorStr = "Error binding socket: '"; + errorStr += strerror(errno); + errorStr += "'"; + return true; + } + +return false; +} + +bool FinalizeNetwork() +{ +if (close(sd) < 0) + { + errorStr = "Error closing socket: '"; + errorStr += strerror(errno); + errorStr += "'"; + return true; + } +return false; +} + +void STG_CONFIG::RealRun() +{ +if (listen(sd, 64) < 0) + { + errorStr = "Error listening socket: '"; + errorStr += strerror(errno); + errorStr += "'"; + return; + } + +fd_set rfds; + +FD_ZERO(&rfds); +FD_SET(sd, &rfds); + +running = true; +while (running) + { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500000; + + int res = select(sd + 1, &rfds, NULL, NULL, &tv); + + if (res < 0) + { + // Error logging + } + else if (res == 0) + { + // Timeout + } + else + { + if (FD_ISSET(sd, &rfds)) + { + AcceptConnection(); + } + } + + // Reorder: right part is done + std::list::iterator done( + std::remove_if( + connections.begin(), + connections.end(), + std::not1(std::mem_fun(&ConnectionThread::isDone)) + ) + ); + // Destruct done + std::for_each( + done, + connections.end(), + DeleteConnection()); + // Erase done + std::erase(done, connections.end()); + + } +stopped = true; +} + +void STG_CONFIG::AcceptConnection() +{ +struct sockaddr_in remoteAddr; +socklen_t len = sizeof(struct sockaddr_in); +int rsd = accept(sd, &remoteAddr, &len); + +if (rsd < 0) + { + // Error logging + } + +connections.push_back(new ConnectionThread(this, rsd, remoteAddr, users, admins, tariffs, store, stgSettings)); +} diff --git a/projects/stargazer/plugins/configuration/sgconfig2/stgconfig.h b/projects/stargazer/plugins/configuration/sgconfig2/stgconfig.h new file mode 100644 index 00000000..a4449531 --- /dev/null +++ b/projects/stargazer/plugins/configuration/sgconfig2/stgconfig.h @@ -0,0 +1,80 @@ +#include + +#include +#include + +#include "base_plugin.h" +#include "base_store.h" +#include "configproto.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +class STG_CONFIG; + +//----------------------------------------------------------------------------- +class STG_CONFIG_SETTINGS +{ +public: + STG_CONFIG_SETTINGS(); + virtual ~STG_CONFIG_SETTINGS(){}; + const string & GetStrError() const; + int ParseSettings(const MODULE_SETTINGS & s); + uint16_t GetPort(); +private: + int ParseIntInRange(const string & str, int min, int max, int * val); + string errorStr; + int port; +}; +//----------------------------------------------------------------------------- +class STG_CONFIG: public BASE_PLUGIN +{ +public: + STG_CONFIG(); + virtual ~STG_CONFIG(){}; + + void SetUsers(USERS * u) { users = u; }; + void SetTariffs(TARIFFS * t) { tariffs = t; }; + void SetAdmins(ADMINS * a) { admins = a; }; + void SetStore(BASE_STORE * s) { store = s; }; + void SetTraffcounter(TRAFFCOUNTER *) {}; + void SetStgSettings(const SETTINGS * s) { stgConfigSettings = s; }; + void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning() { return running; }; + + const string & GetStrError() const { return errorStr; }; + string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + static void * Run(void *); + void RealRun(); + bool PrepareNetwork(); + bool FinalizeNetwork(); + void AcceptConnection(); + + mutable string errorStr; + STG_CONFIG_SETTINGS stgConfigSettings; + pthread_t thread; + bool running; + bool stopped; + CONFIGPROTO config; + USERS * users; + ADMINS * admins; + TARIFFS * tariffs; + BASE_STORE * store; + MODULE_SETTINGS settings; + const SETTINGS * stgSettings; + + std::list connections; + + int sd; +}; +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/configuration/xrconfig/Makefile b/projects/stargazer/plugins/configuration/xrconfig/Makefile new file mode 100644 index 00000000..db6b65b7 --- /dev/null +++ b/projects/stargazer/plugins/configuration/xrconfig/Makefile @@ -0,0 +1,64 @@ +############################################################################### +# $Id: Makefile,v 1.3 2009/03/03 15:49:35 faust Exp $ +############################################################################### +DEFS = -DLINUX + +ifeq ($(OS),bsd) +DEFS = -DFREE_BSD +endif + +ifeq ($(OS),bsd5) +DEFS = -DFREE_BSD5 +endif + + +DIR_INCLUDE = ../../../../../include +DIR_LIB = ../../../../../lib + +PROG = mod_conf_xr.so + +SRCS = ./xrconfig.cpp + + +LIBS = $(LIB_THREAD) + +SEARCH_DIRS = -I $(DIR_INCLUDE) + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +CXXFLAGS = -g3 -Wall -fPIC +LDFLAGS = -g3 -shared + + +vpath %.a $(DIR_LIB) + + +all: $(PROG) + +$(PROG): $(OBJS) $(LIBS) + $(CC) $^ $(LDFLAGS) -o $(PROG) + +clean: + rm -f deps $(PROG) *.o tags *.*~ + + +install: + echo TODO + +uninstall: + echo TODO + +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +include deps +endif +endif + +deps: $(SRCS) + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(DEFS) $(SEARCH_DIRS) -MM $$file` Makefile" >> deps ;\ + echo -e '\t$$(CC) -c $$< $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS)' >> deps ;\ + done + + diff --git a/projects/stargazer/plugins/configuration/xrconfig/xrconfig.cpp b/projects/stargazer/plugins/configuration/xrconfig/xrconfig.cpp new file mode 100644 index 00000000..cb156eed --- /dev/null +++ b/projects/stargazer/plugins/configuration/xrconfig/xrconfig.cpp @@ -0,0 +1,248 @@ +#include +#include +#include + +#include "xrconfig.h" +#include "../../../tariff2.h" +#include "../../../admins.h" +#include "../../../users.h" + +class XR_CONFIG_CREATOR +{ +private: + XR_CONFIG * xrconfig; + +public: + XR_CONFIG_CREATOR() + : xrconfig(new XR_CONFIG()) + { + }; + ~XR_CONFIG_CREATOR() + { + delete xrconfig; + }; + + XR_CONFIG * GetPlugin() + { + return xrconfig; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +XRCONFIG_CREATOR xrc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +XR_CONFIG_SETTINGS::XR_CONFIG_SETTINGS() + : port(0) +{ +} +//----------------------------------------------------------------------------- +const string & XR_CONFIG_SETTINGS::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int XR_CONFIG_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (strtoi2(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int XR_CONFIG_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +/*int p; +PARAM_VALUE pv; +vector::const_iterator pvi; + +pv.param = "Port"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Port\' not found."; + return -1; + } + +if (ParseIntInRange(pvi->value[0], 2, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + return -1; + } +port = p;*/ + +return 0; +} +//----------------------------------------------------------------------------- +uint16_t XR_CONFIG_SETTINGS::GetPort() +{ +return port; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return xrc.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const string XR_CONFIG::GetVersion() const +{ +return "XR_configurator v.0.01"; +} +//----------------------------------------------------------------------------- +XR_CONFIG::XR_CONFIG() +{ +isRunning = false; +nonstop = false; +} +//----------------------------------------------------------------------------- +void XR_CONFIG::SetUsers(USERS * u) +{ +users = u; +} +//----------------------------------------------------------------------------- +void XR_CONFIG::SetTariffs(TARIFFS * t) +{ +tariffs = t; +} +//----------------------------------------------------------------------------- +void XR_CONFIG::SetAdmins(ADMINS * a) +{ +admins = a; +} +//----------------------------------------------------------------------------- +void XR_CONFIG::SetStore(BASE_STORE * s) +{ +store = s; +} +//----------------------------------------------------------------------------- +void XR_CONFIG::SetStgSettings(const SETTINGS * s) +{ +stgSettings = s; +} +//----------------------------------------------------------------------------- +void XR_CONFIG::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int XR_CONFIG::ParseSettings() +{ +int ret = xrConfigSettings.ParseSettings(settings); +if (ret) + errorStr = xrConfigSettings.GetStrError(); +return ret; +} +//----------------------------------------------------------------------------- +const string & XR_CONFIG::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int XR_CONFIG::Start() +{ +if (isRunning) + return 0; + +nonstop = true; + +config.SetPort(xrConfigSettings.GetPort()); +config.SetAdmins(admins); +config.SetUsers(users); +config.SetTariffs(tariffs); +config.SetStgSettings(stgSettings); +config.SetStore(store); + +if (config.Prepare()) + { + errorStr = config.GetStrError(); + return -1; + } + +if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + printfd(__FILE__, "Cannot create thread\n"); + return -1; + } +errorStr = ""; +return 0; +} +//----------------------------------------------------------------------------- +int XR_CONFIG::Stop() +{ +if (!isRunning) + return 0; + +config.Stop(); + +//5 seconds to thread stops itself +int i; +for (i = 0; i < 25; i++) + { + if (!isRunning) + break; + + stgUsleep(200000); + } + +//after 5 seconds waiting thread still running. now killing it +if (isRunning) + { + //TODO pthread_cancel() + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return -1; + } + printfd(__FILE__, "XR_CONFIG killed\n"); + } + +return 0; +} +//----------------------------------------------------------------------------- +bool XR_CONFIG::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * XR_CONFIG::Run(void * d) +{ +XR_CONFIG * stgConf = (XR_CONFIG *)d; +stgConf->isRunning = true; + +stgConf->config.Run(&stgConf->config); + +stgConf->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t XR_CONFIG::GetStartPosition() const +{ +return 221; +} +//----------------------------------------------------------------------------- +uint16_t XR_CONFIG::GetStopPosition() const +{ +return 221; +} +//----------------------------------------------------------------------------- +int XR_CONFIG::SetUserCash(const string & admLogin, const string & usrLogin, double cash) const +{ +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/configuration/xrconfig/xrconfig.h b/projects/stargazer/plugins/configuration/xrconfig/xrconfig.h new file mode 100644 index 00000000..79e099f1 --- /dev/null +++ b/projects/stargazer/plugins/configuration/xrconfig/xrconfig.h @@ -0,0 +1,78 @@ +#include +#include +#include "base_plugin.h" +//#include "common_settings.h" +#include "common.h" +//#include "configproto.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +class STG_CONFIG; + +//----------------------------------------------------------------------------- +class XR_CONFIG_SETTINGS +{ +public: + XR_CONFIG_SETTINGS(); + virtual ~XR_CONFIG_SETTINGS(){}; + const string & GetStrError() const; + int ParseSettings(const MODULE_SETTINGS & s); + uint16_t GetPort(); + +private: + int ParseIntInRange(const string & str, int min, int max, int * val); + string errorStr; + int port; +}; +//----------------------------------------------------------------------------- +class XR_CONFIG :public BASE_PLUGIN +{ +public: + XR_CONFIG(); + virtual ~XR_CONFIG(){}; + + void SetUsers(USERS * u); + void SetTariffs(TARIFFS * t); + void SetAdmins(ADMINS * a); + void SetStore(BASE_STORE * s); + void SetTraffcounter(TRAFFCOUNTER * tc){}; + void SetStgSettings(const SETTINGS * s); + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + +private: + + + int SetUserCash(const string & admLogin, const string & usrLogin, double cash) const; + + static void * Run(void *); + mutable string errorStr; + XR_CONFIG_SETTINGS xrConfigSettings; + pthread_t thread; + bool nonstop; + bool isRunning; + + //CONFIGPROTO config; + + USERS * users; + ADMINS * admins; + TARIFFS * tariffs; + BASE_STORE * store; + MODULE_SETTINGS settings; + const SETTINGS * stgSettings; +}; +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/plugins/other/ping/Makefile b/projects/stargazer/plugins/other/ping/Makefile new file mode 100644 index 00000000..3e4d36d5 --- /dev/null +++ b/projects/stargazer/plugins/other/ping/Makefile @@ -0,0 +1,16 @@ +############################################################################### +# $Id: Makefile,v 1.11 2008/12/04 17:21:14 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +LIBS += $(LIB_THREAD) + +PROG = mod_ping.so + +SRCS = ./ping.cpp + +STGLIBS = -lstg_pinger -lstg_common + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/other/ping/ping.cpp b/projects/stargazer/plugins/other/ping/ping.cpp new file mode 100644 index 00000000..7fa5aec1 --- /dev/null +++ b/projects/stargazer/plugins/other/ping/ping.cpp @@ -0,0 +1,421 @@ +#include +#include +#include + +#include "ping.h" +#include "../../../user.h" + +class PING_CREATOR +{ +private: + PING * ping; + +public: + PING_CREATOR() + : ping(new PING()) + { + }; + ~PING_CREATOR() + { + delete ping; + }; + + PING * GetPlugin() + { + return ping; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PING_CREATOR pc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// ëÌÁÓÓ ÄÌÑ ÐÏÉÓËÁ ÀÚÅÒÁ × ÓÐÉÓËÅ ÎÏÔÉÆÉËÁÔÏÒÏ× +template +class IS_CONTAINS_USER: public binary_function +{ +public: + bool operator()(varType notifier, user_iter user) const + { + return notifier.GetUser() == user; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return pc.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PING_SETTINGS::PING_SETTINGS() + : pingDelay(0), + errorStr() +{ +} +//----------------------------------------------------------------------------- +int PING_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +PARAM_VALUE pv; +vector::const_iterator pvi; + +pv.param = "PingDelay"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'PingDelay\' not found."; + printfd(__FILE__, "Parameter 'PingDelay' not found\n"); + return -1; + } +if (ParseIntInRange(pvi->value[0], 5, 3600, &pingDelay)) + { + errorStr = "Cannot parse parameter \'PingDelay\': " + errorStr; + printfd(__FILE__, "Canot parse parameter 'PingDelay'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int PING_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +PING::PING() +{ +pthread_mutex_init(&mutex, NULL); +isRunning = false; +} +//----------------------------------------------------------------------------- +PING::~PING() +{ +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +const string PING::GetVersion() const +{ +return "Pinger v.1.01"; +} +//----------------------------------------------------------------------------- +void PING::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int PING::ParseSettings() +{ +int ret = pingSettings.ParseSettings(settings); +if (ret) + errorStr = pingSettings.GetStrError(); +return ret; +} +//----------------------------------------------------------------------------- +void PING::SetUsers(USERS * u) +{ +users = u; +} +//----------------------------------------------------------------------------- +const string & PING::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int PING::Start() +{ +GetUsers(); + +onAddUserNotifier.SetPinger(this); +onDelUserNotifier.SetPinger(this); +users->AddNotifierUserAdd(&onAddUserNotifier); +users->AddNotifierUserDel(&onDelUserNotifier); + +nonstop = true; + +pinger.SetDelayTime(pingSettings.GetPingDelay()); +pinger.Start(); + +if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot start thread."; + printfd(__FILE__, "Cannot start thread\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int PING::Stop() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (!isRunning) + return 0; + +pinger.Stop(); +nonstop = false; +//5 seconds to thread stops itself +for (int i = 0; i < 25; i++) + { + if (!isRunning) + break; + + usleep(200000); + } + +//after 5 seconds waiting thread still running. now kill it +if (isRunning) + { + printfd(__FILE__, "kill PING thread.\n"); + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill PING thread."; + printfd(__FILE__, "Cannot kill PING thread.\n"); + return -1; + } + printfd(__FILE__, "PING killed\n"); + } + +users->DelNotifierUserAdd(&onAddUserNotifier); +users->DelNotifierUserDel(&onDelUserNotifier); + +list::iterator users_iter; +users_iter = usersList.begin(); +while (users_iter != usersList.end()) + { + UnSetUserNotifiers(*users_iter); + users_iter++; + } + +return 0; +} +//----------------------------------------------------------------------------- +bool PING::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +void * PING::Run(void * d) +{ +PING * ping = (PING*)d; +ping->isRunning = true; +list::iterator iter; +uint32_t ip; +time_t t; + +while (ping->nonstop) + { + iter = ping->usersList.begin(); + { + STG_LOCKER lock(&ping->mutex, __FILE__, __LINE__); + while (iter != ping->usersList.end()) + { + if ((*iter)->property.ips.ConstData().OnlyOneIP()) + { + ip = (*iter)->property.ips.ConstData()[0].ip; + if (ping->pinger.GetIPTime(ip, &t) == 0) + { + if (t) + (*iter)->UpdatePingTime(t); + } + } + else + { + ip = (*iter)->GetCurrIP(); + if (ip) + { + if (ping->pinger.GetIPTime(ip, &t) == 0) + { + if (t) + (*iter)->UpdatePingTime(t); + } + } + } + iter++; + } + } + for (int i = 0; i < 100; i++) + { + if (ping->nonstop) + { + usleep((10000*ping->pingSettings.GetPingDelay())/3 + 50000); + } + } + } +ping->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +uint16_t PING::GetStartPosition() const +{ +return 100; +} +//----------------------------------------------------------------------------- +uint16_t PING::GetStopPosition() const +{ +return 100; +} +//----------------------------------------------------------------------------- +void PING::SetUserNotifiers(user_iter u) +{ +CHG_CURRIP_NOTIFIER_PING ChgCurrIPNotifier; +CHG_IPS_NOTIFIER_PING ChgIPNotifier; + +ChgCurrIPNotifier.SetPinger(this); +ChgCurrIPNotifier.SetUser(u); +ChgCurrIPNotifierList.push_front(ChgCurrIPNotifier); + +ChgIPNotifier.SetPinger(this); +ChgIPNotifier.SetUser(u); +ChgIPNotifierList.push_front(ChgIPNotifier); + +u->AddCurrIPAfterNotifier(&(*ChgCurrIPNotifierList.begin())); +u->property.ips.AddAfterNotifier(&(*ChgIPNotifierList.begin())); +} +//----------------------------------------------------------------------------- +void PING::UnSetUserNotifiers(user_iter u) +{ +// --- CurrIP --- +IS_CONTAINS_USER IsContainsUserCurrIP; +IS_CONTAINS_USER IsContainsUserIP; + +list::iterator currIPter; +list::iterator IPIter; + +currIPter = find_if(ChgCurrIPNotifierList.begin(), + ChgCurrIPNotifierList.end(), + bind2nd(IsContainsUserCurrIP, u)); + +if (currIPter != ChgCurrIPNotifierList.end()) + { + currIPter->GetUser()->DelCurrIPAfterNotifier(&(*currIPter)); + ChgCurrIPNotifierList.erase(currIPter); + } +// --- CurrIP end --- + +// --- IP --- +IPIter = find_if(ChgIPNotifierList.begin(), + ChgIPNotifierList.end(), + bind2nd(IsContainsUserIP, u)); + +if (IPIter != ChgIPNotifierList.end()) + { + IPIter->GetUser()->property.ips.DelAfterNotifier(&(*IPIter)); + ChgIPNotifierList.erase(IPIter); + } +// --- IP end --- +} +//----------------------------------------------------------------------------- +void PING::GetUsers() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +user_iter u; +int h = users->OpenSearch(); +if (!h) + { + printfd(__FILE__, "users->OpenSearch() error\n"); + return; + } + +while (users->SearchNext(h, &u) == 0) + { + usersList.push_back(u); + SetUserNotifiers(u); + if (u->property.ips.ConstData().OnlyOneIP()) + { + pinger.AddIP(u->property.ips.ConstData()[0].ip); + } + else + { + uint32_t ip = u->GetCurrIP(); + if (ip) + { + pinger.AddIP(ip); + } + } + } + +users->CloseSearch(h); +} +//----------------------------------------------------------------------------- +void PING::AddUser(user_iter u) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +SetUserNotifiers(u); +usersList.push_back(u); +} +//----------------------------------------------------------------------------- +void PING::DelUser(user_iter u) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +UnSetUserNotifiers(u); + +list::iterator users_iter; +users_iter = usersList.begin(); + +while (users_iter != usersList.end()) + { + if (u == *users_iter) + { + usersList.erase(users_iter); + break; + } + users_iter++; + } +} +//----------------------------------------------------------------------------- +void CHG_CURRIP_NOTIFIER_PING::Notify(const uint32_t & oldIP, const uint32_t & newIP) +{ +ping->pinger.DelIP(oldIP); +if (newIP) + { + ping->pinger.AddIP(newIP); + } +} +//----------------------------------------------------------------------------- +void CHG_IPS_NOTIFIER_PING::Notify(const USER_IPS & oldIPS, const USER_IPS & newIPS) +{ +if (oldIPS.OnlyOneIP()) + { + ping->pinger.DelIP(oldIPS[0].ip); + } + +if (newIPS.OnlyOneIP()) + { + ping->pinger.AddIP(newIPS[0].ip); + } +} +//----------------------------------------------------------------------------- +void ADD_USER_NONIFIER_PING::Notify(const user_iter & user) +{ +ping->AddUser(user); +} +//----------------------------------------------------------------------------- +void DEL_USER_NONIFIER_PING::Notify(const user_iter & user) +{ +ping->DelUser(user); +} +//----------------------------------------------------------------------------- + + + + + + diff --git a/projects/stargazer/plugins/other/ping/ping.h b/projects/stargazer/plugins/other/ping/ping.h new file mode 100644 index 00000000..c94c17e8 --- /dev/null +++ b/projects/stargazer/plugins/other/ping/ping.h @@ -0,0 +1,155 @@ + /* + $Revision: 1.16 $ + $Date: 2009/06/23 11:32:28 $ + $Author: faust $ + */ + +#ifndef PING_H +#define PING_H + +#include +#include + +#include "os_int.h" +#include "base_plugin.h" +#include "notifer.h" +#include "user_ips.h" +#include "pinger.h" +#include "../../../users.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +class PING; +//-----------------------------------------------------------------------------*/ +class CHG_CURRIP_NOTIFIER_PING: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const uint32_t & oldIP, const uint32_t & newIP); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() {return user; } + void SetPinger(const PING * p) { ping = p; } + +private: + user_iter user; + const PING * ping; +}; +//----------------------------------------------------------------------------- +class CHG_IPS_NOTIFIER_PING: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const USER_IPS & oldIPS, const USER_IPS & newIPS); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() {return user; } + void SetPinger(const PING * p) { ping = p; } + +private: + user_iter user; + const PING * ping; +}; +//----------------------------------------------------------------------------- +class ADD_USER_NONIFIER_PING: public NOTIFIER_BASE +{ +public: + ADD_USER_NONIFIER_PING(){}; + virtual ~ADD_USER_NONIFIER_PING(){}; + + void SetPinger(PING * p) { ping = p; } + void Notify(const user_iter & user); + +private: + PING * ping; +}; +//----------------------------------------------------------------------------- +class DEL_USER_NONIFIER_PING: public NOTIFIER_BASE +{ +public: + DEL_USER_NONIFIER_PING(){}; + virtual ~DEL_USER_NONIFIER_PING(){}; + + void SetPinger(PING * p) { ping = p; } + void Notify(const user_iter & user); + +private: + PING * ping; +}; +//----------------------------------------------------------------------------- +class PING_SETTINGS +{ +public: + PING_SETTINGS(); + virtual ~PING_SETTINGS(){}; + const string& GetStrError() const { return errorStr; } + int ParseSettings(const MODULE_SETTINGS & s); + int GetPingDelay(){ return pingDelay; }; +private: + int ParseIntInRange(const string & str, int min, int max, int * val); + int pingDelay; + mutable string errorStr; +}; +//----------------------------------------------------------------------------- +class PING: public BASE_PLUGIN +{ +friend class CHG_CURRIP_NOTIFIER_PING; +friend class CHG_IPS_NOTIFIER_PING; +public: + PING(); + virtual ~PING(); + + void SetUsers(USERS * u); + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER *){}; + void SetStore(BASE_STORE *){}; + void SetStgSettings(const SETTINGS *){}; + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + const string & GetStrError() const; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + + void AddUser(user_iter u); + void DelUser(user_iter u); + +private: + void GetUsers(); + void SetUserNotifiers(user_iter u); + void UnSetUserNotifiers(user_iter u); + static void * Run(void * d); + mutable string errorStr; + PING_SETTINGS pingSettings; + MODULE_SETTINGS settings; + USERS * users; + list usersList; + + /* + ÍÙ ÄÏÌÖÎÙ ÐÅÒÅÐÒÏ×ÅÒÉÔØ ×ÏÚÍÏÖÎÏÓÔØ ÐÉÎÇÏ×ÁÎÉÑ ÀÚÅÒÁ ÐÒÉ ÉÚÍÅÎÅÎÉÉ + ÓÌÅÄÕÀÝÉÈ ÅÇÏ ÐÁÒÁÍÅÔÒÏ×: + - currIP + - ips + */ + pthread_t thread; + pthread_mutex_t mutex; + bool nonstop; + bool isRunning; + mutable STG_PINGER pinger; + + list ChgCurrIPNotifierList; + list ChgIPNotifierList; + + ADD_USER_NONIFIER_PING onAddUserNotifier; + DEL_USER_NONIFIER_PING onDelUserNotifier; +}; +//----------------------------------------------------------------------------- + +#endif + + diff --git a/projects/stargazer/plugins/other/radius/Makefile b/projects/stargazer/plugins/other/radius/Makefile new file mode 100644 index 00000000..68be7618 --- /dev/null +++ b/projects/stargazer/plugins/other/radius/Makefile @@ -0,0 +1,16 @@ +############################################################################### +# $Id: Makefile,v 1.3 2010/04/26 12:44:42 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +LIBS += $(LIB_THREAD) + +PROG = mod_radius.so + +SRCS = ./radius.cpp + +STGLIBS = -lstg_common -lstg_crypto + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/other/radius/radius.cpp b/projects/stargazer/plugins/other/radius/radius.cpp new file mode 100644 index 00000000..591f4b63 --- /dev/null +++ b/projects/stargazer/plugins/other/radius/radius.cpp @@ -0,0 +1,728 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * This file contains a realization of radius data access plugin for Stargazer + * + * $Revision: 1.14 $ + * $Date: 2009/12/13 14:17:13 $ + * + */ + +#include +#include + +#include "radius.h" +#include "common.h" + +extern volatile const time_t stgTime; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +class RAD_CREATOR +{ +private: + RADIUS * rad; + +public: + RAD_CREATOR() + : rad(new RADIUS()) + { + }; + ~RAD_CREATOR() + { + delete rad; + }; + + RADIUS * GetPlugin() + { + return rad; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +RAD_CREATOR radc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return radc.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +uint16_t RAD_SETTINGS::GetPort() const +{ +return port; +} +//----------------------------------------------------------------------------- +uint32_t RAD_SETTINGS::GetServerIP() const +{ +return serverIP; +} +//----------------------------------------------------------------------------- +int RAD_SETTINGS::GetPassword(string * password) const +{ +*password = RAD_SETTINGS::password; +return 0; +} +//----------------------------------------------------------------------------- +int RAD_SETTINGS::GetAuthServices(list * svcs) const +{ +*svcs = authServices; +return 0; +} +//----------------------------------------------------------------------------- +int RAD_SETTINGS::GetAcctServices(list * svcs) const +{ +*svcs = acctServices; +return 0; +} +//----------------------------------------------------------------------------- +int RAD_SETTINGS::ParseIP(const string & str, uint32_t * IP) +{ +*IP = inet_addr(str.c_str()); +return *IP == INADDR_NONE ? -1 : 0; +} +//----------------------------------------------------------------------------- +int RAD_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int RAD_SETTINGS::ParseServices(const vector & str, list * lst) +{ + copy(str.begin(), str.end(), back_inserter(*lst)); + list::iterator it(find(lst->begin(), + lst->end(), + "empty")); + if (it != lst->end()) + *it = ""; + + return 0; +} +//----------------------------------------------------------------------------- +int RAD_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +int p; +PARAM_VALUE pv; +vector::const_iterator pvi; +/////////////////////////// +pv.param = "Port"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Port\' not found."; + printfd(__FILE__, "Parameter 'Port' not found\n"); + return -1; + } +if (ParseIntInRange(pvi->value[0], 2, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'Port'\n"); + return -1; + } +port = p; +/////////////////////////// +pv.param = "ServerIP"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + serverIP = 0; + } +else + { + if (ParseIP(pvi->value[0], &serverIP)) + { + serverIP = 0; + } + } +/////////////////////////// +pv.param = "Password"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Password\' not found."; + printfd(__FILE__, "Parameter 'Password' not found\n"); + return -1; + } +password = pvi->value[0]; +/////////////////////////// +pv.param = "AuthServices"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi != s.moduleParams.end()) + { + ParseServices(pvi->value, &authServices); + } +/////////////////////////// +pv.param = "AcctServices"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi != s.moduleParams.end()) + { + ParseServices(pvi->value, &acctServices); + } + +return 0; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +RADIUS::RADIUS() +{ +isRunning = false; +} +//----------------------------------------------------------------------------- +void RADIUS::SetUsers(USERS * u) +{ +users = u; +} +//----------------------------------------------------------------------------- +void RADIUS::SetStgSettings(const SETTINGS * s) +{ +stgSettings = s; +} +//----------------------------------------------------------------------------- +void RADIUS::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +void RADIUS::SetStore(BASE_STORE * s) +{ +store = s; +} +//----------------------------------------------------------------------------- +int RADIUS::ParseSettings() +{ +int ret = radSettings.ParseSettings(settings); +if (ret) + errorStr = radSettings.GetStrError(); +return ret; +} +//----------------------------------------------------------------------------- +bool RADIUS::IsRunning() +{ +return isRunning; +} +//----------------------------------------------------------------------------- +const string RADIUS::GetVersion() const +{ +return "RADIUS data access plugin v 0.6"; +} +//----------------------------------------------------------------------------- +uint16_t RADIUS::GetStartPosition() const +{ +// Start before any authorizers!!! +return 20; +} +//----------------------------------------------------------------------------- +uint16_t RADIUS::GetStopPosition() const +{ +return 20; +} +//----------------------------------------------------------------------------- +void RADIUS::SetUserNotifier(user_iter) +{ +} +//----------------------------------------------------------------------------- +void RADIUS::UnSetUserNotifier(user_iter) +{ +} +//----------------------------------------------------------------------------- +int RADIUS::PrepareNet() +{ +sock = socket(AF_INET, SOCK_DGRAM, 0); + +if (sock < 0) + { + errorStr = "Cannot create socket."; + printfd(__FILE__, "Cannot create socket\n"); + return -1; + } + +inAddr.sin_family = AF_INET; +inAddr.sin_port = htons(port); +inAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); + +if (bind(sock, (struct sockaddr*)&inAddr, sizeof(inAddr)) < 0) + { + errorStr = "RADIUS: Bind failed."; + printfd(__FILE__, "Cannot bind socket\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::FinalizeNet() +{ +close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::Start() +{ +string password; + +radSettings.GetPassword(&password); +port = radSettings.GetPort(); +serverIP = radSettings.GetServerIP(); +radSettings.GetAuthServices(&authServices); +radSettings.GetAcctServices(&acctServices); + +InitEncrypt(&ctx, password); + +nonstop = true; + +if (PrepareNet()) + { + return -1; + } + +if (!isRunning) + { + if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + printfd(__FILE__, "Cannot create thread\n"); + return -1; + } + } + +errorStr = ""; +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::Stop() +{ +if (!IsRunning()) + return 0; + +nonstop = false; + +map::iterator it; +for (it = sessions.begin(); it != sessions.end(); ++it) + { + user_iter ui; + if (users->FindByName(it->second.userName, &ui)) + { + ui->Unauthorize(this); + } + } +sessions.erase(sessions.begin(), sessions.end()); + +FinalizeNet(); + +if (isRunning) + { + //5 seconds to thread stops itself + for (int i = 0; i < 25 && isRunning; i++) + { + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (isRunning) + { + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return -1; + } + printfd(__FILE__, "RADIUS::Stop killed Run\n"); + } + } + +return 0; +} +//----------------------------------------------------------------------------- +void * RADIUS::Run(void * d) +{ +RADIUS * rad = (RADIUS *)d; +RAD_PACKET packet; + +rad->isRunning = true; + +while (rad->nonstop) + { + if (!rad->WaitPackets(rad->sock)) + { + continue; + } + if (rad->RecvData(&packet)) + { + printfd(__FILE__, "RADIUS::Run Error on RecvData\n"); + } + else + { + if (rad->ProcessData(&packet)) + { + packet.packetType = RAD_REJECT_PACKET; + } + rad->Send(packet); + } + } + +rad->isRunning = false; + +return NULL; +} +//----------------------------------------------------------------------------- +int RADIUS::RecvData(RAD_PACKET * packet) +{ + int8_t buf[RAD_MAX_PACKET_LEN]; + outerAddrLen = sizeof(struct sockaddr_in); + int dataLen = recvfrom(sock, buf, RAD_MAX_PACKET_LEN, 0, (struct sockaddr *)&outerAddr, &outerAddrLen); + if (dataLen > 0) { + Decrypt(&ctx, (char *)packet, (const char *)buf, dataLen / 8); + } + if (strncmp((char *)packet->magic, RAD_ID, RAD_MAGIC_LEN)) + { + printfd(__FILE__, "RADIUS::RecvData Error magic. Wanted: '%s', got: '%s'\n", RAD_ID, packet->magic); + return -1; + } + return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::Send(const RAD_PACKET & packet) +{ +int res, len = sizeof(RAD_PACKET); +char buf[1032]; + +Encrypt(&ctx, buf, (char *)&packet, len / 8); +res = sendto(sock, buf, len, 0, (struct sockaddr *)&outerAddr, outerAddrLen); + +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessData(RAD_PACKET * packet) +{ +//struct in_addr addr = {packet->ip}; +if (strncmp((const char *)packet->protoVer, "01", 2)) + { + printfd(__FILE__, "RADIUS::ProcessData packet.protoVer incorrect\n"); + return -1; + } +switch (packet->packetType) + { + case RAD_AUTZ_PACKET: + return ProcessAutzPacket(packet); + case RAD_AUTH_PACKET: + return ProcessAuthPacket(packet); + case RAD_POST_AUTH_PACKET: + return ProcessPostAuthPacket(packet); + case RAD_ACCT_START_PACKET: + return ProcessAcctStartPacket(packet); + case RAD_ACCT_STOP_PACKET: + return ProcessAcctStopPacket(packet); + case RAD_ACCT_UPDATE_PACKET: + return ProcessAcctUpdatePacket(packet); + case RAD_ACCT_OTHER_PACKET: + return ProcessAcctOtherPacket(packet); + default: + printfd(__FILE__, "RADIUS::ProcessData Unsupported packet type: %d\n", packet->packetType); + return -1; + }; +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessAutzPacket(RAD_PACKET * packet) +{ +USER_CONF conf; + +if (!IsAllowedService((char *)packet->service)) + { + printfd(__FILE__, "RADIUS::ProcessAutzPacket service '%s' is not allowed to authorize\n", packet->service); + packet->packetType = RAD_REJECT_PACKET; + return 0; + } + +if (store->RestoreUserConf(&conf, (char *)packet->login)) + { + packet->packetType = RAD_REJECT_PACKET; + printfd(__FILE__, "RADIUS::ProcessAutzPacket cannot restore conf for user '%s'\n", packet->login); + return 0; + } + +// At this point service can be authorized at least +// So we send a plain-text password + +packet->packetType = RAD_ACCEPT_PACKET; +strncpy((char *)packet->password, conf.password.c_str(), RAD_PASSWORD_LEN); + +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessAuthPacket(RAD_PACKET * packet) +{ +user_iter ui; + +if (!CanAcctService((char *)packet->service)) + { + + // There are no sense to check for allowed service + // It has allready checked at previous stage (authorization) + + printfd(__FILE__, "RADIUS::ProcessAuthPacket service '%s' neednot stargazer authentication\n", (char *)packet->service); + packet->packetType = RAD_ACCEPT_PACKET; + return 0; + } + +// At this point we have an accountable service +// All other services got a password if allowed or rejected + +if (!FindUser(&ui, (char *)packet->login)) + { + packet->packetType = RAD_REJECT_PACKET; + printfd(__FILE__, "RADIUS::ProcessAuthPacket user '%s' not found\n", (char *)packet->login); + return 0; + } + +if (ui->IsInetable()) + { + packet->packetType = RAD_ACCEPT_PACKET; + } +else + { + packet->packetType = RAD_REJECT_PACKET; + } + +packet->packetType = RAD_ACCEPT_PACKET; +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessPostAuthPacket(RAD_PACKET * packet) +{ +user_iter ui; + +if (!CanAcctService((char *)packet->service)) + { + + // There are no sense to check for allowed service + // It has allready checked at previous stage (authorization) + + packet->packetType = RAD_ACCEPT_PACKET; + return 0; + } + +if (!FindUser(&ui, (char *)packet->login)) + { + packet->packetType = RAD_REJECT_PACKET; + printfd(__FILE__, "RADIUS::ProcessPostAuthPacket user '%s' not found\n", (char *)packet->login); + return 0; + } + +// I think that only Framed-User services has sense to be accountable +// So we have to supply a Framed-IP + +USER_IPS ips = ui->property.ips; +packet->packetType = RAD_ACCEPT_PACKET; + +// Additional checking for Framed-User service + +if (!strncmp((char *)packet->service, "Framed-User", RAD_SERVICE_LEN)) + packet->ip = ips[0].ip; +else + packet->ip = 0; + +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessAcctStartPacket(RAD_PACKET * packet) +{ +user_iter ui; + +if (!FindUser(&ui, (char *)packet->login)) + { + packet->packetType = RAD_REJECT_PACKET; + printfd(__FILE__, "RADIUS::ProcessAcctStartPacket user '%s' not found\n", (char *)packet->login); + return 0; + } + +// At this point we have to unauthorize user only if it is an accountable service + +if (CanAcctService((char *)packet->service)) + { + if (sessions.find((const char *)packet->sessid) != sessions.end()) + { + printfd(__FILE__, "RADIUS::ProcessAcctStartPacket session already started!\n"); + packet->packetType = RAD_REJECT_PACKET; + return -1; + } + USER_IPS ips = ui->property.ips; + if (ui->Authorize(ips[0].ip, "", 0xffFFffFF, this)) + { + printfd(__FILE__, "RADIUS::ProcessAcctStartPacket cannot authorize user '%s'\n", packet->login); + packet->packetType = RAD_REJECT_PACKET; + return -1; + } + sessions[(const char *)packet->sessid].userName = (const char *)packet->login; + sessions[(const char *)packet->sessid].serviceType = (const char *)packet->service; + for_each(sessions.begin(), sessions.end(), SPrinter()); + } +else + { + printfd(__FILE__, "RADIUS::ProcessAcctStartPacket service '%s' can not be accounted\n", (char *)packet->service); + } + +packet->packetType = RAD_ACCEPT_PACKET; +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessAcctStopPacket(RAD_PACKET * packet) +{ +map::iterator sid; + +if ((sid = sessions.find((const char *)packet->sessid)) == sessions.end()) + { + printfd(__FILE__, "RADIUS::ProcessAcctStopPacket session had not started yet\n"); + packet->packetType = RAD_REJECT_PACKET; + return -1; + } + +user_iter ui; + +if (!FindUser(&ui, sid->second.userName)) + { + packet->packetType = RAD_REJECT_PACKET; + printfd(__FILE__, "RADIUS::ProcessPostAuthPacket user '%s' not found\n", sid->second.userName.c_str()); + return 0; + } + +sessions.erase(sid); + +ui->Unauthorize(this); + +packet->packetType = RAD_ACCEPT_PACKET; +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessAcctUpdatePacket(RAD_PACKET * packet) +{ +// Fake. May be used later +packet->packetType = RAD_ACCEPT_PACKET; +return 0; +} +//----------------------------------------------------------------------------- +int RADIUS::ProcessAcctOtherPacket(RAD_PACKET * packet) +{ +// Fake. May be used later +packet->packetType = RAD_ACCEPT_PACKET; +return 0; +} +//----------------------------------------------------------------------------- +void RADIUS::InitEncrypt(BLOWFISH_CTX * ctx, const string & password) +{ +unsigned char keyL[RAD_PASSWORD_LEN]; // Пароль для шифровки +memset(keyL, 0, RAD_PASSWORD_LEN); +strncpy((char *)keyL, password.c_str(), RAD_PASSWORD_LEN); +Blowfish_Init(ctx, keyL, RAD_PASSWORD_LEN); +} +//----------------------------------------------------------------------------- +void RADIUS::Encrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8) +{ +// len8 - длина в 8-ми байтовых блоках +if (dst != src) + memcpy(dst, src, len8 * 8); + +for (int i = 0; i < len8; i++) + Blowfish_Encrypt(ctx, (uint32_t *)(dst + i*8), (uint32_t *)(dst + i*8 + 4)); +} +//----------------------------------------------------------------------------- +void RADIUS::Decrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8) +{ +// len8 - длина в 8-ми байтовых блоках +if (dst != src) + memcpy(dst, src, len8 * 8); + +for (int i = 0; i < len8; i++) + Blowfish_Decrypt(ctx, (uint32_t *)(dst + i*8), (uint32_t *)(dst + i*8 + 4)); +} +//----------------------------------------------------------------------------- +void RADIUS::PrintServices(const list & svcs) +{ + for_each(svcs.begin(), svcs.end(), Printer()); +} +//----------------------------------------------------------------------------- +bool RADIUS::FindUser(user_iter * ui, const std::string & login) const +{ +if (users->FindByName(login, ui)) + { + return false; + } +return true; +} +//----------------------------------------------------------------------------- +bool RADIUS::CanAuthService(const std::string & svc) const +{ + return find(authServices.begin(), authServices.end(), svc) != authServices.end(); +} +//----------------------------------------------------------------------------- +bool RADIUS::CanAcctService(const std::string & svc) const +{ + return find(acctServices.begin(), acctServices.end(), svc) != acctServices.end(); +} +//----------------------------------------------------------------------------- +bool RADIUS::IsAllowedService(const std::string & svc) const +{ + return CanAuthService(svc) || CanAcctService(svc); +} +//----------------------------------------------------------------------------- +bool RADIUS::WaitPackets(int sd) const +{ +fd_set rfds; +FD_ZERO(&rfds); +FD_SET(sd, &rfds); + +struct timeval tv; +tv.tv_sec = 0; +tv.tv_usec = 500000; + +int res = select(sd + 1, &rfds, NULL, NULL, &tv); +if (res == -1) // Error + { + if (errno != EINTR) + { + printfd(__FILE__, "Error on select: '%s'\n", strerror(errno)); + } + return false; + } + +if (res == 0) // Timeout + { + return false; + } + +return true; +} diff --git a/projects/stargazer/plugins/other/radius/radius.h b/projects/stargazer/plugins/other/radius/radius.h new file mode 100644 index 00000000..b26a3bb3 --- /dev/null +++ b/projects/stargazer/plugins/other/radius/radius.h @@ -0,0 +1,194 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Radius data access plugin for Stargazer + * + * $Revision: 1.10 $ + * $Date: 2009/12/13 14:17:13 $ + * + */ + +#ifndef RADIUS_H +#define RADIUS_H + +#include +#include +#include +#include +#include + +#include "os_int.h" +#include "base_auth.h" +#include "notifer.h" +#include "user_ips.h" +#include "../../../user.h" +#include "../../../users.h" +#include "blowfish.h" +#include "rad_packets.h" + +using namespace std; + +extern "C" BASE_PLUGIN * GetPlugin(); + +#define RAD_DEBUG (1) + +class RADIUS; +//----------------------------------------------------------------------------- +class RAD_SETTINGS +{ +public: + virtual ~RAD_SETTINGS(){}; + const string& GetStrError() const { return errorStr; }; + int ParseSettings(const MODULE_SETTINGS & s); + uint16_t GetPort() const; + uint32_t GetServerIP() const; + int GetPassword(string * password) const; + int GetAuthServices(list * svcs) const; + int GetAcctServices(list * svcs) const; + +private: + int ParseIntInRange(const string & str, int min, int max, int * val); + int ParseIP(const string & str, uint32_t * routerIP); + int ParseServices(const vector & str, list * lst); + + uint16_t port; + string errorStr; + string password; + uint32_t serverIP; + list authServices; + list acctServices; +}; +//----------------------------------------------------------------------------- +struct RAD_SESSION { + std::string userName; + std::string serviceType; +}; +//----------------------------------------------------------------------------- +class RADIUS :public BASE_AUTH +{ +public: + RADIUS(); + virtual ~RADIUS(){}; + + void SetUsers(USERS * u); + void SetTariffs(TARIFFS *){}; + void SetAdmins(ADMINS *){}; + void SetTraffcounter(TRAFFCOUNTER *){}; + void SetStore(BASE_STORE * ); + void SetStgSettings(const SETTINGS * s); + void SetSettings(const MODULE_SETTINGS & s); + int ParseSettings(); + + int Start(); + int Stop(); + int Reload() { return 0; }; + bool IsRunning(); + + const string & GetStrError() const { return errorStr; }; + const string GetVersion() const; + uint16_t GetStartPosition() const; + uint16_t GetStopPosition() const; + + int SendMessage(const STG_MSG &, uint32_t) const { return 0; }; + +private: + static void * Run(void *); + int PrepareNet(); + int FinalizeNet(); + + void InitEncrypt(BLOWFISH_CTX * ctx, const string & password); + void Decrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8); + void Encrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, int len8); + + int Send(const RAD_PACKET & packet); + int RecvData(RAD_PACKET * packet); + int ProcessData(RAD_PACKET * packet); + + int ProcessAutzPacket(RAD_PACKET * packet); + int ProcessAuthPacket(RAD_PACKET * packet); + int ProcessPostAuthPacket(RAD_PACKET * packet); + int ProcessAcctStartPacket(RAD_PACKET * packet); + int ProcessAcctStopPacket(RAD_PACKET * packet); + int ProcessAcctUpdatePacket(RAD_PACKET * packet); + int ProcessAcctOtherPacket(RAD_PACKET * packet); + + bool FindUser(user_iter * ui, const std::string & login) const; + bool CanAuthService(const std::string & svc) const; + bool CanAcctService(const std::string & svc) const; + bool IsAllowedService(const std::string & svc) const; + + void SetUserNotifier(user_iter u); + void UnSetUserNotifier(user_iter u); + + bool WaitPackets(int sd) const; + + void PrintServices(const std::list & svcs); + + struct Printer : public unary_function + { + void operator()(const std::string & line) + { + printfd("radius.cpp", "'%s'\n", line.c_str()); + }; + }; + struct SPrinter : public unary_function, void> + { + void operator()(const std::pair & it) + { + printfd("radius.cpp", "%s - ('%s', '%s')\n", it.first.c_str(), it.second.userName.c_str(), it.second.serviceType.c_str()); + }; + }; + + BLOWFISH_CTX ctx; + + mutable string errorStr; + RAD_SETTINGS radSettings; + MODULE_SETTINGS settings; + list authServices; + list acctServices; + map sessions; + + bool nonstop; + + bool isRunning; + + USERS * users; + const SETTINGS * stgSettings; + const BASE_STORE * store; + + pthread_t thread; + pthread_mutex_t mutex; + + int sock; + struct sockaddr_in inAddr; + socklen_t inAddrLen; + uint16_t port; + uint32_t serverIP; + struct sockaddr_in outerAddr; + socklen_t outerAddrLen; + + RAD_PACKET packet; + +}; +//----------------------------------------------------------------------------- + +#endif + diff --git a/projects/stargazer/plugins/other/rscript/Makefile b/projects/stargazer/plugins/other/rscript/Makefile new file mode 100644 index 00000000..cad853c7 --- /dev/null +++ b/projects/stargazer/plugins/other/rscript/Makefile @@ -0,0 +1,17 @@ +############################################################################### +# $Id: Makefile,v 1.7 2010/02/16 11:41:00 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +LIBS += $(LIB_THREAD) + +PROG = mod_remote_script.so + +SRCS = ./rscript.cpp \ + ./nrmap_parser.cpp + +STGLIBS = -lstg_common -lstg_crypto + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/other/rscript/nrmap_parser.cpp b/projects/stargazer/plugins/other/rscript/nrmap_parser.cpp new file mode 100644 index 00000000..38caa1cf --- /dev/null +++ b/projects/stargazer/plugins/other/rscript/nrmap_parser.cpp @@ -0,0 +1,229 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.8 $ + $Author: faust $ + $Date: 2009/10/22 09:58:53 $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +#include "nrmap_parser.h" + +NRMapParser::NRMapParser() +{ +} + +NRMapParser::~NRMapParser() +{ +} + +bool NRMapParser::ReadFile(const std::string & fileName) +{ +std::ifstream source(fileName.c_str()); + +if (!source) + { + errorStr = "Error opening file "; + errorStr += fileName; + printfd(__FILE__, "NRMapParser::ReadFile(): %s\n", errorStr.c_str()); + return true; + } + +int lineNumber = 0; +std::string line; +std::vector _nrmap; + +while (getline(source, line)) + { + ++lineNumber; + NET_ROUTER nr; + + if (Trim(line) == "") + { + continue; + } + + if (ParseLine(line, nr)) + { + printfd(__FILE__, "NRMapParser::ReadFile(): Error parsing line %d: '%s'\n", lineNumber, errorStr.c_str()); + return true; + } + + _nrmap.push_back(nr); + } + +nrmap = _nrmap; + +return false; +} + +bool NRMapParser::ParseLine(const std::string & line, NET_ROUTER & nr) const +{ +// xxx.xxx.xxx.xxx/yy zzz.zzz.zzz.zzz +size_t pos = line.find_first_of(" \t"); + +if (pos == std::string::npos) + { + errorStr = "No space between subnet and router"; + return true; + } + +std::string subnet(line.substr(0, pos)); // xxx.xxx.xxx.xxx/yy + +uint32_t ip; +uint32_t mask; + +if (ParseNet(subnet, ip, mask)) + { + return true; + } + +nr.subnetIP = ip; +nr.subnetMask = mask; + +pos = line.find_first_not_of(" \t", pos); + +if (pos == std::string::npos) + { + errorStr = "No router address found"; + return true; + } + +size_t pos2 = line.find_first_of(" \t", pos); + +std::string router(line.substr(pos, pos2 == std::string::npos ? line.length() - pos2 - 1 : pos2 - pos)); //zzz.zzz.zzz.zzz + +uint32_t routerIP; + +if (ParseRouter(router, routerIP)) + { + return true; + } + +std::vector::iterator it; + +it = std::lower_bound( + nr.routers.begin(), + nr.routers.end(), + routerIP + ); +nr.routers.insert(it, routerIP); + +//nr.routers.push_back(routerIP); + +while (pos2 != std::string::npos) + { + pos = line.find_first_not_of(" \t", pos2); + + if (pos == std::string::npos) + { + return false; + } + + pos2 = line.find_first_of(" \t", pos); + + if (ParseRouter(line.substr( + pos, + pos2 == std::string::npos ? line.length() - pos2 - 1 : pos2 - pos), + routerIP)) + { + return true; + } + + it = std::lower_bound( + nr.routers.begin(), + nr.routers.end(), + routerIP + ); + nr.routers.insert(it, routerIP); + + //nr.routers.push_back(routerIP); + + } + +return false; +} + +bool NRMapParser::ParseNet(const std::string & line, uint32_t & ip, uint32_t & mask) const +{ +// xxx.xxx.xxx.xxx/yy + +size_t pos = line.find_first_of('/'); + +if (pos == std::string::npos) + { + errorStr = "Subnet is not in CIDR notation"; + return true; + } + +int res = inet_pton(AF_INET, line.substr(0, pos).c_str(), &ip); //xxx.xxx.xxx.xxx + +if (res < 0) + { + errorStr = strerror(errno); + return true; + } +else if (res == 0) + { + errorStr = "Invalid subnet address"; + return true; + } + +if (str2x(line.substr(pos + 1, line.length() - pos - 1), mask)) //yy + { + errorStr = "Invalid subnet mask"; + return true; + } +if (mask > 32) + { + errorStr = "Subnet mask is out of range [0..32]"; + return true; + } +mask = htonl(0xffFFffFF << (32 - mask)); //bitmask + +return false; +} + +bool NRMapParser::ParseRouter(const std::string & line, uint32_t & ip) const +{ +int res = inet_pton(AF_INET, line.c_str(), &ip); //zzz.zzz.zzz.zzz + +if (res < 0) + { + errorStr = strerror(errno); + return true; + } +else if (res == 0) + { + printfd(__FILE__, "NRMapParser::ParseRouter(): IP '%s' is invalid\n", line.c_str()); + errorStr = "Invalid router address"; + return true; + } +return false; +} diff --git a/projects/stargazer/plugins/other/rscript/nrmap_parser.h b/projects/stargazer/plugins/other/rscript/nrmap_parser.h new file mode 100644 index 00000000..d882c3a2 --- /dev/null +++ b/projects/stargazer/plugins/other/rscript/nrmap_parser.h @@ -0,0 +1,58 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.2 $ + $Author: faust $ + $Date: 2009/09/23 12:51:42 $ + */ + +#ifndef __NRMAP_PARSER_H__ +#define __NRMAP_PARSER_H__ + +#include +#include +#include "os_int.h" + +struct NET_ROUTER +{ +uint32_t subnetIP; +uint32_t subnetMask; +std::vector routers; +}; + +class NRMapParser { +public: + NRMapParser(); + ~NRMapParser(); + + bool ReadFile(const std::string & fileName); + const std::vector & GetMap() const { return nrmap; }; + const std::string & GetErrorStr() const { return errorStr; }; +private: + std::vector nrmap; + mutable std::string errorStr; + + bool ParseLine(const std::string & line, NET_ROUTER & nr) const; + bool ParseNet(const std::string & line, uint32_t & ip, uint32_t & mask) const; + bool ParseRouter(const std::string & line, uint32_t & ip) const; +}; + +#endif diff --git a/projects/stargazer/plugins/other/rscript/rscript.cpp b/projects/stargazer/plugins/other/rscript/rscript.cpp new file mode 100644 index 00000000..3b00b3f8 --- /dev/null +++ b/projects/stargazer/plugins/other/rscript/rscript.cpp @@ -0,0 +1,695 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.33 $ + $Date: 2010/04/16 12:30:37 $ + $Author: faust $ +*/ + +#include + +#include +#include +#include + +#include "rscript.h" +#include "common.h" +#include "ur_functor.h" +#include "send_functor.h" + +extern volatile const time_t stgTime; + +#define RS_MAX_ROUTERS (100) + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +class RS_CREATOR +{ +private: + REMOTE_SCRIPT * rs; + +public: + RS_CREATOR() + : rs(new REMOTE_SCRIPT()) + { + }; + ~RS_CREATOR() + { + delete rs; + }; + + REMOTE_SCRIPT * GetPlugin() + { + return rs; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +RS_CREATOR rsc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_PLUGIN * GetPlugin() +{ +return rsc.GetPlugin(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +RS_USER::RS_USER() + : lastSentTime(0), + shortPacketsCount(0) +{ +} +//----------------------------------------------------------------------------- +RS_USER::RS_USER(const std::vector & r, user_iter it) + : lastSentTime(0), + user(it), + routers(r), + shortPacketsCount(0) +{ +} +//----------------------------------------------------------------------------- +RS_SETTINGS::RS_SETTINGS() + : sendPeriod(0), + port(0) +{ +} +//----------------------------------------------------------------------------- +int RS_SETTINGS::ParseIntInRange(const string & str, int min, int max, int * val) +{ +if (str2x(str.c_str(), *val)) + { + errorStr = "Incorrect value \'" + str + "\'."; + return -1; + } +if (*val < min || *val > max) + { + errorStr = "Value \'" + str + "\' out of range."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int RS_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +int p; +PARAM_VALUE pv; +vector::const_iterator pvi; +netRouters.clear(); +/////////////////////////// +pv.param = "Port"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Port\' not found."; + printfd(__FILE__, "Parameter 'Port' not found\n"); + return -1; + } +if (ParseIntInRange(pvi->value[0], 2, 65535, &p)) + { + errorStr = "Cannot parse parameter \'Port\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'Port'\n"); + return -1; + } +port = p; +/////////////////////////// +pv.param = "SendPeriod"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'SendPeriod\' not found."; + printfd(__FILE__, "Parameter 'SendPeriod' not found\n"); + return -1; + } + +if (ParseIntInRange(pvi->value[0], 5, 600, &sendPeriod)) + { + errorStr = "Cannot parse parameter \'SendPeriod\': " + errorStr; + printfd(__FILE__, "Cannot parse parameter 'SendPeriod'\n"); + return -1; + } +/////////////////////////// +pv.param = "UserParams"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'UserParams\' not found."; + printfd(__FILE__, "Parameter 'UserParams' not found\n"); + return -1; + } +userParams = pvi->value; +/////////////////////////// +pv.param = "Password"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'Password\' not found."; + printfd(__FILE__, "Parameter 'Password' not found\n"); + return -1; + } +password = pvi->value[0]; +/////////////////////////// +pv.param = "SubnetFile"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'SubnetFile\' not found."; + printfd(__FILE__, "Parameter 'SubnetFile' not found\n"); + return -1; + } +subnetFile = pvi->value[0]; + +NRMapParser nrMapParser; + +if (nrMapParser.ReadFile(subnetFile)) + { + errorStr = nrMapParser.GetErrorStr(); + return -1; + } + +netRouters = nrMapParser.GetMap(); + +if (netRouters.empty()) + { + errorStr = "Parameter(s) \'Subnet*\' not found."; + printfd(__FILE__, "Parameter(s) 'Subnet*' not found\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +REMOTE_SCRIPT::REMOTE_SCRIPT() + : sendPeriod(15), + halfPeriod(8), + nonstop(false), + isRunning(false), + users(NULL), + sock(0) +{ +pthread_mutex_init(&mutex, NULL); +} +//----------------------------------------------------------------------------- +REMOTE_SCRIPT::~REMOTE_SCRIPT() +{ +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +void * REMOTE_SCRIPT::Run(void * d) +{ +REMOTE_SCRIPT * rs = static_cast(d); + +rs->isRunning = true; + +while (rs->nonstop) + { + rs->PeriodicSend(); + sleep(2); + } + +rs->isRunning = false; +return NULL; +} +//----------------------------------------------------------------------------- +int REMOTE_SCRIPT::ParseSettings() +{ +int ret = rsSettings.ParseSettings(settings); +if (ret) + errorStr = rsSettings.GetStrError(); + +sendPeriod = rsSettings.GetSendPeriod(); +halfPeriod = sendPeriod / 2; + +return ret; +} +//----------------------------------------------------------------------------- +int REMOTE_SCRIPT::Start() +{ +netRouters = rsSettings.GetSubnetsMap(); + +InitEncrypt(&ctx, rsSettings.GetPassword()); + +onAddUserNotifier.SetRemoteScript(this); +onDelUserNotifier.SetRemoteScript(this); + +users->AddNotifierUserAdd(&onAddUserNotifier); +users->AddNotifierUserDel(&onDelUserNotifier); + +nonstop = true; + +if (GetUsers()) + { + return -1; + } + +if (PrepareNet()) + { + return -1; + } + +if (!isRunning) + { + if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot create thread."; + printfd(__FILE__, "Cannot create thread\n"); + return -1; + } + } + +errorStr = ""; +return 0; +} +//----------------------------------------------------------------------------- +int REMOTE_SCRIPT::Stop() +{ +if (!IsRunning()) + return 0; + +nonstop = false; + +std::for_each( + authorizedUsers.begin(), + authorizedUsers.end(), + DisconnectUser(*this) + ); + +FinalizeNet(); + +if (isRunning) + { + //5 seconds to thread stops itself + for (int i = 0; i < 25 && isRunning; i++) + { + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (isRunning) + { + if (pthread_kill(thread, SIGINT)) + { + errorStr = "Cannot kill thread."; + printfd(__FILE__, "Cannot kill thread\n"); + return -1; + } + printfd(__FILE__, "REMOTE_SCRIPT killed Run\n"); + } + } + +users->DelNotifierUserDel(&onDelUserNotifier); +users->DelNotifierUserAdd(&onAddUserNotifier); + +return 0; +} +//----------------------------------------------------------------------------- +int REMOTE_SCRIPT::Reload() +{ +NRMapParser nrMapParser; + +if (nrMapParser.ReadFile(rsSettings.GetMapFileName())) + { + errorStr = nrMapParser.GetErrorStr(); + return -1; + } + + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + + printfd(__FILE__, "REMOTE_SCRIPT::Reload()\n"); + + netRouters = nrMapParser.GetMap(); + } + +std::for_each(authorizedUsers.begin(), + authorizedUsers.end(), + UpdateRouter(*this)); + +return 0; +} +//----------------------------------------------------------------------------- +bool REMOTE_SCRIPT::PrepareNet() +{ +sock = socket(AF_INET, SOCK_DGRAM, 0); + +if (sock < 0) + { + errorStr = "Cannot create socket."; + printfd(__FILE__, "Cannot create socket\n"); + return true; + } + +return false; +} +//----------------------------------------------------------------------------- +bool REMOTE_SCRIPT::FinalizeNet() +{ +close(sock); +return false; +} +//----------------------------------------------------------------------------- +void REMOTE_SCRIPT::PeriodicSend() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +map::iterator it(authorizedUsers.begin()); +while (it != authorizedUsers.end()) + { + if (difftime(stgTime, it->second.lastSentTime) - (rand() % halfPeriod) > sendPeriod) + //if (stgTime - it->second.lastSentTime > sendPeriod) + { + Send(it->first, it->second); + } + ++it; + } +} +//----------------------------------------------------------------------------- +bool REMOTE_SCRIPT::PreparePacket(char * buf, size_t bufSize, uint32_t ip, RS_USER & rsu, bool forceDisconnect) const +{ +RS_PACKET_HEADER packetHead; + +memset(packetHead.padding, 0, sizeof(packetHead.padding)); +strcpy((char*)packetHead.magic, RS_ID); +packetHead.protoVer[0] = '0'; +packetHead.protoVer[1] = '2'; +if (forceDisconnect) + { + packetHead.packetType = RS_DISCONNECT_PACKET; + } +else + { + if (rsu.shortPacketsCount % MAX_SHORT_PCKT == 0) + { + //SendLong + packetHead.packetType = rsu.user->IsInetable() ? RS_CONNECT_PACKET : RS_DISCONNECT_PACKET; + } + else + { + //SendShort + packetHead.packetType = rsu.user->IsInetable() ? RS_ALIVE_PACKET : RS_DISCONNECT_PACKET; + } + } +rsu.shortPacketsCount++; +rsu.lastSentTime = stgTime; + +packetHead.ip = htonl(ip); +packetHead.id = htonl(rsu.user->GetID()); +strncpy((char*)packetHead.login, rsu.user->GetLogin().c_str(), RS_LOGIN_LEN); +packetHead.login[RS_LOGIN_LEN - 1] = 0; + +memcpy(buf, &packetHead, sizeof(packetHead)); + +if (packetHead.packetType == RS_ALIVE_PACKET) + { + return false; + } + +RS_PACKET_TAIL packetTail; + +memset(packetTail.padding, 0, sizeof(packetTail.padding)); +strcpy((char*)packetTail.magic, RS_ID); +vector::const_iterator it; +std::string params; +for(it = rsSettings.GetUserParams().begin(); + it != rsSettings.GetUserParams().end(); + ++it) + { + std::string parameter(GetUserParam(rsu.user, *it)); + if (params.length() + parameter.length() > RS_PARAMS_LEN - 1) + break; + params += parameter + " "; + } +strncpy((char *)packetTail.params, params.c_str(), RS_PARAMS_LEN); +packetTail.params[RS_PARAMS_LEN - 1] = 0; + +assert(sizeof(packetHead) + sizeof(packetTail) <= bufSize && "Insufficient buffer space"); + +Encrypt(&ctx, buf + sizeof(packetHead), (char *)&packetTail, sizeof(packetTail) / 8); + +return false; +} +//----------------------------------------------------------------------------- +bool REMOTE_SCRIPT::Send(uint32_t ip, RS_USER & rsu, bool forceDisconnect) const +{ +char buffer[RS_MAX_PACKET_LEN]; + +memset(buffer, 0, sizeof(buffer)); + +if (PreparePacket(buffer, sizeof(buffer), ip, rsu, forceDisconnect)) + { + printfd(__FILE__, "REMOTE_SCRIPT::Send() - Invalid packet length!\n"); + return true; + } + +std::for_each( + rsu.routers.begin(), + rsu.routers.end(), + PacketSender(sock, buffer, sizeof(buffer), htons(rsSettings.GetPort())) + ); + +return false; +} +//----------------------------------------------------------------------------- +bool REMOTE_SCRIPT::SendDirect(uint32_t ip, RS_USER & rsu, uint32_t routerIP, bool forceDisconnect) const +{ +char buffer[RS_MAX_PACKET_LEN]; + +if (PreparePacket(buffer, sizeof(buffer), ip, rsu, forceDisconnect)) + { + printfd(__FILE__, "REMOTE_SCRIPT::SendDirect() - Invalid packet length!\n"); + return true; + } + +struct sockaddr_in sendAddr; + +sendAddr.sin_family = AF_INET; +sendAddr.sin_port = htons(rsSettings.GetPort()); +sendAddr.sin_addr.s_addr = routerIP; + +int res = sendto(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&sendAddr, sizeof(sendAddr)); + +return (res != sizeof(buffer)); +} +//----------------------------------------------------------------------------- +bool REMOTE_SCRIPT::GetUsers() +{ +user_iter u; + +int h = users->OpenSearch(); +if (!h) + { + errorStr = "users->OpenSearch() error."; + printfd(__FILE__, "OpenSearch() error\n"); + return true; + } + +while (!users->SearchNext(h, &u)) + { + SetUserNotifier(u); + } + +users->CloseSearch(h); +return false; +} +//----------------------------------------------------------------------------- +void REMOTE_SCRIPT::ChangedIP(user_iter u, uint32_t oldIP, uint32_t newIP) +{ +/* + * When ip changes process looks like: + * old => 0, 0 => new + * + */ +if (newIP) + { + RS_USER rsu(IP2Routers(newIP), u); + Send(newIP, rsu); + + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + authorizedUsers[newIP] = rsu; + } +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + const map::iterator it( + authorizedUsers.find(oldIP) + ); + if (it != authorizedUsers.end()) + { + Send(oldIP, it->second, true); + authorizedUsers.erase(it); + } + } +} +//----------------------------------------------------------------------------- +std::vector REMOTE_SCRIPT::IP2Routers(uint32_t ip) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +for (size_t i = 0; i < netRouters.size(); ++i) + { + if ((ip & netRouters[i].subnetMask) == (netRouters[i].subnetIP & netRouters[i].subnetMask)) + { + return netRouters[i].routers; + } + } +return std::vector(); +} +//----------------------------------------------------------------------------- +string REMOTE_SCRIPT::GetUserParam(user_iter u, const string & paramName) const +{ +string value = ""; +if (strcasecmp(paramName.c_str(), "cash") == 0) + strprintf(&value, "%f", u->property.cash.Get()); +else +if (strcasecmp(paramName.c_str(), "freeMb") == 0) + strprintf(&value, "%f", u->property.freeMb.Get()); +else +if (strcasecmp(paramName.c_str(), "passive") == 0) + strprintf(&value, "%d", u->property.passive.Get()); +else +if (strcasecmp(paramName.c_str(), "disabled") == 0) + strprintf(&value, "%d", u->property.disabled.Get()); +else +if (strcasecmp(paramName.c_str(), "alwaysOnline") == 0) + strprintf(&value, "%d", u->property.alwaysOnline.Get()); +else +if (strcasecmp(paramName.c_str(), "tariffName") == 0 || + strcasecmp(paramName.c_str(), "tariff") == 0) + value = "\"" + u->property.tariffName.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "nextTariff") == 0) + value = "\"" + u->property.nextTariff.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "address") == 0) + value = "\"" + u->property.address.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "note") == 0) + value = "\"" + u->property.note.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "group") == 0) + value = "\"" + u->property.group.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "email") == 0) + value = "\"" + u->property.email.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "realName") == 0) + value = "\"" + u->property.realName.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "credit") == 0) + strprintf(&value, "%f", u->property.credit.Get()); +else +if (strcasecmp(paramName.c_str(), "userdata0") == 0) + value = "\"" + u->property.userdata0.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata1") == 0) + value = "\"" + u->property.userdata1.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata2") == 0) + value = "\"" + u->property.userdata2.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata3") == 0) + value = "\"" + u->property.userdata3.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata4") == 0) + value = "\"" + u->property.userdata4.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata5") == 0) + value = "\"" + u->property.userdata5.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata6") == 0) + value = "\"" + u->property.userdata6.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata7") == 0) + value = "\"" + u->property.userdata7.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata8") == 0) + value = "\"" + u->property.userdata8.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "userdata9") == 0) + value = "\"" + u->property.userdata9.Get() + "\""; +else +if (strcasecmp(paramName.c_str(), "enabledDirs") == 0) + value = u->GetEnabledDirs(); +else + printfd(__FILE__, "Unknown value name: %s\n", paramName.c_str()); +return value; +} +//----------------------------------------------------------------------------- +void REMOTE_SCRIPT::SetUserNotifier(user_iter u) +{ +RS_CHG_AFTER_NOTIFIER AfterChgIPNotifier; + +AfterChgIPNotifier.SetRemoteScript(this); +AfterChgIPNotifier.SetUser(u); +AfterChgIPNotifierList.push_front(AfterChgIPNotifier); + +u->AddCurrIPAfterNotifier(&(*AfterChgIPNotifierList.begin())); +} +//----------------------------------------------------------------------------- +void REMOTE_SCRIPT::UnSetUserNotifier(user_iter u) +{ +list >::iterator ipAIter; +std::list >::iterator> toErase; + +for (ipAIter = AfterChgIPNotifierList.begin(); ipAIter != AfterChgIPNotifierList.end(); ++ipAIter) + { + if (ipAIter->GetUser() == u) + { + u->DelCurrIPAfterNotifier(&(*ipAIter)); + toErase.push_back(ipAIter); + } + } + +std::list >::iterator>::iterator eIter; + +for (eIter = toErase.begin(); eIter != toErase.end(); ++eIter) + { + AfterChgIPNotifierList.erase(*eIter); + } +} +//----------------------------------------------------------------------------- +template +void RS_CHG_AFTER_NOTIFIER::Notify(const varParamType & oldValue, const varParamType & newValue) +{ +rs->ChangedIP(user, oldValue, newValue); +} +//----------------------------------------------------------------------------- +void REMOTE_SCRIPT::InitEncrypt(BLOWFISH_CTX * ctx, const string & password) const +{ +unsigned char keyL[PASSWD_LEN]; // Пароль для шифровки +memset(keyL, 0, PASSWD_LEN); +strncpy((char *)keyL, password.c_str(), PASSWD_LEN); +Blowfish_Init(ctx, keyL, PASSWD_LEN); +} +//----------------------------------------------------------------------------- +void REMOTE_SCRIPT::Encrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, size_t len8) const +{ +if (dst != src) + memcpy(dst, src, len8 * 8); +for (size_t i = 0; i < len8; ++i) + Blowfish_Encrypt(ctx, (uint32_t *)(dst + i * 8), (uint32_t *)(dst + i * 8 + 4)); +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/other/rscript/rscript.h b/projects/stargazer/plugins/other/rscript/rscript.h new file mode 100644 index 00000000..ed08e2bc --- /dev/null +++ b/projects/stargazer/plugins/other/rscript/rscript.h @@ -0,0 +1,237 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.16 $ + $Date: 2010/09/10 06:43:59 $ + $Author: faust $ +*/ + +#ifndef RSCRIPT_H +#define RSCRIPT_H + +#include + +#include +#include +#include +#include +#include + +#include "base_store.h" +#include "os_int.h" +#include "notifer.h" +#include "user_ips.h" +#include "../../../user.h" +#include "../../../users.h" +#include "blowfish.h" +#include "rs_packets.h" +#include "nrmap_parser.h" + +extern "C" BASE_PLUGIN * GetPlugin(); + +#define RS_DEBUG (1) + +#define MAX_SHORT_PCKT (3) + +class REMOTE_SCRIPT; +//----------------------------------------------------------------------------- +class RS_ADD_USER_NONIFIER: public NOTIFIER_BASE +{ +public: + RS_ADD_USER_NONIFIER() {}; + virtual ~RS_ADD_USER_NONIFIER() {}; + + void SetRemoteScript(REMOTE_SCRIPT * a) { rs = a; } + void Notify(const user_iter & user); + +private: + REMOTE_SCRIPT * rs; +}; +//----------------------------------------------------------------------------- +class RS_DEL_USER_NONIFIER: public NOTIFIER_BASE +{ +public: + RS_DEL_USER_NONIFIER() {}; + virtual ~RS_DEL_USER_NONIFIER() {}; + + void SetRemoteScript(REMOTE_SCRIPT * a) { rs = a; } + void Notify(const user_iter & user); + +private: + REMOTE_SCRIPT * rs; +}; +//----------------------------------------------------------------------------- +template +class RS_CHG_AFTER_NOTIFIER: public PROPERTY_NOTIFIER_BASE +{ +public: + void Notify(const varParamType & oldValue, const varParamType & newValue); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() {return user; } + void SetRemoteScript(REMOTE_SCRIPT * a) { rs = a; } + +private: + user_iter user; + REMOTE_SCRIPT * rs; +}; +//----------------------------------------------------------------------------- +struct RS_USER +{ + RS_USER(); + RS_USER(const std::vector & r, user_iter it); +time_t lastSentTime; +user_iter user; +std::vector routers; +int shortPacketsCount; +}; +//----------------------------------------------------------------------------- +class RS_SETTINGS +{ +public: + RS_SETTINGS(); + virtual ~RS_SETTINGS() {}; + const std::string & GetStrError() const { return errorStr; }; + int ParseSettings(const MODULE_SETTINGS & s); + int GetSendPeriod() const { return sendPeriod; }; + int GetPort() const { return port; }; + const std::vector & GetSubnetsMap() const { return netRouters; }; + const std::vector & GetUserParams() const { return userParams; }; + const std::string & GetPassword() const { return password; }; + const std::string & GetMapFileName() const { return subnetFile; }; + +private: + int ParseIntInRange(const std::string & str, int min, int max, int * val); + int sendPeriod; + uint16_t port; + string errorStr; + std::vector netRouters; + std::vector userParams; + string password; + string subnetFile; +}; +//----------------------------------------------------------------------------- +class REMOTE_SCRIPT : public BASE_PLUGIN +{ +public: + REMOTE_SCRIPT(); + virtual ~REMOTE_SCRIPT(); + + void SetUsers(USERS * u) { users = u; }; + void SetTariffs(TARIFFS *) {}; + void SetAdmins(ADMINS *) {}; + void SetTraffcounter(TRAFFCOUNTER *) {}; + void SetStore(BASE_STORE *) {}; + void SetStgSettings(const SETTINGS *) {}; + void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + int ParseSettings(); + + int Start(); + int Stop(); + int Reload(); + bool IsRunning() { return isRunning; }; + + const std::string & GetStrError() const { return errorStr; }; + const std::string GetVersion() const { return "Remote script v 0.3"; }; + uint16_t GetStartPosition() const { return 20; }; + uint16_t GetStopPosition() const { return 20; }; + + void DelUser(user_iter u) { UnSetUserNotifier(u); }; + void AddUser(user_iter u) { SetUserNotifier(u); }; + + void ChangedIP(user_iter u, uint32_t oldIP, uint32_t newIP); + +private: + static void * Run(void *); + bool PrepareNet(); + bool FinalizeNet(); + + bool Send(uint32_t ip, RS_USER & rsu, bool forceDisconnect = false) const; + bool SendDirect(uint32_t ip, RS_USER & rsu, uint32_t routerIP, bool forceDisconnect = false) const; + bool PreparePacket(char * buf, size_t bufSize, uint32_t ip, RS_USER &rsu, bool forceDisconnect = false) const; + void PeriodicSend(); + + std::vector IP2Routers(uint32_t ip); + bool GetUsers(); + std::string GetUserParam(user_iter u, const std::string & paramName) const; + + void SetUserNotifier(user_iter u); + void UnSetUserNotifier(user_iter u); + + void InitEncrypt(BLOWFISH_CTX * ctx, const string & password) const; + void Encrypt(BLOWFISH_CTX * ctx, char * dst, const char * src, size_t len8) const; + + mutable BLOWFISH_CTX ctx; + + std::list > AfterChgIPNotifierList; + std::map authorizedUsers; + + mutable std::string errorStr; + RS_SETTINGS rsSettings; + MODULE_SETTINGS settings; + int sendPeriod; + int halfPeriod; + + bool nonstop; + bool isRunning; + + USERS * users; + + std::vector netRouters; + + pthread_t thread; + pthread_mutex_t mutex; + + int sock; + + RS_ADD_USER_NONIFIER onAddUserNotifier; + RS_DEL_USER_NONIFIER onDelUserNotifier; + + friend class UpdateRouter; + friend class DisconnectUser; +}; +//----------------------------------------------------------------------------- +class DisconnectUser : public std::unary_function &, void> +{ + public: + DisconnectUser(REMOTE_SCRIPT & rs) : rscript(rs) {}; + void operator()(std::pair & p) + { + rscript.Send(p.first, p.second, true); + } + private: + REMOTE_SCRIPT & rscript; +}; +//----------------------------------------------------------------------------- +inline void RS_ADD_USER_NONIFIER::Notify(const user_iter & user) +{ +printfd(__FILE__, "ADD_USER_NONIFIER\n"); +rs->AddUser(user); +} +//----------------------------------------------------------------------------- +inline void RS_DEL_USER_NONIFIER::Notify(const user_iter & user) +{ +printfd(__FILE__, "DEL_USER_NONIFIER\n"); +rs->DelUser(user); +} +//----------------------------------------------------------------------------- + +#endif diff --git a/projects/stargazer/plugins/other/rscript/send_functor.h b/projects/stargazer/plugins/other/rscript/send_functor.h new file mode 100644 index 00000000..479cdc3b --- /dev/null +++ b/projects/stargazer/plugins/other/rscript/send_functor.h @@ -0,0 +1,62 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.2 $ + $Date: 2010/03/04 12:11:09 $ + $Author: faust $ +*/ + +#ifndef __SEND_FUNCTOR_H__ +#define __SEND_FUNCTOR_H__ + +#include +#include + +#include + +#include "os_int.h" + +class PacketSender : public std::unary_function { + public: + PacketSender(int s, char * b, int l, uint16_t p) + : sock(s), + buffer(b), + length(l), + port(p) {}; + void operator() (uint32_t ip) + { + int res; + struct sockaddr_in sendAddr; + + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = port; + sendAddr.sin_addr.s_addr = ip; + + res = sendto(sock, buffer, length, 0, (struct sockaddr*)&sendAddr, sizeof(sendAddr)); + } + private: + int sock; + char * buffer; + int length; + uint16_t port; +}; + +#endif diff --git a/projects/stargazer/plugins/other/rscript/ur_functor.h b/projects/stargazer/plugins/other/rscript/ur_functor.h new file mode 100644 index 00000000..d9706a75 --- /dev/null +++ b/projects/stargazer/plugins/other/rscript/ur_functor.h @@ -0,0 +1,101 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.3 $ + $Date: 2010/03/04 12:07:03 $ + $Author: faust $ +*/ + +#ifndef __UR_FUNCTOR_H__ +#define __UR_FUNCTOR_H__ + +#include +#include +#include + +#include "rscript.h" +#include "os_int.h" + +#include "common.h" + +class UpdateRouter : public std::unary_function, void> +{ +public: + UpdateRouter(REMOTE_SCRIPT & t) + : obj(t) {}; + + void operator() (std::pair & val) + { + std::vector newRouters = obj.IP2Routers(val.first); + std::vector::const_iterator oldIt(val.second.routers.begin()); + std::vector::const_iterator newIt(newRouters.begin()); + val.second.shortPacketsCount = 0; + while (oldIt != val.second.routers.end() || + newIt != newRouters.end()) + { + if (oldIt == val.second.routers.end()) + { + if (newIt != newRouters.end()) + { + obj.SendDirect(val.first, val.second, *newIt); // Connect on new router + ++newIt; + } + } + else if (newIt == newRouters.end()) + { + //if (oldIt != newRouters.end()) + //{ // Already checked it + obj.SendDirect(val.first, val.second, *oldIt, true); // Disconnect on old router + ++oldIt; + //} + } + else if (*oldIt < *newIt) + { + obj.SendDirect(val.first, val.second, *oldIt, true); // Disconnect on old router + ++oldIt; + } + else if (*oldIt > *newIt) + { + obj.SendDirect(val.first, val.second, *newIt); // Connect on new router + ++newIt; + } + else + { + if (oldIt != val.second.routers.end()) + ++oldIt; + if (newIt != newRouters.end()) + ++newIt; + } + } + val.second.routers = newRouters; + /*if (val.second.souters != newRouters) + { + obj.Send(val.first, val.second, true); // Disconnect on old router + val.second.routerIP = obj.IP2Router(val.first); // Change router + val.second.shortPacketsCount = 0; // Reset packets count (to prevent alive send) + obj.Send(val.first, val.second); // Connect on new router + }*/ + } +private: + REMOTE_SCRIPT & obj; +}; + +#endif diff --git a/projects/stargazer/plugins/other/userstat/Makefile b/projects/stargazer/plugins/other/userstat/Makefile new file mode 100644 index 00000000..23470f88 --- /dev/null +++ b/projects/stargazer/plugins/other/userstat/Makefile @@ -0,0 +1,29 @@ +############################################################################### +# $Id: Makefile,v 1.1 2008/07/05 12:35:53 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +LIBS += -lexpat + +ifeq ($(OS),linux) +LIBS += -lpthread +endif + +ifeq ($(OS),bsd) +LIBS += -lc_r +endif + +ifeq ($(OS),bsd5) +LIBS += -lc_r +endif + +PROG = mod_userstat.so + +SRCS = ./userstat.cpp + +LIBS += -lstg_common \ + -lstg_crypto + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/other/userstat/datathread.cpp b/projects/stargazer/plugins/other/userstat/datathread.cpp new file mode 100644 index 00000000..40d44305 --- /dev/null +++ b/projects/stargazer/plugins/other/userstat/datathread.cpp @@ -0,0 +1,193 @@ +#include "datathread.h" + +bool DataThread::Init() +{ + parser = XML_ParserCreate(NULL); + if (!parser) { + printfd(__FILE__, "Error creating XML parser\n"); + } + XML_SetStartElementHandler(parser, StartHandler); + XML_SetEndElementHandler(parser, EndHandler); + XML_SetCharacterDataHandler(parser, DataHandler); +} + +DataThread::~DataThread() +{ + XML_ParserFree(parser); +} + +void * DataThread::Run(void * val) +{ + DataThread * dt = reinterpret_cast(val); + + running = true; + stoppped = false; + while (running) { + if (sock >= 0) { + done = false; + dt->Handle(); + done = true; + close(sock); + sock = -1; + } else { + usleep(1000); + } + } + stopped = true; + running = false; + + return NULL; +} + +void DataThread::Handle() +{ + int32_t size; + unsigned char * buf; + + if (!PrepareContext()) + return; + + res = read(sock, &size, sizeof(size)); + if (res != sizeof(size)) + { + printfd(__FILE__, "Reading stream size failed! Wanted %d bytes, got %d bytes.\n", sizeof(size), res); + return; + } + + printfd(__FILE__, "DataThread::Handle() size = %d\n", size); + + if (size < 0) { + printfd(__FILE__, "DataThread::Handle() Invalid data size.\n"); + return; + } + + buf = new unsigned char[size]; + res = read(sock, buf, size); + if (res != size) + { + printfd(__FILE__, "Reading stream failed! Wanted %d bytes, got %d bytes.\n", size, res); + return; + } + + std::string data; + Decode(buf, data, size); + + printfd(__FILE__, "Received XML: %s\n", data.c_str()); + + XML_ParserReset(parser, NULL); + + if (XML_Parse(parser, data.c_str(), data.length(), true) == XML_STATUS_OK) { + SendReply(); + } else { + SendError(); + } + + delete[] buf; + + return; +} + +bool DataThread::PrepareContext() +{ + int32_t size; + char * login; + + int res = read(sock, &size, sizeof(size)); + if (res != sizeof(size)) + { + printfd(__FILE__, "Reading stream size failed! Wanted %d bytes, got %d bytes.\n", sizeof(size), res); + return; + } + + printfd(__FILE__, "DataThread::Handle() size = %d\n", size); + + if (size < 0) { + printfd(__FILE__, "DataThread::Handle() Invalid data size.\n"); + return; + } + + login = new char[size]; + + res = read(sock, login, size); + if (res != size) + { + printfd(__FILE__, "Reading login failed! Wanted %d bytes, got %d bytes.\n", 32, res); + return; + } + + std::string l; + l.assign(login, size); + delete[] login; + + user_iter it; + if (users->FindByName(l, &it)) + { + printfd(__FILE__, "User '%s' not found.\n", login); + return; + } + + password = it->property.password; + + printfd(__FILE__, "DataThread::Handle() Requested user: '%s'\n", login); + printfd(__FILE__, "DataThread::Handle() Encryption initiated using password: '%s'\n", password.c_str()); + + char * key = new char[password.length()]; + strncpy(key, password.c_str(), password.length()); + + Blowfish_Init(&ctx, + reinterpret_cast(key), + password.length()); + delete[] key; + + return true; +} + +void DataThread::Encode(const std::string & src, char * dst, int size) +{ + const char * ptr = src.c_str(); + for (int i = 0; i < size / 8; ++i) { + uint32_t a; + uint32_t b; + a = n2l(ptr + i * 8); + b = n2l(ptr + i * 8 + 4); + Blowfish_Encrypt(&ctx, + &a, + &b); + l2n(a, dst + i * 8); + l2n(b, dst + i * 8 + 4); + } +} + +void DataThread::Decode(char * src, std::string & dst, int size) +{ + char tmp[9]; + tmp[8] = 0; + dst = ""; + + for (int i = 0; i < size / 8; ++i) { + uint32_t a; + uint32_t b; + a = n2l(src + i * 8); + b = n2l(src + i * 8 + 4); + Blowfish_Decrypt(&ctx, + &a, + &b); + l2n(a, tmp); + l2n(b, tmp + 4); + + dst += tmp; + } +} + +void StartHandler(void *data, const char *el, const char **attr) +{ + printfd(__FILE__, "Node: %s\n", el); +} + +void EndHandler(void *data, const char *el) +{ +} + +void DataHandler(void *data, const char *el) +{ +} diff --git a/projects/stargazer/plugins/other/userstat/datathread.h b/projects/stargazer/plugins/other/userstat/datathread.h new file mode 100644 index 00000000..cd9a87b6 --- /dev/null +++ b/projects/stargazer/plugins/other/userstat/datathread.h @@ -0,0 +1,58 @@ +#ifndef __DATATHREAD_H__ +#define __DATATHREAD_H__ + +#include "../../../users.h" +#include "base_store.h" +#include +#include + +class DataThread { +public: + DataThread() : done(false), sock(-1) { Init(); }; + DataThread(USERS * u, BASE_STORE * s, int sd) + : users(u), + store(s), + sock(sd), + done(false) + { + Init(); + }; + ~DataThread(); + + void SetUsers(USERS * u) { users = u; }; + void SetStore(BASE_STORE * s) { store = s; }; + void SetSocket(int s) { sock = s; }; + + bool isDone() const { return done; }; + bool Init(); + + bool Start(); + bool Stop(); + + static void * Run(void *); + + +private: + pthread_t thread; + USERS * users; + BASE_STORE * store; + XML_Parser parser; + int sock; + bool done; + bool running; + bool stopped; + BLOWFISH_CTX ctx; + std::string password; + std::string reply; + + void Handle(); + bool PrepareContect(); + void Encode(const std::string &, char *); + void Decode(char *, const std::string &); + + friend void StartHandler(void *data, const char *el, const char **attr); + friend void EndHandler(void *data, const char *el); + friend void DataHandler(void *data, const char *el); +}; + +#endif diff --git a/projects/stargazer/plugins/other/userstat/userstat.cpp b/projects/stargazer/plugins/other/userstat/userstat.cpp new file mode 100644 index 00000000..eca424e8 --- /dev/null +++ b/projects/stargazer/plugins/other/userstat/userstat.cpp @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include + +#include "common.h" +#include "../../../users.h" + +#include "userstat.h" + +BASE_PLUGIN * GetPlugin() +{ +return new USERSTAT(); +} + +USERSTAT::USERSTAT() + : maxThreads(16), + port(5555) +{ +xmlParser = XML_ParserCreate(NULL); +pthread_mutex_init(&mutex, NULL); +} + +USERSTAT::~USERSTAT() +{ +XML_ParserFree(xmlParser); +} + +int USERSTAT::ParseSettings() +{ +vector::iterator i; +string s; + +for(i = settings.moduleParams.begin(); i != settings.moduleParams.end(); ++i) + { + s = i->param; + transform(s.begin(), s.end(), s.begin(), USERSTAT::ToLower()); + if (s == "port") + { + if (str2x(*(i->value.begin()), port)) + { + errorStr = "'Port' parameter must be a numeric value"; + return -1; + } + } + if (s == "maxthreads") + { + if (str2x(*(i->value.begin()), maxThreads)) + { + errorStr = "'MaxThreads' parameter must be a numeric value"; + return -1; + } + } + } + +return 0; +} + +int USERSTAT::Prepare() +{ +listenSocket = socket(PF_INET, SOCK_STREAM, 0); + +if (listenSocket < 0) + { + errorStr = "Create USERSTAT socket failed."; + return -1; + } + +printfd(__FILE__, "USERSTAT::Prepare() socket - ok\n"); + +listenAddr.sin_family = PF_INET; +listenAddr.sin_port = htons(port); +listenAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); + +int lng = 1; + +if (0 != setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &lng, 4)) + { + errorStr = "Setsockopt failed. " + string(strerror(errno)); + return -1; + } + +printfd(__FILE__, "USERSTAT::Prepare() setsockopt - ok\n"); + +int res = bind(listenSocket, (struct sockaddr*)&listenAddr, sizeof(listenAddr)); + +if (res == -1) + { + errorStr = "Bind USERSTAT socket failed"; + return -1; + } + +printfd(__FILE__, "USERSTAT::Prepare() bind - ok port: %d\n", port); + +res = listen(listenSocket, 0); +if (res == -1) + { + errorStr = "Listen USERSTAT socket failed"; + return -1; + } +printfd(__FILE__, "USERSTAT::Prepare() listen - ok\n"); + +errorStr = ""; +return 0; +} + +int USERSTAT::Finalize() +{ +return close(listenSocket); +} + +int USERSTAT::Start() +{ +if (Prepare()) + { + return -1; + } +nonstop = true; +if (pthread_create(&thread, NULL, Run, this)) + { + errorStr = "Cannot create thread"; + return -1; + } + +return 0; +} + +int USERSTAT::Stop() +{ +nonstop = false; +if (pthread_kill(thread, SIGTERM)) + { + errorStr = "Cannot send signal to thread"; + return -1; + } +for (int i = 0; i < 25; i++) + { + if (!isRunning) + break; + + usleep(200000); + } +if (isRunning) + { + errorStr = "Cannot stop thread"; + return -1; + } +return 0; +} + +void * USERSTAT::Run(void * t) +{ +USERSTAT * us = reinterpret_cast(t); +pthread_t thread; +int outerSocket; +struct sockaddr_in outerAddr; +socklen_t outerAddrLen; +THREAD_INFO info; + +us->isRunning = true; +while (us->nonstop) + { + outerSocket = accept(us->listenSocket, (struct sockaddr *)&outerAddr, &outerAddrLen); + if (outerSocket > 0) + { + std::vector::iterator it; + us->pool.erase(remove_if(us->pool.begin(), us->pool.end(), USERSTAT::IsDone()), us->pool.end()); + + while (us->pool.size() >= us->maxThreads) + usleep(200000); + + info.users = us->users; + info.store = us->store; + info.outerSocket = outerSocket; + info.done = false; + us->pool.push_back(info); + it = us->pool.end(); + --it; + + if (pthread_create(&thread, NULL, Operate, &(*it))) + { + us->errorStr = "Cannot create thread"; + printfd(__FILE__, "Cannot create thread\n"); + } + it->thread = thread; + } + } +us->isRunning = false; +return NULL; +} + +void * USERSTAT::Operate(void * i) +{ + THREAD_INFO * info = reinterpret_cast(i); + unsigned char * buf; + int32_t size; + char * login; + + int res = read(info->outerSocket, &size, sizeof(size)); + if (res != sizeof(size)) + { + printfd(__FILE__, "Reading stream size failed! Wanted %d bytes, got %d bytes.\n", sizeof(size), res); + info->done = true; + return NULL; + } + + printfd(__FILE__, "USERSTAT::Operate() size = %d\n", size); + + if (size < 0) { + printfd(__FILE__, "USERSTAT::Operate() Invalid data size.\n"); + info->done = true; + return NULL; + } + + login = new char[size]; + + res = read(info->outerSocket, login, size); + if (res != size) + { + printfd(__FILE__, "Reading login failed! Wanted %d bytes, got %d bytes.\n", 32, res); + info->done = true; + return NULL; + } + + std::string l; + l.assign(login, size); + + res = read(info->outerSocket, &size, sizeof(size)); + if (res != sizeof(size)) + { + printfd(__FILE__, "Reading stream size failed! Wanted %d bytes, got %d bytes.\n", sizeof(size), res); + info->done = true; + return NULL; + } + + printfd(__FILE__, "USERSTAT::Operate() size = %d\n", size); + + if (size < 0) { + printfd(__FILE__, "USERSTAT::Operate() Invalid data size.\n"); + info->done = true; + return NULL; + } + + buf = new unsigned char[size]; + res = read(info->outerSocket, buf, size); + if (res != size) + { + printfd(__FILE__, "Reading stream failed! Wanted %d bytes, got %d bytes.\n", size, res); + info->done = true; + return NULL; + } + + printfd(__FILE__, "Received data: %s\n", buf); + + user_iter it; + if (info->users->FindByName(l, &it)) + { + printfd(__FILE__, "User '%s' not found.\n", login); + info->done = true; + return NULL; + } + + std::string password = it->property.password; + + printfd(__FILE__, "USERSTAT::Operate() Requested user: '%s'\n", login); + printfd(__FILE__, "USERSTAT::Operate() Encription init using password: '%s'\n", password.c_str()); + + BLOWFISH_CTX ctx; + char * key = new char[password.length()]; + strncpy(key, password.c_str(), password.length()); + + Blowfish_Init(&ctx, + reinterpret_cast(key), + password.length()); + + for (int i = 0; i < size / 8; ++i) { + uint32_t a; + uint32_t b; + a = n2l(buf + i * 8); + b = n2l(buf + i * 8 + 4); + Blowfish_Decrypt(&ctx, + &a, + &b); + l2n(a, buf + i * 8); + l2n(b, buf + i * 8 + 4); + } + + delete[] key; + + printfd(__FILE__, "Received XML: %s\n", buf); + + info->done = true; + delete[] buf; + return NULL; +} diff --git a/projects/stargazer/plugins/other/userstat/userstat.h b/projects/stargazer/plugins/other/userstat/userstat.h new file mode 100644 index 00000000..83f66202 --- /dev/null +++ b/projects/stargazer/plugins/other/userstat/userstat.h @@ -0,0 +1,124 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.1 $ + $Date: 2008/07/05 12:35:53 $ + $Author: faust $ +*/ + +#ifndef __USERSTAT_H__ +#define __USERSTAT_H__ + +#include +#include +#include + +#include "base_plugin.h" + +extern "C" BASE_PLUGIN * GetPlugin(); + +struct THREAD_INFO +{ + pthread_t thread; + USERS * users; + BASE_STORE * store; + int outerSocket; + bool done; +}; + +uint32_t n2l(unsigned char * c) +{ + uint32_t t = *c++ << 24; + t += *c++ << 16; + t += *c++ << 8; + t += *c; + return t; +} + +void l2n(uint32_t t, unsigned char * c) +{ + *c++ = t >> 24 & 0x000000FF; + *c++ = t >> 16 & 0x000000FF; + *c++ = t >> 8 & 0x000000FF; + *c++ = t & 0x000000FF; +} + +class USERSTAT : public BASE_PLUGIN +{ +public: + USERSTAT(); + ~USERSTAT(); + + virtual void SetUsers(USERS * u) { users = u; }; + virtual void SetTariffs(TARIFFS * t) {}; + virtual void SetAdmins(ADMINS * a) {}; + virtual void SetTraffcounter(TRAFFCOUNTER * tc) {}; + virtual void SetStore(BASE_STORE * st) { store = st; }; + virtual void SetStgSettings(const SETTINGS * s) {}; + virtual void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + virtual int ParseSettings(); + + virtual int Start(); + virtual int Stop(); + virtual bool IsRunning() { return isRunning; }; + virtual const string & GetStrError() const { return errorStr; }; + virtual const string GetVersion() const { return version; }; + virtual uint16_t GetStartPosition() const { return 0; }; + virtual uint16_t GetStopPosition() const { return 0; }; + +private: + struct IsDone : public unary_function + { + bool operator()(const THREAD_INFO & info) { return info.done; }; + }; + struct ToLower : public unary_function + { + char operator() (char c) const { return std::tolower(c); } + }; + bool isRunning; + bool nonstop; + std::string errorStr; + std::string version; + std::vector pool; + int listenSocket; + int threads; + unsigned maxThreads; + uint16_t port; + struct sockaddr_in listenAddr; + pthread_t thread; + pthread_mutex_t mutex; + USERS * users; + BASE_STORE * store; + + MODULE_SETTINGS settings; + + XML_Parser xmlParser; + + int Prepare(); + int Finalize(); + static void * Run(void *); + static void * Operate(void *); + + friend void ParseXMLStart(void * data, char * name, char ** attr); + friend void ParseXMLEnd(void * data, char * name); +}; + +#endif diff --git a/projects/stargazer/plugins/store/db/.libs/pg_driver.la b/projects/stargazer/plugins/store/db/.libs/pg_driver.la new file mode 120000 index 00000000..13e4dfc6 --- /dev/null +++ b/projects/stargazer/plugins/store/db/.libs/pg_driver.la @@ -0,0 +1 @@ +../pg_driver.la \ No newline at end of file diff --git a/projects/stargazer/plugins/store/db/Makefile b/projects/stargazer/plugins/store/db/Makefile new file mode 100644 index 00000000..586dfe22 --- /dev/null +++ b/projects/stargazer/plugins/store/db/Makefile @@ -0,0 +1,20 @@ +SOURCES=$(wildcard *.cpp) + +all: test_pg_driver pg_driver.so + +test_pg_driver: test_pg_driver.o + $(CXX) $^ -ldl -o $@ + +pg_driver.so: pg_driver.o + $(CXX) $^ -shared -lpq -o $@ + +clean: + rm -f *.d *.o *.so test_pg_driver + + +-include $(subst .cpp,.d,$(SOURCES)) + +%.d: %.cpp + @$(CC) -MM $(CXXFLAGS) $< > $@.$$$$; \ + sed 's,\($*\).o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$ diff --git a/projects/stargazer/plugins/store/db/pg_driver.cpp b/projects/stargazer/plugins/store/db/pg_driver.cpp new file mode 100644 index 00000000..4dda25d3 --- /dev/null +++ b/projects/stargazer/plugins/store/db/pg_driver.cpp @@ -0,0 +1,107 @@ +#include + +#include "pg_driver.h" + +BASE_DB * CreateDriver() +{ + return new PG_DRIVER(); +} + +void DestroyDriver(BASE_DB * drv) +{ + delete drv; +} + +PG_DRIVER::~PG_DRIVER() +{ + if (conn != NULL) + PQfinish(conn); +} + +bool PG_DRIVER::Connect() +{ + std::stringstream params; + params << "host=" << host << " " + << "dbname=" << database << " " + << "user=" << user << " " + << "password=" << password; + std::string str = params.str(); + conn = PQconnectdb(str.c_str()); + errorMsg = PQerrorMessage(conn); + return PQstatus(conn) != CONNECTION_OK; +} + +bool PG_DRIVER::Disconnect() +{ + if (PQstatus(conn) == CONNECTION_OK) { + PQfinish(conn); + conn = NULL; + } + + return false; +} + +bool PG_DRIVER::Query(const std::string & query) +{ + PQclear(result); + result = PQexec(conn, query.c_str()); + errorMsg = PQerrorMessage(conn); + tuples = PQntuples(result); + columns = PQnfields(result); + affected = atoi(PQcmdTuples(result)); + + cols.erase(cols.begin(), cols.end()); + cols.reserve(columns); + + if (tuples) { + for (int i = 0; i < columns; ++i) { + cols.push_back(PQfname(result, i)); + } + } + + if (!result) + return true; + + if (PQresultStatus(result) == PGRES_COMMAND_OK) + return false; + + if (PQresultStatus(result) == PGRES_TUPLES_OK) + return false; + + return true; +} + +bool PG_DRIVER::Start() +{ + return Query("BEGIN"); +} + +bool PG_DRIVER::Commit() +{ + return Query("COMMIT"); +} + +bool PG_DRIVER::Rollback() +{ + return Query("ROLLBACK"); +} + +BASE_DB::TUPLE PG_DRIVER::GetTuple(int n) const +{ + BASE_DB::TUPLE tuple; + + for (int i = 0; i < columns; ++i) + tuple[cols[i]] = PQgetvalue(result, n, i); + + return tuple; +} + +BASE_DB::TUPLES PG_DRIVER::GetResult() const +{ + BASE_DB::TUPLES tpls; + + for (int i = 0; i < tuples; ++i) + tpls.push_back(GetTuple(i)); + + return tpls; +} diff --git a/projects/stargazer/plugins/store/db/pg_driver.h b/projects/stargazer/plugins/store/db/pg_driver.h new file mode 100644 index 00000000..a6cee6e2 --- /dev/null +++ b/projects/stargazer/plugins/store/db/pg_driver.h @@ -0,0 +1,27 @@ +#ifndef __PG_DRIVER_H__ +#define __PG_DRIVER_H__ + +#include + +#include "base_db.h" + +class PG_DRIVER : public BASE_DB { +public: + virtual ~PG_DRIVER(); + + virtual bool Connect(); + virtual bool Disconnect(); + virtual bool Query(const std::string &); + virtual bool Start(); + virtual bool Commit(); + virtual bool Rollback(); + + virtual BASE_DB::TUPLES GetResult() const; + virtual BASE_DB::TUPLE GetTuple(int n = 0) const; + +private: + PGconn * conn; + PGresult * result; +}; + +#endif diff --git a/projects/stargazer/plugins/store/db/pg_driver.la b/projects/stargazer/plugins/store/db/pg_driver.la new file mode 100644 index 00000000..f090c519 --- /dev/null +++ b/projects/stargazer/plugins/store/db/pg_driver.la @@ -0,0 +1,35 @@ +# pg_driver.la - a libtool library file +# Generated by ltmain.sh - GNU libtool 1.5.26 (1.1220.2.493 2008/02/01 16:58:18) +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='' + +# Names of this library. +library_names='' + +# The name of the static archive. +old_library='pg_driver.a' + +# Libraries that this one depends upon. +dependency_libs=' -lpq' + +# Version information for pg_driver. +current= +age= +revision= + +# Is this an already installed library? +installed=no + +# Should we warn about portability when linking against -modules? +shouldnotlink=yes + +# Files to dlopen/dlpreopen +dlopen='' +dlpreopen='' + +# Directory that this library needs to be installed in: +libdir='' diff --git a/projects/stargazer/plugins/store/db/pg_driver.lo b/projects/stargazer/plugins/store/db/pg_driver.lo new file mode 100644 index 00000000..107fa03f --- /dev/null +++ b/projects/stargazer/plugins/store/db/pg_driver.lo @@ -0,0 +1,12 @@ +# pg_driver.lo - a libtool object file +# Generated by ltmain.sh - GNU libtool 1.5.26 (1.1220.2.493 2008/02/01 16:58:18) +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# Name of the PIC object. +pic_object='.libs/pg_driver.o' + +# Name of the non-PIC object. +non_pic_object='pg_driver.o' + diff --git a/projects/stargazer/plugins/store/db/test_pg_driver.cpp b/projects/stargazer/plugins/store/db/test_pg_driver.cpp new file mode 100644 index 00000000..57b94f3a --- /dev/null +++ b/projects/stargazer/plugins/store/db/test_pg_driver.cpp @@ -0,0 +1,147 @@ +#include +#include +#include + +#include + +#include "base_db.h" + +int main(int argc, char ** argv) +{ + BASE_DB * db; + + void * lh = dlopen("./pg_driver.so", RTLD_NOW | RTLD_GLOBAL); + + if (lh == NULL) { + std::cout << "Error loading shared object file pg_driver.so. Reason: '" << dlerror() << "'" << std::endl; + return EXIT_FAILURE; + } + + CreateDriverFn CreateDriver = reinterpret_cast(dlsym(lh, "CreateDriver")); + if (CreateDriver == NULL) { + std::cout << "Error getting symbol 'CreateDriver' address. Reason: '" << dlerror() << "'" << std::endl; + dlclose(lh); + return EXIT_FAILURE; + } + DestroyDriverFn DestroyDriver = reinterpret_cast(dlsym(lh, "DestroyDriver")); + if (DestroyDriver == NULL) { + std::cout << "Error getting symbol 'DestroyDriver' address. Reason: '" << dlerror() << "'" << std::endl; + dlclose(lh); + return EXIT_FAILURE; + } + + db = CreateDriver(); + + db->SetHost("localhost"); + db->SetDatabase("stargazer"); + db->SetUser("stg"); + db->SetPassword("123456"); + + if (db->Connect()) { + std::cout << "Error connecting db. Reason: '" << db->GetErrorMsg() << "'" << std::endl; + DestroyDriver(db); + dlclose(lh); + return EXIT_FAILURE; + } + + std::stringstream query; + query << "SELECT * FROM information_schema.tables"; + + if (db->Query(query.str())) { + std::cout << "Error querying db. Reason: '" << db->GetErrorMsg() << "'" << std::endl; + db->Disconnect(); + DestroyDriver(db); + dlclose(lh); + return EXIT_FAILURE; + } + + std::cout << "Tuples: " << db->GetTuples() << std::endl; + std::cout << "Columns: " << db->GetColumns() << std::endl; + BASE_DB::COLUMNS cols; + BASE_DB::COLUMNS::iterator it; + cols = db->GetColumnsNames(); + std::cout << "Cols count: " << cols.size() << std::endl; + std::cout << "Columns names:" << std::endl; + for (it = cols.begin(); it != cols.end(); ++it) + std::cout << *it << " "; + std::cout << std::endl; + + for (int i = 0; i < db->GetTuples(); ++i) { + BASE_DB::TUPLE tuple(db->GetTuple(i)); + BASE_DB::TUPLE::iterator it; + for (it = tuple.begin(); it != tuple.end(); ++it) + std::cout << it->second << " "; + std::cout << std::endl; + } + + query.str(""); + query << "create table test ( id bigserial, message text )"; + if (db->Query(query.str())) { + std::cout << "Error querying db. Reason: '" << db->GetErrorMsg() << "'" << std::endl; + db->Disconnect(); + DestroyDriver(db); + dlclose(lh); + return EXIT_FAILURE; + } + + query.str(""); + query << "insert into test (message) values ('abc');"; + query << "insert into test (message) values ('def');"; + query << "insert into test (message) values ('zxc');"; + if (db->Query(query.str())) { + std::cout << "Error querying db. Reason: '" << db->GetErrorMsg() << "'" << std::endl; + db->Disconnect(); + DestroyDriver(db); + dlclose(lh); + return EXIT_FAILURE; + } + + query.str(""); + query << "SELECT * FROM test"; + if (db->Query(query.str())) { + std::cout << "Error querying db. Reason: '" << db->GetErrorMsg() << "'" << std::endl; + db->Disconnect(); + DestroyDriver(db); + dlclose(lh); + return EXIT_FAILURE; + } + std::cout << "Tuples: " << db->GetTuples() << std::endl; + std::cout << "Columns: " << db->GetColumns() << std::endl; + cols = db->GetColumnsNames(); + std::cout << "Cols count: " << cols.size() << std::endl; + std::cout << "Columns names:" << std::endl; + for (it = cols.begin(); it != cols.end(); ++it) + std::cout << *it << " "; + std::cout << std::endl; + + for (int i = 0; i < db->GetTuples(); ++i) { + BASE_DB::TUPLE tuple(db->GetTuple(i)); + BASE_DB::TUPLE::iterator it; + for (it = tuple.begin(); it != tuple.end(); ++it) + std::cout << it->second << " "; + std::cout << std::endl; + } + + query.str(""); + query << "drop table test"; + if (db->Query(query.str())) { + std::cout << "Error querying db. Reason: '" << db->GetErrorMsg() << "'" << std::endl; + db->Disconnect(); + DestroyDriver(db); + dlclose(lh); + return EXIT_FAILURE; + } + + if (db->Disconnect()) { + std::cout << "Error connecting db. Reason: '" << db->GetErrorMsg() << "'" << std::endl; + DestroyDriver(db); + dlclose(lh); + return EXIT_FAILURE; + } + + DestroyDriver(db); + + dlclose(lh); + + return EXIT_SUCCESS; +} diff --git a/projects/stargazer/plugins/store/files/Makefile b/projects/stargazer/plugins/store/files/Makefile new file mode 100644 index 00000000..3357a501 --- /dev/null +++ b/projects/stargazer/plugins/store/files/Makefile @@ -0,0 +1,14 @@ +############################################################################### +# $Id: Makefile,v 1.16 2010/03/04 10:47:45 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_store_files.so + +SRCS = ./file_store.cpp + +STGLIBS = -lconffiles -lstg_common -lstg_locker -lstg_crypto + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/store/files/file_store.cpp b/projects/stargazer/plugins/store/files/file_store.cpp new file mode 100644 index 00000000..07638400 --- /dev/null +++ b/projects/stargazer/plugins/store/files/file_store.cpp @@ -0,0 +1,2217 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.67 $ + $Date: 2010/10/07 19:53:11 $ + $Author: faust $ + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "user_ips.h" +#include "user_conf.h" +#include "user_stat.h" +#include "stg_const.h" +#include "file_store.h" +#include "blowfish.h" +#include "stg_logger.h" +#include "stg_locker.h" + +#define DELETED_USERS_DIR "deleted_users" + +#define adm_enc_passwd "cjeifY8m3" + +using namespace std; + +const int pt_mega = 1024 * 1024; +//----------------------------------------------------------------------------- +class BAK_FILE +{ +public: + + //------------------------------------------------------------------------- + BAK_FILE(const string & fileName, bool removeBak) + : f(NULL), + removeBak(false) + { + bakSuccessed = false; + BAK_FILE::removeBak = removeBak; + fileNameBak = fileName + ".bak"; + /*struct stat fileStat; + if (stat(fileName.c_str(), &fileStat) == 0) + { + char * buff = new char[fileStat.st_size]; + f = fopen(fileName.c_str(), "rb"); + if(f) + { + fread(buff, 1, fileStat.st_size, f); + fclose(f); + + fileNameBak = fileName + ".bak"; + f = fopen(fileNameBak.c_str(), "wb"); + if(f) + { + fwrite(buff, 1, fileStat.st_size, f); + fclose(f); + } + } + + delete[] buff; + + bakSuccessed = true; + }*/ + if (rename(fileName.c_str(), fileNameBak.c_str())) + { + printfd(__FILE__, "BAK_FILE::BAK_FILE - rename failed. Message: '%s'\n", strerror(errno)); + } + else + { + bakSuccessed = true; + } + + } + //------------------------------------------------------------------------- + ~BAK_FILE() + { + if(bakSuccessed && removeBak) + { + if (unlink(fileNameBak.c_str())) + { + printfd(__FILE__, "BAK_FILE::~BAK_FILE - unlink failed. Message: '%s'\n", strerror(errno)); + } + } + } + //------------------------------------------------------------------------- + +private: + FILE * f; + bool bakSuccessed; + string fileNameBak; + bool removeBak; +}; +//----------------------------------------------------------------------------- +class FILES_STORE_CREATOR +{ +private: + FILES_STORE * fs; + +public: + FILES_STORE_CREATOR() + : fs(new FILES_STORE()) + { + }; + ~FILES_STORE_CREATOR() + { + delete fs; + }; + + FILES_STORE * GetStore() + { + return fs; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +FILES_STORE_CREATOR fsc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_STORE * GetStore() +{ +return fsc.GetStore(); +} +//----------------------------------------------------------------------------- +FILES_STORE_SETTINGS::FILES_STORE_SETTINGS() + : settings(NULL), + removeBak(true), + readBak(true) +{ +} +//----------------------------------------------------------------------------- +FILES_STORE_SETTINGS::~FILES_STORE_SETTINGS() +{ +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::ParseOwner(const vector & moduleParams, const string & owner, uid_t * uid) +{ +PARAM_VALUE pv; +pv.param = owner; +vector::const_iterator pvi; +pvi = find(moduleParams.begin(), moduleParams.end(), pv); +if (pvi == moduleParams.end()) + { + errorStr = "Parameter \'" + owner + "\' not found."; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +if (User2UID(pvi->value[0].c_str(), uid) < 0) + { + errorStr = "Parameter \'" + owner + "\': Unknown user \'" + pvi->value[0] + "\'"; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::ParseGroup(const vector & moduleParams, const string & group, gid_t * gid) +{ +PARAM_VALUE pv; +pv.param = group; +vector::const_iterator pvi; +pvi = find(moduleParams.begin(), moduleParams.end(), pv); +if (pvi == moduleParams.end()) + { + errorStr = "Parameter \'" + group + "\' not found."; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +if (Group2GID(pvi->value[0].c_str(), gid) < 0) + { + errorStr = "Parameter \'" + group + "\': Unknown group \'" + pvi->value[0] + "\'"; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::ParseYesNo(const string & value, bool * val) +{ +if (0 == strcasecmp(value.c_str(), "yes")) + { + *val = true; + return 0; + } +if (0 == strcasecmp(value.c_str(), "no")) + { + *val = false; + return 0; + } + +errorStr = "Incorrect value \'" + value + "\'."; +return -1; +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::ParseMode(const vector & moduleParams, const string & modeStr, mode_t * mode) +{ +PARAM_VALUE pv; +pv.param = modeStr; +vector::const_iterator pvi; +pvi = find(moduleParams.begin(), moduleParams.end(), pv); +if (pvi == moduleParams.end()) + { + errorStr = "Parameter \'" + modeStr + "\' not found."; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +if (Str2Mode(pvi->value[0].c_str(), mode) < 0) + { + errorStr = "Parameter \'" + modeStr + "\': Incorrect mode \'" + pvi->value[0] + "\'"; + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +if (ParseOwner(s.moduleParams, "StatOwner", &statUID) < 0) + return -1; +if (ParseGroup(s.moduleParams, "StatGroup", &statGID) < 0) + return -1; +if (ParseMode(s.moduleParams, "StatMode", &statMode) < 0) + return -1; + +if (ParseOwner(s.moduleParams, "ConfOwner", &confUID) < 0) + return -1; +if (ParseGroup(s.moduleParams, "ConfGroup", &confGID) < 0) + return -1; +if (ParseMode(s.moduleParams, "ConfMode", &confMode) < 0) + return -1; + +if (ParseOwner(s.moduleParams, "UserLogOwner", &userLogUID) < 0) + return -1; +if (ParseGroup(s.moduleParams, "UserLogGroup", &userLogGID) < 0) + return -1; +if (ParseMode(s.moduleParams, "UserLogMode", &userLogMode) < 0) + return -1; + +vector::const_iterator pvi; +PARAM_VALUE pv; +pv.param = "RemoveBak"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + removeBak = true; + } +else + { + if (ParseYesNo(pvi->value[0], &removeBak)) + { + printfd(__FILE__, "Cannot parse parameter 'RemoveBak'\n"); + return -1; + } + } + +pv.param = "ReadBak"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + readBak = false; + } +else + { + if (ParseYesNo(pvi->value[0], &readBak)) + { + printfd(__FILE__, "Cannot parse parameter 'ReadBak'\n"); + return -1; + } + } + +pv.param = "WorkDir"; +pvi = find(s.moduleParams.begin(), s.moduleParams.end(), pv); +if (pvi == s.moduleParams.end()) + { + errorStr = "Parameter \'WorkDir\' not found."; + printfd(__FILE__, "Parameter 'WorkDir' not found\n"); + return -1; + } + +workDir = pvi->value[0]; +if (workDir.size() && workDir[workDir.size() - 1] == '/') + { + workDir.resize(workDir.size() - 1); + } +usersDir = workDir + "/users/"; +tariffsDir = workDir + "/tariffs/"; +adminsDir = workDir + "/admins/"; + +return 0; +} +//----------------------------------------------------------------------------- +const string & FILES_STORE_SETTINGS::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::User2UID(const char * user, uid_t * uid) +{ +struct passwd * pw; +pw = getpwnam(user); +if (!pw) + { + errorStr = string("User \'") + string(user) + string("\' not found in system."); + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +*uid = pw->pw_uid; +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::Group2GID(const char * gr, gid_t * gid) +{ +struct group * grp; +grp = getgrnam(gr); +if (!grp) + { + errorStr = string("Group \'") + string(gr) + string("\' not found in system."); + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +*gid = grp->gr_gid; +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE_SETTINGS::Str2Mode(const char * str, mode_t * mode) +{ +char a; +char b; +char c; +if (strlen(str) > 3) + { + errorStr = string("Error parsing mode \'") + str + string("\'"); + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +for (int i = 0; i < 3; i++) + if (str[i] > '7' || str[i] < '0') + { + errorStr = string("Error parsing mode \'") + str + string("\'"); + printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +a = str[0] - '0'; +b = str[1] - '0'; +c = str[2] - '0'; + +*mode = ((mode_t)c) + ((mode_t)b << 3) + ((mode_t)a << 6); + +return 0; +} +//----------------------------------------------------------------------------- +string FILES_STORE_SETTINGS::GetWorkDir() const +{ +return workDir; +} +//----------------------------------------------------------------------------- +string FILES_STORE_SETTINGS::GetUsersDir() const +{ +return usersDir; +} +//----------------------------------------------------------------------------- +string FILES_STORE_SETTINGS::GetAdminsDir() const +{ +return adminsDir; +} +//----------------------------------------------------------------------------- +string FILES_STORE_SETTINGS::GetTariffsDir() const +{ +return tariffsDir; +} +//----------------------------------------------------------------------------- +mode_t FILES_STORE_SETTINGS::GetStatMode() const +{ +return statMode; +} +//----------------------------------------------------------------------------- +mode_t FILES_STORE_SETTINGS::GetStatModeDir() const +{ +mode_t mode = statMode; +if (statMode & S_IRUSR) mode |= S_IXUSR; +if (statMode & S_IRGRP) mode |= S_IXGRP; +if (statMode & S_IROTH) mode |= S_IXOTH; +return mode; +} +//----------------------------------------------------------------------------- +uid_t FILES_STORE_SETTINGS::GetStatUID() const +{ +return statUID; +} +//----------------------------------------------------------------------------- +gid_t FILES_STORE_SETTINGS::GetStatGID() const +{ +return statGID; +} +//----------------------------------------------------------------------------- +mode_t FILES_STORE_SETTINGS::GetConfMode() const +{ +return confMode; +} +//----------------------------------------------------------------------------- +mode_t FILES_STORE_SETTINGS::GetConfModeDir() const +{ +mode_t mode = confMode; +if (statMode & S_IRUSR) mode |= S_IXUSR; +if (statMode & S_IRGRP) mode |= S_IXGRP; +if (statMode & S_IROTH) mode |= S_IXOTH; +return mode; +} +//----------------------------------------------------------------------------- +uid_t FILES_STORE_SETTINGS::GetConfUID() const +{ +return confUID; +} +//----------------------------------------------------------------------------- +gid_t FILES_STORE_SETTINGS::GetConfGID() const +{ +return confGID; +} +//----------------------------------------------------------------------------- +mode_t FILES_STORE_SETTINGS::GetLogMode() const +{ +return userLogMode; +} +//----------------------------------------------------------------------------- +uid_t FILES_STORE_SETTINGS::GetLogUID() const +{ +return userLogUID; +} +//----------------------------------------------------------------------------- +gid_t FILES_STORE_SETTINGS::GetLogGID() const +{ +return userLogGID; +} +//----------------------------------------------------------------------------- +bool FILES_STORE_SETTINGS::GetRemoveBak() const +{ +return removeBak; +} +//----------------------------------------------------------------------------- +bool FILES_STORE_SETTINGS::GetReadBak() const +{ +return readBak; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +/*BASE_SETTINGS * FILES_STORE::GetStoreSettings() +{ +return &storeSettings; +}*/ +//----------------------------------------------------------------------------- +FILES_STORE::FILES_STORE() +{ +version = "file_store v.1.04"; + +pthread_mutexattr_t attr; +pthread_mutexattr_init(&attr); +pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +pthread_mutex_init(&mutex, &attr); +}; +//----------------------------------------------------------------------------- +FILES_STORE::~FILES_STORE() +{ + +}; +//----------------------------------------------------------------------------- +void FILES_STORE::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int FILES_STORE::ParseSettings() +{ +int ret = storeSettings.ParseSettings(settings); +if (ret) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = storeSettings.GetStrError(); + } +return ret; +} +//----------------------------------------------------------------------------- +const string & FILES_STORE::GetStrError() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return errorStr; +} +//----------------------------------------------------------------------------- +const string & FILES_STORE::GetVersion() const +{ +return version; +} +//----------------------------------------------------------------------------- +int FILES_STORE::GetFilesList(vector * filesList, const string & directory, mode_t mode, const string & ext) const +{ +// æÕÎËÃÉÑ ÐÒÏÓÍÁÔÒÉ×ÁÅÔ ÓÏÄÅÒÖÉÍÏÅ ÄÉÒÅËÔÏÒÉÉ +DIR * d; +string str; +struct stat st; +dirent * dir; + +filesList->clear(); + +d = opendir(directory.c_str()); + +if (!d) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Directory \'" + directory + "\' cannot be opened."; + //printfd(__FILE__, "%s\n", errorStr.c_str()); + return -1; + } + +int d_nameLen; +int extLen = ext.size() ; +while ((dir = readdir(d))) + { + if (strcmp(dir->d_name, ".") && strcmp(dir->d_name, "..")) + { + str = directory + "/" + string(dir->d_name); + if (!stat(str.c_str(), &st)) + { + if (st.st_mode & mode) // ïÔÓÅ× ÆÁÊÌÏ× or directories + { + d_nameLen = strlen(dir->d_name); + if (d_nameLen > extLen) + { + if (strcmp(dir->d_name + (d_nameLen - extLen), ext.c_str()) == 0) + { + dir->d_name[d_nameLen - extLen] = 0; + filesList->push_back(dir->d_name); + } + } + } + } + } + } + +closedir(d); +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::GetUsersList(vector * usersList) const +{ +return GetFilesList(usersList, storeSettings.GetUsersDir(), S_IFDIR, ""); +} +//----------------------------------------------------------------------------- +int FILES_STORE::GetAdminsList(vector * adminsList) const +{ +return GetFilesList(adminsList, storeSettings.GetAdminsDir(), S_IFREG, ".adm"); +} +//----------------------------------------------------------------------------- +int FILES_STORE::GetTariffsList(vector * tariffsList) const +{ +return GetFilesList(tariffsList, storeSettings.GetTariffsDir(), S_IFREG, ".tf"); +} +//----------------------------------------------------------------------------- +int FILES_STORE::RemoveDir(const char * path) const +{ +vector filesList; + +GetFilesList(&filesList, path, S_IFREG, ""); + +for (unsigned i = 0; i < filesList.size(); i++) + { + string file = path + string("/") + filesList[i]; + if (unlink(file.c_str())) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "unlink failed. Message: '"; + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::RemoveDir - unlink failed. Message: '%s'\n", strerror(errno)); + return -1; + } + } + +GetFilesList(&filesList, path, S_IFDIR, ""); + +for (unsigned i = 0; i < filesList.size(); i++) + { + string dir = string(path) + "/" + filesList[i]; + RemoveDir(dir.c_str()); + } + +if (rmdir(path)) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "rmdir failed. Message: '"; + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::RemoveDir - rmdir failed. Message: '%s'\n", strerror(errno)); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::AddUser(const string & login) const +{ +FILE * f; +string fileName; + +strprintf(&fileName, "%s%s", storeSettings.GetUsersDir().c_str(), login.c_str()); + +if (mkdir(fileName.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("mkdir failed. Message: '") + strerror(errno) + "'"; + printfd(__FILE__, "FILES_STORE::AddUser - mkdir failed. Message: '%s'\n", strerror(errno)); + return -1; + } + +strprintf(&fileName, "%s%s/conf", storeSettings.GetUsersDir().c_str(), login.c_str()); +f = fopen(fileName.c_str(), "wt"); +if (f) + { + if (fprintf(f, "\n") < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "fprintf failed. Message: '"; + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::AddUser - fprintf failed. Message: '%s'\n", strerror(errno)); + return -1; + } + fclose(f); + } +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot create file \"" + fileName + "\'"; + printfd(__FILE__, "FILES_STORE::AddUser - fopen failed. Message: '%s'\n", strerror(errno)); + return -1; + } + +strprintf(&fileName, "%s%s/stat", storeSettings.GetUsersDir().c_str(), login.c_str()); +f = fopen(fileName.c_str(), "wt"); +if (f) + { + if (fprintf(f, "\n") < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "fprintf failed. Message: '"; + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::AddUser - fprintf failed. Message: '%s'\n", strerror(errno)); + return -1; + } + fclose(f); + } +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot create file \"" + fileName + "\'"; + printfd(__FILE__, "FILES_STORE::AddUser - fopen failed. Message: '%s'\n", strerror(errno)); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::DelUser(const string & login) const +{ +string dirName; +string dirName1; + +strprintf(&dirName, "%s/"DELETED_USERS_DIR, storeSettings.GetWorkDir().c_str()); +if (access(dirName.c_str(), F_OK) != 0) + { + if (mkdir(dirName.c_str(), 0700) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Directory '" + dirName + "' cannot be created."; + printfd(__FILE__, "FILES_STORE::DelUser - mkdir failed. Message: '%s'\n", strerror(errno)); + return -1; + } + } + +if (access(dirName.c_str(), F_OK) == 0) + { + strprintf(&dirName, "%s/"DELETED_USERS_DIR"/%s.%lu", storeSettings.GetWorkDir().c_str(), login.c_str(), time(NULL)); + strprintf(&dirName1, "%s/%s", storeSettings.GetUsersDir().c_str(), login.c_str()); + if (rename(dirName1.c_str(), dirName.c_str())) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error moving dir from " + dirName1 + " to " + dirName; + printfd(__FILE__, "FILES_STORE::DelUser - rename failed. Message: '%s'\n", strerror(errno)); + return -1; + } + } +else + { + strprintf(&dirName, "%s/%s", storeSettings.GetUsersDir().c_str(), login.c_str()); + if (RemoveDir(dirName.c_str())) + { + return -1; + } + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::RestoreUserConf(USER_CONF * conf, const string & login) const +{ +string fileName; +fileName = storeSettings.GetUsersDir() + "/" + login + "/conf"; +if (RestoreUserConf(conf, login, fileName)) + { + if (!storeSettings.GetReadBak()) + { + return -1; + } + return RestoreUserConf(conf, login, fileName + ".bak"); + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::RestoreUserConf(USER_CONF * conf, const string & login, const string & fileName) const +{ +CONFIGFILE cf(fileName); +int e = cf.Error(); +string str; + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - conf read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadString("Password", &conf->password, "") < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read. Parameter Password."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - password read failed for user '%s'\n", login.c_str()); + return -1; + } +if (conf->password.empty()) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' password is blank."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - password is blank for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadString("tariff", &conf->tariffName, "") < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read. Parameter Tariff."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - tariff read failed for user '%s'\n", login.c_str()); + return -1; + } +if (conf->tariffName.empty()) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' tariff is blank."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - tariff is blank for user '%s'\n", login.c_str()); + return -1; + } + +string ipStr; +cf.ReadString("IP", &ipStr, "?"); +USER_IPS i; +try + { + i = StrToIPS(ipStr); + } +catch (string s) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read. Parameter IP address. " + s; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - ip read failed for user '%s'\n", login.c_str()); + return -1; + } +conf->ips = i; + +if (cf.ReadInt("alwaysOnline", &conf->alwaysOnline, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read. Parameter AlwaysOnline."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - alwaysonline read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadInt("down", &conf->disabled, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read. Parameter Down."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - down read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadInt("passive", &conf->passive, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read. Parameter Passive."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - passive read failed for user '%s'\n", login.c_str()); + return -1; + } + +cf.ReadInt("DisabledDetailStat", &conf->disabledDetailStat, 0); +cf.ReadTime("CreditExpire", &conf->creditExpire, 0); +cf.ReadString("TariffChange", &conf->nextTariff, ""); +cf.ReadString("Group", &conf->group, ""); +cf.ReadString("RealName", &conf->realName, ""); +cf.ReadString("Address", &conf->address, ""); +cf.ReadString("Phone", &conf->phone, ""); +cf.ReadString("Note", &conf->note, ""); +cf.ReadString("email", &conf->email, ""); + +char userdataName[12]; +for (int i = 0; i < USERDATA_NUM; i++) + { + snprintf(userdataName, 12, "Userdata%d", i); + cf.ReadString(userdataName, &conf->userdata[i], ""); + } + +if (cf.ReadDouble("Credit", &conf->credit, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' data not read. Parameter Credit."; + printfd(__FILE__, "FILES_STORE::RestoreUserConf - credit read failed for user '%s'\n", login.c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::RestoreUserStat(USER_STAT * stat, const string & login) const +{ +string fileName; +fileName = storeSettings.GetUsersDir() + "/" + login + "/stat"; + +if (RestoreUserStat(stat, login, fileName)) + { + if (!storeSettings.GetReadBak()) + { + return -1; + } + return RestoreUserStat(stat, login, fileName + ".bak"); + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::RestoreUserStat(USER_STAT * stat, const string & login, const string & fileName) const +{ +CONFIGFILE cf(fileName); + +int e = cf.Error(); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Cannot open file " + fileName + "."; + printfd(__FILE__, "FILES_STORE::RestoreUserStat - stat read failed for user '%s'\n", login.c_str()); + return -1; + } + +char s[22]; + +for (int i = 0; i < DIR_NUM; i++) + { + uint64_t traff; + snprintf(s, 22, "D%d", i); + if (cf.ReadULongLongInt(s, &traff, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter " + string(s); + printfd(__FILE__, "FILES_STORE::RestoreUserStat - download stat read failed for user '%s'\n", login.c_str()); + return -1; + } + stat->down[i] = traff; + + snprintf(s, 22, "U%d", i); + if (cf.ReadULongLongInt(s, &traff, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter " + string(s); + printfd(__FILE__, "FILES_STORE::RestoreUserStat - upload stat read failed for user '%s'\n", login.c_str()); + return -1; + } + stat->up[i] = traff; + } + +if (cf.ReadDouble("Cash", &stat->cash, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter Cash"; + printfd(__FILE__, "FILES_STORE::RestoreUserStat - cash read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadDouble("FreeMb", &stat->freeMb, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter FreeMb"; + printfd(__FILE__, "FILES_STORE::RestoreUserStat - freemb read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadTime("LastCashAddTime", &stat->lastCashAddTime, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter LastCashAddTime"; + printfd(__FILE__, "FILES_STORE::RestoreUserStat - lastcashaddtime read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadTime("PassiveTime", &stat->passiveTime, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter PassiveTime"; + printfd(__FILE__, "FILES_STORE::RestoreUserStat - passivetime read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadDouble("LastCashAdd", &stat->lastCashAdd, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter LastCashAdd"; + printfd(__FILE__, "FILES_STORE::RestoreUserStat - lastcashadd read failed for user '%s'\n", login.c_str()); + return -1; + } + +if (cf.ReadTime("LastActivityTime", &stat->lastActivityTime, 0) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "User \'" + login + "\' stat not read. Parameter LastActivityTime"; + printfd(__FILE__, "FILES_STORE::RestoreUserStat - lastactivitytime read failed for user '%s'\n", login.c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::SaveUserConf(const USER_CONF & conf, const string & login) const +{ +string fileName; +fileName = storeSettings.GetUsersDir() + "/" + login + "/conf"; + +BAK_FILE bakFile(fileName, storeSettings.GetRemoveBak()); + +if (access(fileName.c_str(), W_OK) != 0) + { + FILE * f; + f = fopen(fileName.c_str(), "wb"); + if (f) + fclose(f); + } + +CONFIGFILE cfstat(fileName); + +int e = cfstat.Error(); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("User \'") + login + "\' conf not written\n"; + printfd(__FILE__, "FILES_STORE::SaveUserConf - conf write failed for user '%s'\n", login.c_str()); + return -1; + } + +e = chmod(fileName.c_str(), storeSettings.GetConfMode()); +e += chown(fileName.c_str(), storeSettings.GetConfUID(), storeSettings.GetConfGID()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::SaveUserConf - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +cfstat.WriteString("Password", conf.password); +cfstat.WriteInt ("Passive", conf.passive); +cfstat.WriteInt ("Down", conf.disabled); +cfstat.WriteInt("DisabledDetailStat", conf.disabledDetailStat); +cfstat.WriteInt ("AlwaysOnline", conf.alwaysOnline); +cfstat.WriteString("Tariff", conf.tariffName); +cfstat.WriteString("Address", conf.address); +cfstat.WriteString("Phone", conf.phone); +cfstat.WriteString("Email", conf.email); +cfstat.WriteString("Note", conf.note); +cfstat.WriteString("RealName", conf.realName); +cfstat.WriteString("Group", conf.group); +cfstat.WriteDouble("Credit", conf.credit); +cfstat.WriteString("TariffChange", conf.nextTariff); + +char userdataName[12]; +for (int i = 0; i < USERDATA_NUM; i++) + { + snprintf(userdataName, 12, "Userdata%d", i); + cfstat.WriteString(userdataName, conf.userdata[i]); + } +cfstat.WriteInt("CreditExpire", conf.creditExpire); + +stringstream ipStr; +ipStr << conf.ips; +cfstat.WriteString("IP", ipStr.str()); + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::SaveUserStat(const USER_STAT & stat, const string & login) const +{ +char s[22]; +string fileName; +fileName = storeSettings.GetUsersDir() + "/" + login + "/stat"; + +BAK_FILE bakFile(fileName, storeSettings.GetRemoveBak()); + +if (access(fileName.c_str(), W_OK) != 0) + { + FILE * f; + f = fopen(fileName.c_str(), "wb"); + if (f) + fclose(f); + } + +CONFIGFILE cfstat(fileName); +int e = cfstat.Error(); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("User \'") + login + "\' stat not written\n"; + printfd(__FILE__, "FILES_STORE::SaveUserStat - stat write failed for user '%s'\n", login.c_str()); + return -1; + } + +for (int i = 0; i < DIR_NUM; i++) + { + snprintf(s, 22, "D%d", i); + cfstat.WriteInt(s, stat.down[i]); + snprintf(s, 22, "U%d", i); + cfstat.WriteInt(s, stat.up[i]); + } + +cfstat.WriteDouble("Cash", stat.cash); +cfstat.WriteDouble("FreeMb", stat.freeMb); +cfstat.WriteDouble("LastCashAdd", stat.lastCashAdd); +cfstat.WriteInt("LastCashAddTime", stat.lastCashAddTime); +cfstat.WriteInt("PassiveTime", stat.passiveTime); +cfstat.WriteInt("LastActivityTime", stat.lastActivityTime); + +e = chmod(fileName.c_str(), storeSettings.GetStatMode()); +e += chown(fileName.c_str(), storeSettings.GetStatUID(), storeSettings.GetStatGID()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::SaveUserStat - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::WriteLogString(const string & str, const string & login) const +{ +FILE * f; +time_t tm = time(NULL); +string fileName; +fileName = storeSettings.GetUsersDir() + "/" + login + "/log"; +f = fopen(fileName.c_str(), "at"); + +if (f) + { + fprintf(f, "%s", LogDate(tm)); + fprintf(f, " -- "); + fprintf(f, "%s", str.c_str()); + fprintf(f, "\n"); + fclose(f); + } +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot open \'" + fileName + "\'"; + printfd(__FILE__, "FILES_STORE::WriteLogString - log write failed for user '%s'\n", login.c_str()); + return -1; + } + +int e = chmod(fileName.c_str(), storeSettings.GetLogMode()); +e += chown(fileName.c_str(), storeSettings.GetLogUID(), storeSettings.GetLogGID()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::WriteLogString - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::WriteLog2String(const string & str, const string & login) const +{ +FILE * f; +time_t tm = time(NULL); +string fileName; +fileName = storeSettings.GetUsersDir() + "/" + login + "/log2"; +f = fopen(fileName.c_str(), "at"); + +if (f) + { + fprintf(f, "%s", LogDate(tm)); + fprintf(f, " -- "); + fprintf(f, "%s", str.c_str()); + fprintf(f, "\n"); + fclose(f); + } +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot open \'" + fileName + "\'"; + printfd(__FILE__, "FILES_STORE::WriteLogString - log write failed for user '%s'\n", login.c_str()); + return -1; + } + +int e = chmod(fileName.c_str(), storeSettings.GetLogMode()); +e += chown(fileName.c_str(), storeSettings.GetLogUID(), storeSettings.GetLogGID()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::WriteLogString - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message) const +{ +string userLogMsg = "Admin \'" + admLogin + "\', " + inet_ntostring(admIP) + ": \'" + + paramName + "\' parameter changed from \'" + oldValue + + "\' to \'" + newValue + "\'. " + message; + +return WriteLogString(userLogMsg, login); +} +//----------------------------------------------------------------------------- +int FILES_STORE::WriteUserConnect(const string & login, uint32_t ip) const +{ +string logStr = "Connect, " + inet_ntostring(ip); +if (WriteLogString(logStr, login)) + return -1; +return WriteLog2String(logStr, login); +} +//----------------------------------------------------------------------------- +int FILES_STORE::WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const +{ +stringstream logStr; +logStr << "Disconnect, "; +/*stringstream sssu; +stringstream sssd; +stringstream ssmu; +stringstream ssmd; +stringstream sscash; + +ssmu << up; +ssmd << down; + +sssu << sessionUp; +sssd << sessionDown; + +sscash << cash;*/ + +logStr << " session upload: \'" + << sessionUp + << "\' session download: \'" + << sessionDown + << "\' month upload: \'" + << up + << "\' month download: \'" + << down + << "\' cash: \'" + << cash + << "\'"; + +if (WriteLogString(logStr.str(), login)) + return -1; + +logStr << " freeMb: \'" + << freeMb + << "\'" + << " reason: \'" + << reason + << "\'"; + +return WriteLog2String(logStr.str(), login); +} +//----------------------------------------------------------------------------- +int FILES_STORE::SaveMonthStat(const USER_STAT & stat, int month, int year, const string & login) const +{ +string str; +CONFIGFILE * s; +int e; +FILE *f; + +strprintf(&str,"%s/%s/stat.%d.%02d", + storeSettings.GetUsersDir().c_str(), login.c_str(), year + 1900, month + 1); + +if ((f = fopen(str.c_str(), "w"))) + { + fprintf(f, "\n"); + fclose(f); + } + +s = new CONFIGFILE(str); +e = s->Error(); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot create file " + str; + printfd(__FILE__, "FILES_STORE::SaveMonthStat - month stat write failed for user '%s'\n", login.c_str()); + return -1; + } + +char dirName[3]; + +for (int i = 0; i < DIR_NUM; i++) + { + snprintf(dirName, 3, "U%d", i); + s->WriteInt(dirName, stat.up[i]); + snprintf(dirName, 3, "D%d", i); + s->WriteInt(dirName, stat.down[i]); + } + +s->WriteDouble("cash", stat.cash); + +delete s; + +return 0; +} +//-----------------------------------------------------------------------------*/ +int FILES_STORE::AddAdmin(const string & login) const +{ +string fileName; +strprintf(&fileName, "%s/%s.adm", storeSettings.GetAdminsDir().c_str(), login.c_str()); +FILE * f; +f = fopen(fileName.c_str(), "wt"); +if (f) + { + fprintf(f, "\n"); + fclose(f); + return 0; + } + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +errorStr = "Cannot create file " + fileName; +printfd(__FILE__, "FILES_STORE::AddAdmin - failed to add admin '%s'\n", login.c_str()); +return -1; +} +//-----------------------------------------------------------------------------*/ +int FILES_STORE::DelAdmin(const string & login) const +{ +string fileName; +strprintf(&fileName, "%s/%s.adm", storeSettings.GetAdminsDir().c_str(), login.c_str()); +if (unlink(fileName.c_str())) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "unlink failed. Message: '"; + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::DelAdmin - unlink failed. Message: '%s'\n", strerror(errno)); + } +return 0; +} +//-----------------------------------------------------------------------------*/ +int FILES_STORE::SaveAdmin(const ADMIN_CONF & ac) const +{ +char passwordE[2 * ADM_PASSWD_LEN + 2]; +char pass[ADM_PASSWD_LEN + 1]; +char adminPass[ADM_PASSWD_LEN + 1]; + +string fileName; + +strprintf(&fileName, "%s/%s.adm", storeSettings.GetAdminsDir().c_str(), ac.login.c_str()); + +CONFIGFILE cf(fileName); + +int e = cf.Error(); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot write admin " + ac.login + ". " + fileName; + printfd(__FILE__, "FILES_STORE::SaveAdmin - failed to save admin '%s'\n", ac.login.c_str()); + return -1; + } + +memset(pass, 0, sizeof(pass)); +memset(adminPass, 0, sizeof(adminPass)); + +BLOWFISH_CTX ctx; +EnDecodeInit(adm_enc_passwd, strlen(adm_enc_passwd), &ctx); + +strncpy(adminPass, ac.password.c_str(), ADM_PASSWD_LEN); +adminPass[ADM_PASSWD_LEN - 1] = 0; + +for (int i = 0; i < ADM_PASSWD_LEN/8; i++) + { + EncodeString(pass + 8*i, adminPass + 8*i, &ctx); + } + +pass[ADM_PASSWD_LEN - 1] = 0; +Encode12(passwordE, pass, ADM_PASSWD_LEN); +//printfd(__FILE__, "passwordE %s\n", passwordE); + +cf.WriteString("password", passwordE); +cf.WriteInt("ChgConf", ac.priv.userConf); +cf.WriteInt("ChgPassword", ac.priv.userPasswd); +cf.WriteInt("ChgStat", ac.priv.userStat); +cf.WriteInt("ChgCash", ac.priv.userCash); +cf.WriteInt("UsrAddDel", ac.priv.userAddDel); +cf.WriteInt("ChgTariff", ac.priv.tariffChg); +cf.WriteInt("ChgAdmin", ac.priv.adminChg); + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::RestoreAdmin(ADMIN_CONF * ac, const string & login) const +{ +string fileName; +strprintf(&fileName, "%s/%s.adm", storeSettings.GetAdminsDir().c_str(), login.c_str()); +CONFIGFILE cf(fileName); +char pass[ADM_PASSWD_LEN + 1]; +char password[ADM_PASSWD_LEN + 1]; +char passwordE[2*ADM_PASSWD_LEN + 2]; +BLOWFISH_CTX ctx; + +string p; + +if (cf.Error()) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot open " + fileName; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - failed to restore admin '%s'\n", ac->login.c_str()); + return -1; + } + +int a; + +if (cf.ReadString("password", &p, "*")) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter password"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - password read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +memset(passwordE, 0, sizeof(passwordE)); +strncpy(passwordE, p.c_str(), 2*ADM_PASSWD_LEN); + +//printfd(__FILE__, "passwordE %s\n", passwordE); + +memset(pass, 0, sizeof(pass)); + +if (passwordE[0] != 0) + { + Decode21(pass, passwordE); + EnDecodeInit(adm_enc_passwd, strlen(adm_enc_passwd), &ctx); + + for (int i = 0; i < ADM_PASSWD_LEN/8; i++) + { + DecodeString(password + 8*i, pass + 8*i, &ctx); + } + } +else + { + password[0] = 0; + } + +ac->password = password; + +if (cf.ReadInt("ChgConf", &a, 0) == 0) + ac->priv.userConf = a; +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter ChgConf"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - chgconf read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +if (cf.ReadInt("ChgPassword", &a, 0) == 0) + ac->priv.userPasswd = a; +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter ChgPassword"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - chgpassword read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +if (cf.ReadInt("ChgStat", &a, 0) == 0) + ac->priv.userStat = a; +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter ChgStat"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - chgstat read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +if (cf.ReadInt("ChgCash", &a, 0) == 0) + ac->priv.userCash = a; +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter ChgCash"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - chgcash read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +if (cf.ReadInt("UsrAddDel", &a, 0) == 0) + ac->priv.userAddDel = a; +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter UsrAddDel"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - usradddel read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +if (cf.ReadInt("ChgAdmin", &a, 0) == 0) + ac->priv.adminChg = a; +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter ChgAdmin"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - chgadmin read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +if (cf.ReadInt("ChgTariff", &a, 0) == 0) + ac->priv.tariffChg = a; +else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error in parameter ChgTariff"; + printfd(__FILE__, "FILES_STORE::RestoreAdmin - chgtariff read failed for admin '%s'\n", ac->login.c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::AddTariff(const string & name) const +{ +string fileName; +strprintf(&fileName, "%s/%s.tf", storeSettings.GetTariffsDir().c_str(), name.c_str()); +FILE * f; +f = fopen(fileName.c_str(), "wt"); +if (f) + { + fprintf(f, "\n"); + fclose(f); + return 0; + } + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +errorStr = "Cannot create file " + fileName; +printfd(__FILE__, "FILES_STORE::AddTariff - failed to add tariff '%s'\n", name.c_str()); +return -1; +} +//----------------------------------------------------------------------------- +int FILES_STORE::DelTariff(const string & name) const +{ +string fileName; +strprintf(&fileName, "%s/%s.tf", storeSettings.GetTariffsDir().c_str(), name.c_str()); +if (unlink(fileName.c_str())) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "unlink failed. Message: '"; + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::DelTariff - unlink failed. Message: '%s'\n", strerror(errno)); + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::RestoreTariff(TARIFF_DATA * td, const string & tariffName) const +{ +string tariffFileName = storeSettings.GetTariffsDir() + "/" + tariffName + ".tf"; +CONFIGFILE conf(tariffFileName); +string str; +td->tariffConf.name = tariffName; + +if (conf.Error() != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read file " + tariffFileName; + printfd(__FILE__, "FILES_STORE::RestoreTariff - failed to read tariff '%s'\n", tariffName.c_str()); + return -1; + } + +string param; +for (int i = 0; idirPrice[i].hDay, + td->dirPrice[i].mDay, + td->dirPrice[i].hNight, + td->dirPrice[i].mNight); + + strprintf(¶m, "PriceDayA%d", i); + if (conf.ReadDouble(param, &td->dirPrice[i].priceDayA, 0.0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + printfd(__FILE__, "FILES_STORE::RestoreTariff - pricedaya read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + td->dirPrice[i].priceDayA /= (1024*1024); + + strprintf(¶m, "PriceDayB%d", i); + if (conf.ReadDouble(param, &td->dirPrice[i].priceDayB, 0.0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + printfd(__FILE__, "FILES_STORE::RestoreTariff - pricedayb read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + td->dirPrice[i].priceDayB /= (1024*1024); + + strprintf(¶m, "PriceNightA%d", i); + if (conf.ReadDouble(param, &td->dirPrice[i].priceNightA, 0.0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + printfd(__FILE__, "FILES_STORE::RestoreTariff - pricenighta read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + td->dirPrice[i].priceNightA /= (1024*1024); + + strprintf(¶m, "PriceNightB%d", i); + if (conf.ReadDouble(param, &td->dirPrice[i].priceNightB, 0.0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + printfd(__FILE__, "FILES_STORE::RestoreTariff - pricenightb read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + td->dirPrice[i].priceNightB /= (1024*1024); + + strprintf(¶m, "Threshold%d", i); + if (conf.ReadInt(param, &td->dirPrice[i].threshold, 0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + printfd(__FILE__, "FILES_STORE::RestoreTariff - threshold read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + + strprintf(¶m, "SinglePrice%d", i); + if (conf.ReadInt(param, &td->dirPrice[i].singlePrice, 0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + printfd(__FILE__, "FILES_STORE::RestoreTariff - singleprice read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + + strprintf(¶m, "NoDiscount%d", i); + if (conf.ReadInt(param, &td->dirPrice[i].noDiscount, 0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + printfd(__FILE__, "FILES_STORE::RestoreTariff - nodiscount read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + } + +if (conf.ReadDouble("Fee", &td->tariffConf.fee, 0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter Fee"; + printfd(__FILE__, "FILES_STORE::RestoreTariff - fee read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + +if (conf.ReadDouble("Free", &td->tariffConf.free, 0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter Free"; + printfd(__FILE__, "FILES_STORE::RestoreTariff - free read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + +if (conf.ReadDouble("PassiveCost", &td->tariffConf.passiveCost, 0) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter PassiveCost"; + printfd(__FILE__, "FILES_STORE::RestoreTariff - passivecost read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + +if (conf.ReadString("TraffType", &str, "") < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter TraffType"; + printfd(__FILE__, "FILES_STORE::RestoreTariff - trafftype read failed for tariff '%s'\n", tariffName.c_str()); + return -1; + } + +if (!strcasecmp(str.c_str(), "up")) + td->tariffConf.traffType = TRAFF_UP; +else + if (!strcasecmp(str.c_str(), "down")) + td->tariffConf.traffType = TRAFF_DOWN; + else + if (!strcasecmp(str.c_str(), "up+down")) + td->tariffConf.traffType = TRAFF_UP_DOWN; + else + if (!strcasecmp(str.c_str(), "max")) + td->tariffConf.traffType = TRAFF_MAX; + else + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read tariff " + tariffName + ". Parameter TraffType incorrect"; + printfd(__FILE__, "FILES_STORE::RestoreTariff - invalid trafftype for tariff '%s'\n", tariffName.c_str()); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::SaveTariff(const TARIFF_DATA & td, const string & tariffName) const +{ +string tariffFileName = storeSettings.GetTariffsDir() + "/" + tariffName + ".tf"; +if (access(tariffFileName.c_str(), W_OK) != 0) + { + int fd = open(tariffFileName.c_str(), O_CREAT, 0600); + if (fd) + close(fd); + } + +CONFIGFILE cf(tariffFileName); + +int e = cf.Error(); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Error writing tariff " + tariffName; + printfd(__FILE__, "FILES_STORE::RestoreTariff - failed to save tariff '%s'\n", tariffName.c_str()); + return e; + } + +string param; +for (int i = 0; i < DIR_NUM; i++) + { + strprintf(¶m, "PriceDayA%d", i); + cf.WriteDouble(param, td.dirPrice[i].priceDayA * pt_mega); + + strprintf(¶m, "PriceDayB%d", i); + cf.WriteDouble(param, td.dirPrice[i].priceDayB * pt_mega); + + strprintf(¶m, "PriceNightA%d", i); + cf.WriteDouble(param, td.dirPrice[i].priceNightA * pt_mega); + + strprintf(¶m, "PriceNightB%d", i); + cf.WriteDouble(param, td.dirPrice[i].priceNightB * pt_mega); + + strprintf(¶m, "Threshold%d", i); + cf.WriteInt(param, td.dirPrice[i].threshold); + + string s; + strprintf(¶m, "Time%d", i); + + strprintf(&s, "%0d:%0d-%0d:%0d", + td.dirPrice[i].hDay, + td.dirPrice[i].mDay, + td.dirPrice[i].hNight, + td.dirPrice[i].mNight); + + cf.WriteString(param, s); + + strprintf(¶m, "NoDiscount%d", i); + cf.WriteInt(param, td.dirPrice[i].noDiscount); + + strprintf(¶m, "SinglePrice%d", i); + cf.WriteInt(param, td.dirPrice[i].singlePrice); + } + +cf.WriteDouble("PassiveCost", td.tariffConf.passiveCost); +cf.WriteDouble("Fee", td.tariffConf.fee); +cf.WriteDouble("Free", td.tariffConf.free); + +switch (td.tariffConf.traffType) + { + case TRAFF_UP: + cf.WriteString("TraffType", "up"); + break; + case TRAFF_DOWN: + cf.WriteString("TraffType", "down"); + break; + case TRAFF_UP_DOWN: + cf.WriteString("TraffType", "up+down"); + break; + case TRAFF_MAX: + cf.WriteString("TraffType", "max"); + break; + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const +{ +char fn[FN_STR_LEN]; +char dn[FN_STR_LEN]; +FILE * statFile; +//char timestr[30]; +time_t t;//, lastTimeReal; +tm * lt; + +t = time(NULL); + +snprintf(dn, FN_STR_LEN, "%s/%s/detail_stat", storeSettings.GetUsersDir().c_str(), login.c_str()); +if (access(dn, F_OK) != 0) + { + if (mkdir(dn, 0700) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Directory \'" + string(dn) + "\' cannot be created."; + printfd(__FILE__, "FILES_STORE::WriteDetailStat - mkdir failed. Message: '%s'\n", strerror(errno)); + return -1; + } + } + +int e = chown(dn, storeSettings.GetStatUID(), storeSettings.GetStatGID()); +e += chmod(dn, storeSettings.GetStatModeDir()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::WriteDetailStat - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +lt = localtime(&t); + +if (lt->tm_hour == 0 && lt->tm_min <= 5) + { + t -= 3600 * 24; + lt = localtime(&t); + } + +snprintf(dn, FN_STR_LEN, "%s/%s/detail_stat/%d", + storeSettings.GetUsersDir().c_str(), + login.c_str(), + lt->tm_year+1900); + +if (access(dn, F_OK) != 0) + { + if (mkdir(dn, 0700) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Directory \'" + string(dn) + "\' cannot be created."; + printfd(__FILE__, "FILES_STORE::WriteDetailStat - mkdir failed. Message: '%s'\n", strerror(errno)); + return -1; + } + } + +e = chown(dn, storeSettings.GetStatUID(), storeSettings.GetStatGID()); +e += chmod(dn, storeSettings.GetStatModeDir()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::WriteDetailStat - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +snprintf(dn, FN_STR_LEN, "%s/%s/detail_stat/%d/%s%d", + storeSettings.GetUsersDir().c_str(), + login.c_str(), + lt->tm_year+1900, + lt->tm_mon+1 < 10 ? "0" : "", + lt->tm_mon+1); +if (access(dn, F_OK) != 0) + { + if (mkdir(dn, 0700) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Directory \'" + string(dn) + "\' cannot be created."; + printfd(__FILE__, "FILES_STORE::WriteDetailStat - mkdir failed. Message: '%s'\n", strerror(errno)); + return -1; + } + } + +e = chown(dn, storeSettings.GetStatUID(), storeSettings.GetStatGID()); +e += chmod(dn, storeSettings.GetStatModeDir()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::WriteDetailStat - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +snprintf(fn, FN_STR_LEN, "%s/%s%d", dn, lt->tm_mday < 10 ? "0" : "", lt->tm_mday); + +statFile = fopen (fn, "at"); + +if (!statFile) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "File \'" + string(fn) + "\' cannot be written."; + printfd(__FILE__, "FILES_STORE::WriteDetailStat - fopen failed. Message: '%s'\n", strerror(errno)); + return -1; + } + +struct tm * lt1; +struct tm * lt2; + +lt1 = localtime(&lastStat); + +int h1, m1, s1; +int h2, m2, s2; + +h1 = lt1->tm_hour; +m1 = lt1->tm_min; +s1 = lt1->tm_sec; + +lt2 = localtime(&t); + +h2 = lt2->tm_hour; +m2 = lt2->tm_min; +s2 = lt2->tm_sec; + +if (fprintf(statFile, "-> %02d.%02d.%02d - %02d.%02d.%02d\n", + h1, m1, s1, h2, m2, s2) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("fprint failed. Message: '") + strerror(errno) + "'"; + printfd(__FILE__, "FILES_STORE::WriteDetailStat - fprintf failed. Message: '%s'\n", strerror(errno)); + return -1; + } + +map::const_iterator stIter; +stIter = statTree->begin(); + +while (stIter != statTree->end()) + { + string u, d; + x2str(stIter->second.up, u); + x2str(stIter->second.down, d); + #ifdef TRAFF_STAT_WITH_PORTS + if (fprintf(statFile, "%17s:%hu\t%15d\t%15s\t%15s\t%f\n", + inet_ntostring(stIter->first.ip).c_str(), + stIter->first.port, + stIter->first.dir, + d.c_str(), + u.c_str(), + stIter->second.cash) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "fprint failed. Message: '"; + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::WriteDetailStat - fprintf failed. Message: '%s'\n", strerror(errno)); + return -1; + } + #else + if (fprintf(statFile, "%17s\t%15d\t%15s\t%15s\t%f\n", + inet_ntostring(stIter->first.ip).c_str(), + stIter->first.dir, + d.c_str(), + u.c_str(), + stIter->second.cash) < 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("fprint failed. Message: '"); + errorStr += strerror(errno); + errorStr += "'"; + printfd(__FILE__, "FILES_STORE::WriteDetailStat - fprintf failed. Message: '%s'\n", strerror(errno)); + return -1; + } + #endif + + ++stIter; + } + +fclose(statFile); + +e = chown(fn, storeSettings.GetStatUID(), storeSettings.GetStatGID()); +e += chmod(fn, storeSettings.GetStatMode()); + +if (e) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + printfd(__FILE__, "FILES_STORE::WriteDetailStat - chmod/chown failed for user '%s'. Error: '%s'\n", login.c_str(), strerror(errno)); + } + +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::AddMessage(STG_MSG * msg, const string & login) const +{ +//ðÒÏ×ÅÒÉÔØ ÅÓÌÔØ ÌÉ ÄÉÒÅËÔÏÒÉÑ ÄÌÑ ÓÏÏÂÝÅÎÉÊ. åÓÌÉ ÎÅÔ - ÓÏÚÄÁÔØ. +//úÁÔÅÍ ÐÏÌÏÖÉÔØ ÓÏÏÂÝÅÎÉÅ Ó ÉÍÅÎÅÍ ÆÁÊÌÁ - ×ÒÅÍÅÎÎOÊ ÍÅÔËÏÊ. úÁÐÉÓÁÔØ ÔÕÄÁ +//ÔÅËÓÔ É ÐÒÉÏÒÉÔÅÔ. + +string fn; +string dn; +struct timeval tv; +FILE * msgFile; + +strprintf(&dn, "%s/%s/messages", storeSettings.GetUsersDir().c_str(), login.c_str()); +if (access(dn.c_str(), F_OK) != 0) + { + if (mkdir(dn.c_str(), 0700) != 0) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Directory \'"; + errorStr += dn; + errorStr += "\' cannot be created."; + printfd(__FILE__, "FILES_STORE::AddMessage - mkdir failed. Message: '%s'\n", strerror(errno)); + return -1; + } + } + +chmod(dn.c_str(), storeSettings.GetConfMode() | S_IXUSR); + +gettimeofday(&tv, NULL); + +msg->header.id = ((long long)tv.tv_sec) * 1000000 + ((long long)tv.tv_usec); +strprintf(&fn, "%s/%lld", dn.c_str(), msg->header.id); + +msgFile = fopen (fn.c_str(), "wt"); + +if (!msgFile) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "File \'"; + errorStr += fn; + errorStr += "\' cannot be writen."; + printfd(__FILE__, "FILES_STORE::AddMessage - fopen failed. Message: '%s'\n", strerror(errno)); + return -1; + } +fclose(msgFile); +return EditMessage(*msg, login); +} +//----------------------------------------------------------------------------- +int FILES_STORE::EditMessage(const STG_MSG & msg, const string & login) const +{ +//ðÒÏ×ÅÒÉÔØ ÅÓÌÔØ ÌÉ ÄÉÒÅËÔÏÒÉÑ ÄÌÑ ÓÏÏÂÝÅÎÉÊ. åÓÌÉ ÎÅÔ - ÓÏÚÄÁÔØ. +//úÁÔÅÍ ÐÏÌÏÖÉÔØ ÓÏÏÂÝÅÎÉÅ Ó ÉÍÅÎÅÍ ÆÁÊÌÁ - ×ÒÅÍÅÎÎOÊ ÍÅÔËÏÊ. úÁÐÉÓÁÔØ ÔÕÄÁ +//ÔÅËÓÔ É ÐÒÉÏÒÉÔÅÔ. + +string fn; + +FILE * msgFile; +strprintf(&fn, "%s/%s/messages/%lld", storeSettings.GetUsersDir().c_str(), login.c_str(), msg.header.id); + +if (access(fn.c_str(), F_OK) != 0) + { + string idstr; + x2str(msg.header.id, idstr); + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Message for user \'"; + errorStr += login + "\' with ID \'"; + errorStr += idstr + "\' does not exist."; + printfd(__FILE__, "FILES_STORE::EditMessage - %s\n", errorStr.c_str()); + return -1; + } + +msgFile = fopen(fn.c_str(), "wt"); +if (!msgFile) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "File \'" + fn + "\' cannot be writen."; + printfd(__FILE__, "FILES_STORE::EditMessage - fopen failed. Message: '%s'\n", strerror(errno)); + return -1; + } + +bool res = true; +res &= (fprintf(msgFile, "%d\n", msg.header.type) >= 0); +res &= (fprintf(msgFile, "%u\n", msg.header.lastSendTime) >= 0); +res &= (fprintf(msgFile, "%u\n", msg.header.creationTime) >= 0); +res &= (fprintf(msgFile, "%u\n", msg.header.showTime) >= 0); +res &= (fprintf(msgFile, "%d\n", msg.header.repeat) >= 0); +res &= (fprintf(msgFile, "%u\n", msg.header.repeatPeriod) >= 0); +res &= (fprintf(msgFile, "%s", msg.text.c_str()) >= 0); + +if (!res) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("fprintf failed. Message: '") + strerror(errno) + "'"; + printfd(__FILE__, "FILES_STORE::EditMessage - fprintf failed. Message: '%s'\n", strerror(errno)); + return -1; + } + +fclose(msgFile); + +chmod(fn.c_str(), storeSettings.GetConfMode()); +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::GetMessage(uint64_t id, STG_MSG * msg, const string & login) const +{ +string fn; +strprintf(&fn, "%s/%s/messages/%lld", storeSettings.GetUsersDir().c_str(), login.c_str(), id); +msg->header.id = id; +return ReadMessage(fn, &msg->header, &msg->text); +} +//----------------------------------------------------------------------------- +int FILES_STORE::DelMessage(uint64_t id, const string & login) const +{ +string fn; +strprintf(&fn, "%s/%s/messages/%lld", storeSettings.GetUsersDir().c_str(), login.c_str(), id); + +return unlink(fn.c_str()); +} +//----------------------------------------------------------------------------- +int FILES_STORE::GetMessageHdrs(vector * hdrsList, const string & login) const +{ +vector messages; +string dn; +dn = storeSettings.GetUsersDir() + "/" + login + "/messages/"; +GetFilesList(&messages, dn, S_IFREG, ""); + +//hdrsList->resize(messages.size()); + +for (unsigned i = 0; i < messages.size(); i++) + { + unsigned long long id = 0; + + if (str2x(messages[i].c_str(), id)) + { + if (unlink((dn + messages[i]).c_str())) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("unlink failed. Message: '") + strerror(errno) + "'"; + printfd(__FILE__, "FILES_STORE::GetMessageHdrs - unlink failed. Message: '%s'\n", strerror(errno)); + return -1; + } + continue; + } + + STG_MSG_HDR hdr; + if (ReadMessage(dn + messages[i], &hdr, NULL)) + { + return -1; + } + + if (hdr.repeat < 0) + { + if (unlink((dn + messages[i]).c_str())) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = string("unlink failed. Message: '") + strerror(errno) + "'"; + printfd(__FILE__, "FILES_STORE::GetMessageHdrs - unlink failed. Message: '%s'\n", strerror(errno)); + return -1; + } + continue; + } + + hdr.id = id; + hdrsList->push_back(hdr); + } +return 0; +} +//----------------------------------------------------------------------------- +int FILES_STORE::ReadMessage(const string & fileName, + STG_MSG_HDR * hdr, + string * text) const +{ +FILE * msgFile; +msgFile = fopen(fileName.c_str(), "rt"); +if (!msgFile) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "File \'"; + errorStr += fileName; + errorStr += "\' cannot be openned."; + printfd(__FILE__, "FILES_STORE::ReadMessage - fopen failed. Message: '%s'\n", strerror(errno)); + return -1; + } +char p[20]; +unsigned * d[6]; +d[0] = &hdr->type; +d[1] = &hdr->lastSendTime; +d[2] = &hdr->creationTime; +d[3] = &hdr->showTime; +d[4] = (unsigned*)(&hdr->repeat); +d[5] = &hdr->repeatPeriod; + +memset(p, 0, sizeof(p)); + +for (int pos = 0; pos < 6; pos++) + { + if (fgets(p, sizeof(p) - 1, msgFile) == NULL) { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read file \'"; + errorStr += fileName; + errorStr += "\'. Missing data."; + printfd(__FILE__, "FILES_STORE::ReadMessage - cannot read file (missing data)\n"); + printfd(__FILE__, "FILES_STORE::ReadMessage - position: %d\n", pos); + fclose(msgFile); + return -1; + } + + char * ep; + ep = strrchr(p, '\r'); + if (ep) *ep = 0; + ep = strrchr(p, '\n'); + if (ep) *ep = 0; + + if (feof(msgFile)) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read file \'"; + errorStr += fileName; + errorStr += "\'. Missing data."; + printfd(__FILE__, "FILES_STORE::ReadMessage - cannot read file (feof)\n"); + printfd(__FILE__, "FILES_STORE::ReadMessage - position: %d\n", pos); + fclose(msgFile); + return -1; + } + + if (str2x(p, *(d[pos]))) + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + errorStr = "Cannot read file \'"; + errorStr += fileName; + errorStr += "\'. Incorrect value. \'"; + errorStr += p; + errorStr += "\'"; + printfd(__FILE__, "FILES_STORE::ReadMessage - incorrect value\n"); + fclose(msgFile); + return -1; + } + } + +char txt[2048]; +memset(txt, 0, sizeof(txt)); +if (text) + { + text->erase(text->begin(), text->end()); + while (!feof(msgFile)) + { + txt[0] = 0; + if (fgets(txt, sizeof(txt) - 1, msgFile) == NULL) { + break; + } + + (*text) += txt; + } + } +//fprintf(msgFile, "%s\n", msg.text.c_str()); +fclose(msgFile); +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/files/file_store.h b/projects/stargazer/plugins/store/files/file_store.h new file mode 100644 index 00000000..4734a54d --- /dev/null +++ b/projects/stargazer/plugins/store/files/file_store.h @@ -0,0 +1,205 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.22 $ + $Date: 2010/01/19 11:06:53 $ + $Author: faust $ + */ + + +#ifndef FILE_STORE_H +#define FILE_STORE_H + +#include +#include + +#include "base_settings.h" +#include "base_store.h" +#include "conffiles.h" +#include "user_traff.h" + +using namespace std; +//----------------------------------------------------------------------------- +extern "C" BASE_STORE * GetStore(); +//----------------------------------------------------------------------------- +class FILES_STORE_SETTINGS//: public BASE_SETTINGS +{ +public: + FILES_STORE_SETTINGS(); + virtual ~FILES_STORE_SETTINGS(); + virtual int ParseSettings(const MODULE_SETTINGS & s); + virtual const string & GetStrError() const; + + string GetWorkDir() const; + string GetUsersDir() const; + string GetAdminsDir() const; + string GetTariffsDir() const; + + mode_t GetStatMode() const; + mode_t GetStatModeDir() const; + uid_t GetStatUID() const; + gid_t GetStatGID() const; + + mode_t GetConfMode() const; + mode_t GetConfModeDir() const; + uid_t GetConfUID() const; + gid_t GetConfGID() const; + + mode_t GetLogMode() const; + uid_t GetLogUID() const; + gid_t GetLogGID() const; + + bool GetRemoveBak() const; + bool GetReadBak() const; + +private: + const MODULE_SETTINGS * settings; + + int User2UID(const char * user, uid_t * uid); + int Group2GID(const char * gr, gid_t * gid); + int Str2Mode(const char * str, mode_t * mode); + int ParseOwner(const vector & moduleParams, const string & owner, uid_t * uid); + int ParseGroup(const vector & moduleParams, const string & group, uid_t * uid); + int ParseMode(const vector & moduleParams, const string & modeStr, mode_t * mode); + int ParseYesNo(const string & value, bool * val); + string errorStr; + + string workDir; + string usersDir; + string adminsDir; + string tariffsDir; + + mode_t statMode; + uid_t statUID; + gid_t statGID; + + mode_t confMode; + uid_t confUID; + gid_t confGID; + + mode_t userLogMode; + uid_t userLogUID; + gid_t userLogGID; + + bool removeBak; + bool readBak; +}; +//----------------------------------------------------------------------------- +class FILES_STORE: public BASE_STORE +{ +public: + FILES_STORE(); + virtual ~FILES_STORE(); + virtual const string & GetStrError() const; + + //User + virtual int GetUsersList(vector * usersList) const; + virtual int AddUser(const string & login) const; + virtual int DelUser(const string & login) const; + virtual int SaveUserStat(const USER_STAT & stat, const string & login) const; + virtual int SaveUserConf(const USER_CONF & conf, const string & login) const; + + virtual int RestoreUserStat(USER_STAT * stat, const string & login) const; + virtual int RestoreUserConf(USER_CONF * conf, const string & login) const; + + virtual int WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message = "") const; + virtual int WriteUserConnect(const string & login, uint32_t ip) const; + virtual int WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const; + + virtual int WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const; + + virtual int AddMessage(STG_MSG * msg, const string & login) const; + virtual int EditMessage(const STG_MSG & msg, const string & login) const; + virtual int GetMessage(uint64_t id, STG_MSG * msg, const string & login) const; + virtual int DelMessage(uint64_t id, const string & login) const; + virtual int GetMessageHdrs(vector * hdrsList, const string & login) const; + virtual int ReadMessage(const string & fileName, + STG_MSG_HDR * hdr, + string * text) const; + + virtual int SaveMonthStat(const USER_STAT & stat, int month, int year, const string & login) const; + + //Admin + virtual int GetAdminsList(vector * adminsList) const; + virtual int AddAdmin(const string & login) const; + virtual int DelAdmin(const string & login) const; + virtual int RestoreAdmin(ADMIN_CONF * ac, const string & login) const; + virtual int SaveAdmin(const ADMIN_CONF & ac) const; + + //Tariff + virtual int GetTariffsList(vector * tariffsList) const; + virtual int AddTariff(const string & name) const; + virtual int DelTariff(const string & name) const; + virtual int SaveTariff(const TARIFF_DATA & td, const string & tariffName) const; + virtual int RestoreTariff(TARIFF_DATA * td, const string & tariffName) const; + + //Corparation + virtual int GetCorpsList(vector *) const {return 0;}; + virtual int SaveCorp(const CORP_CONF &) const {return 0;}; + virtual int RestoreCorp(CORP_CONF *, const string &) const {return 0;}; + virtual int AddCorp(const string &) const {return 0;}; + virtual int DelCorp(const string &) const {return 0;}; + + // Services + virtual int GetServicesList(vector *) const {return 0;}; + virtual int SaveService(const SERVICE_CONF &) const {return 0;}; + virtual int RestoreService(SERVICE_CONF *, const string &) const {return 0;}; + virtual int AddService(const string &) const {return 0;}; + virtual int DelService(const string &) const {return 0;}; + + //virtual BASE_SETTINGS * GetStoreSettings(); + virtual void SetSettings(const MODULE_SETTINGS & s); + virtual int ParseSettings(); + virtual const string & GetVersion() const; + +private: + virtual int RestoreUserStat(USER_STAT * stat, const string & login, const string & fileName) const; + virtual int RestoreUserConf(USER_CONF * conf, const string & login, const string & fileName) const; + + virtual int WriteLogString(const string & str, const string & login) const; + virtual int WriteLog2String(const string & str, const string & login) const; + int RemoveDir(const char * path) const; + int GetFilesList(vector * filesList, const string & directory, mode_t mode, const string & ext) const; + mutable string errorStr; + string version; + FILES_STORE_SETTINGS storeSettings; + MODULE_SETTINGS settings; + mutable pthread_mutex_t mutex; +}; +//----------------------------------------------------------------------------- + +#endif //FILE_STORE_H + diff --git a/projects/stargazer/plugins/store/firebird/Makefile b/projects/stargazer/plugins/store/firebird/Makefile new file mode 100644 index 00000000..773d778a --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/Makefile @@ -0,0 +1,23 @@ +############################################################################### +# $Id: Makefile,v 1.9 2008/12/04 17:15:53 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +DEFS += -DIBPP_LINUX + +PROG = mod_store_firebird.so + +SRCS = ./firebird_store.cpp \ + ./firebird_store_admins.cpp \ + ./firebird_store_corporations.cpp \ + ./firebird_store_messages.cpp \ + ./firebird_store_services.cpp \ + ./firebird_store_tariffs.cpp \ + ./firebird_store_users.cpp \ + ./firebird_store_utils.cpp + +STGLIBS = -libpp -lstg_common + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/store/firebird/deps b/projects/stargazer/plugins/store/firebird/deps new file mode 100644 index 00000000..5912b318 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/deps @@ -0,0 +1,180 @@ +firebird_store.o: firebird_store.cpp firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX +firebird_store_admins.o: firebird_store_admins.cpp firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/blowfish.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX +firebird_store_corporations.o: firebird_store_corporations.cpp \ + firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX +firebird_store_messages.o: firebird_store_messages.cpp firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX +firebird_store_services.o: firebird_store_services.cpp firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX +firebird_store_tariffs.o: firebird_store_tariffs.cpp firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX +firebird_store_users.o: firebird_store_users.cpp \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX +firebird_store_utils.o: firebird_store_utils.cpp firebird_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX -DIBPP_LINUX diff --git a/projects/stargazer/plugins/store/firebird/firebird_store.cpp b/projects/stargazer/plugins/store/firebird/firebird_store.cpp new file mode 100644 index 00000000..c9265e14 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store.cpp @@ -0,0 +1,156 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * This file contains a realization of a base firebird-storage plugin class + * + * $Revision: 1.18 $ + * $Date: 2010/01/08 16:00:45 $ + * + */ + +#include +#include +#include + +using namespace std; + +#include "firebird_store.h" +#include "ibpp.h" + +class FIREBIRD_STORE_CREATOR +{ +public: + FIREBIRD_STORE_CREATOR() + : frb(new FIREBIRD_STORE()) + { + }; + ~FIREBIRD_STORE_CREATOR() + { + delete frb; + }; + FIREBIRD_STORE * GetStore() { return frb; }; +private: + FIREBIRD_STORE * frb; +} frsc; + +//----------------------------------------------------------------------------- +BASE_STORE * GetStore() +{ +return frsc.GetStore(); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +FIREBIRD_STORE::FIREBIRD_STORE() +{ +db_server = "localhost"; +db_database = "/var/stg/stargazer.fdb"; +db_user = "stg"; +db_password = "123456"; +version = "firebird_store v.1.4"; +pthread_mutex_init(&mutex, NULL); + +// Advanced settings defaults + +til = IBPP::ilConcurrency; +tlr = IBPP::lrWait; +} +//----------------------------------------------------------------------------- +FIREBIRD_STORE::~FIREBIRD_STORE() +{ +db->Disconnect(); +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::ParseSettings() +{ +vector::iterator i; +string s; + +for(i = settings.moduleParams.begin(); i != settings.moduleParams.end(); ++i) + { + s = i->param; + transform(s.begin(), s.end(), s.begin(), ToLower()); + if (s == "server") + { + db_server = *(i->value.begin()); + } + if (s == "database") + { + db_database = *(i->value.begin()); + } + if (s == "user") + { + db_user = *(i->value.begin()); + } + if (s == "password") + { + db_password = *(i->value.begin()); + } + + // Advanced settings block + + if (s == "isolationLevel") + { + if (*(i->value.begin()) == "Concurrency") + { + til = IBPP::ilConcurrency; + } + else if (*(i->value.begin()) == "DirtyRead") + { + til = IBPP::ilReadDirty; + } + else if (*(i->value.begin()) == "ReadCommitted") + { + til = IBPP::ilReadCommitted; + } + else if (*(i->value.begin()) == "Consistency") + { + til = IBPP::ilConsistency; + } + } + if (s == "lockResolution") + { + if (*(i->value.begin()) == "Wait") + { + tlr = IBPP::lrWait; + } + else if (*(i->value.begin()) == "NoWait") + { + tlr = IBPP::lrNoWait; + } + } + } + +try + { + db = IBPP::DatabaseFactory(db_server, db_database, db_user, db_password, "", "KOI8U", ""); + db->Connect(); + } +catch (IBPP::Exception & ex) + { + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/store/firebird/firebird_store.h b/projects/stargazer/plugins/store/firebird/firebird_store.h new file mode 100644 index 00000000..2cc62cfa --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store.h @@ -0,0 +1,136 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Firebird storage class definition + * + * $Revision: 1.13 $ + * $Date: 2010/01/19 11:07:25 $ + * + */ + +#ifndef FIREBIRD_STORE_H +#define FIREBIRD_STORE_H + +#include +#include +#include + +#include "base_store.h" +#include "stg_locker.h" +#include "ibpp.h" +//#include "firebird_database.h" + +struct ToLower +{ +char operator() (char c) const { return std::tolower(c); } +}; + +extern "C" BASE_STORE * GetStore(); + +class FIREBIRD_STORE : public BASE_STORE { +public: + FIREBIRD_STORE(); + virtual ~FIREBIRD_STORE(); + + int GetUsersList(std::vector * usersList) const; + int AddUser(const std::string & login) const; + int DelUser(const std::string & login) const; + int SaveUserStat(const USER_STAT & stat, const std::string & login) const; + int SaveUserConf(const USER_CONF & conf, const std::string & login) const; + int RestoreUserStat(USER_STAT * stat, const std::string & login) const; + int RestoreUserConf(USER_CONF * conf, const std::string & login) const; + int WriteUserChgLog(const std::string & login, + const std::string & admLogin, + uint32_t admIP, + const std::string & paramName, + const std::string & oldValue, + const std::string & newValue, + const std::string & message) const; + int WriteUserConnect(const std::string & login, uint32_t ip) const; + int WriteUserDisconnect(const std::string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const; + int WriteDetailedStat(const std::map * statTree, + time_t lastStat, + const std::string & login) const; + + int AddMessage(STG_MSG * msg, const std::string & login) const; + int EditMessage(const STG_MSG & msg, const std::string & login) const; + int GetMessage(uint64_t id, STG_MSG * msg, const std::string & login) const; + int DelMessage(uint64_t id, const std::string & login) const; + int GetMessageHdrs(std::vector * hdrsList, const std::string & login) const; + + int SaveMonthStat(const USER_STAT & stat, int month, int year, const std::string & login) const; + + int GetAdminsList(std::vector * adminsList) const; + int SaveAdmin(const ADMIN_CONF & ac) const; + int RestoreAdmin(ADMIN_CONF * ac, const std::string & login) const; + int AddAdmin(const std::string & login) const; + int DelAdmin(const std::string & login) const; + + int GetTariffsList(std::vector * tariffsList) const; + int AddTariff(const std::string & name) const; + int DelTariff(const string & name) const; + int SaveTariff(const TARIFF_DATA & td, const std::string & tariffName) const; + int RestoreTariff(TARIFF_DATA * td, const std::string & tariffName) const; + + int GetCorpsList(std::vector * corpsList) const; + int SaveCorp(const CORP_CONF & cc) const; + int RestoreCorp(CORP_CONF * cc, const std::string & name) const; + int AddCorp(const std::string & name) const; + int DelCorp(const std::string & name) const; + + inline void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + int ParseSettings(); + + inline const string & GetStrError() const { return strError; }; + + inline const string & GetVersion() const { return version; }; + + int GetServicesList(std::vector * servicesList) const; + int SaveService(const SERVICE_CONF & sc) const; + int RestoreService(SERVICE_CONF * sc, const std::string & name) const; + int AddService(const std::string & name) const; + int DelService(const std::string & name) const; +private: + std::string version; + mutable std::string strError; + mutable std::string db_server, db_database, db_user, db_password; + MODULE_SETTINGS settings; + mutable IBPP::Database db; + mutable pthread_mutex_t mutex; + mutable IBPP::TIL til; + mutable IBPP::TLR tlr; + + int SaveStat(const USER_STAT & stat, const std::string & login, int year = 0, int month = 0) const; + + time_t ts2time_t(const IBPP::Timestamp & ts) const; + void time_t2ts(time_t t, IBPP::Timestamp * ts) const; + void ym2date(int year, int month, IBPP::Date * date) const; +}; + +#endif //FIREBIRD_STORE_H + diff --git a/projects/stargazer/plugins/store/firebird/firebird_store_admins.cpp b/projects/stargazer/plugins/store/firebird/firebird_store_admins.cpp new file mode 100644 index 00000000..98383482 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store_admins.cpp @@ -0,0 +1,258 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Administrators manipulation methods + * + * $Revision: 1.11 $ + * $Date: 2008/12/04 17:10:06 $ + * + */ + +#include +#include + +using namespace std; + +#include "firebird_store.h" +#include "ibpp.h" +#include "blowfish.h" + +#define adm_enc_passwd "cjeifY8m3" + +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::GetAdminsList(vector * adminsList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +string login; + +try + { + tr->Start(); + st->Execute("select login from tb_admins"); + while (st->Fetch()) + { + st->Get(1, login); + adminsList->push_back(login); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveAdmin(const ADMIN_CONF & ac) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +char encodedPass[2 * ADM_PASSWD_LEN + 2]; +char cryptedPass[ADM_PASSWD_LEN + 1]; +char adminPass[ADM_PASSWD_LEN + 1]; +BLOWFISH_CTX ctx; + +memset(cryptedPass, 0, ADM_PASSWD_LEN + 1); +strncpy(adminPass, ac.password.c_str(), ADM_PASSWD_LEN); +EnDecodeInit(adm_enc_passwd, sizeof(adm_enc_passwd), &ctx); + +for (int i = 0; i < ADM_PASSWD_LEN / 8; i++) + EncodeString(cryptedPass + 8 * i, adminPass + 8 * i, &ctx); + +cryptedPass[ADM_PASSWD_LEN] = 0; +Encode12(encodedPass, cryptedPass, ADM_PASSWD_LEN); + +try + { + tr->Start(); + st->Prepare("update tb_admins set passwd=?, \ + chg_conf=?, \ + chg_password=?, \ + chg_stat=?, \ + chg_cash=?, \ + usr_add_del=?, \ + chg_tariff=?, \ + chg_admin=? \ + where login=?"); + st->Set(1, encodedPass); + st->Set(2, static_cast(ac.priv.userConf)); + st->Set(3, static_cast(ac.priv.userPasswd)); + st->Set(4, static_cast(ac.priv.userStat)); + st->Set(5, static_cast(ac.priv.userCash)); + st->Set(6, static_cast(ac.priv.userAddDel)); + st->Set(7, static_cast(ac.priv.tariffChg)); + st->Set(8, static_cast(ac.priv.adminChg)); + st->Set(9, ac.login); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::RestoreAdmin(ADMIN_CONF * ac, const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +char cryptedPass[ADM_PASSWD_LEN + 1]; +char adminPass[ADM_PASSWD_LEN + 1]; +BLOWFISH_CTX ctx; + +try + { + tr->Start(); + st->Prepare("select * from tb_admins where login = ?"); + st->Set(1, login); + st->Execute(); + if (st->Fetch()) + { + st->Get(2, ac->login); + st->Get(3, ac->password); + st->Get(4, (int16_t &)ac->priv.userConf); + st->Get(5, (int16_t &)ac->priv.userPasswd); + st->Get(6, (int16_t &)ac->priv.userStat); + st->Get(7, (int16_t &)ac->priv.userCash); + st->Get(8, (int16_t &)ac->priv.userAddDel); + st->Get(9, (int16_t &)ac->priv.tariffChg); + st->Get(10, (int16_t &)ac->priv.adminChg); + } + else + { + strError = "Admin \"" + login + "\" not found in database"; + printfd(__FILE__, "Admin '%s' not found in database\n", login.c_str()); + tr->Rollback(); + return -1; + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +if (ac->password == "") + { + return 0; + } + +Decode21(cryptedPass, ac->password.c_str()); +EnDecodeInit(adm_enc_passwd, sizeof(adm_enc_passwd), &ctx); +for (int i = 0; i < ADM_PASSWD_LEN / 8; i++) + { + DecodeString(adminPass + 8 * i, cryptedPass + 8 * i, &ctx); + } +ac->password = adminPass; + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::AddAdmin(const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("insert into tb_admins(login, \ + passwd, \ + chg_conf, \ + chg_password, \ + chg_stat, \ + chg_cash, \ + usr_add_del, \ + chg_tariff, \ + chg_admin, \ + chg_service, \ + chg_corporation) \ + values (?, '', 0, 0, 0, 0, 0, 0, 0, 0, 0)"); + st->Set(1, login); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::DelAdmin(const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("delete from tb_admins where login = ?"); + st->Set(1, login); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/firebird/firebird_store_corporations.cpp b/projects/stargazer/plugins/store/firebird/firebird_store_corporations.cpp new file mode 100644 index 00000000..71e75cbe --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store_corporations.cpp @@ -0,0 +1,185 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Corporations manipulation methods + * + * $Revision: 1.5 $ + * $Date: 2007/12/23 13:39:59 $ + * + */ + +#include "firebird_store.h" +#include "ibpp.h" + +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::GetCorpsList(vector * corpsList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +string name; + +try + { + tr->Start(); + st->Execute("select name from tb_corporations"); + while (st->Fetch()) + { + st->Get(1, name); + corpsList->push_back(name); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveCorp(const CORP_CONF & cc) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Execute("update tb_corporations set cash = ? where name = ?"); + st->Set(1, cc.cash); + st->Set(2, cc.name); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::RestoreCorp(CORP_CONF * cc, const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("select cash from tb_corporations where name = ?"); + st->Set(1, name); + st->Execute(); + if (st->Fetch()) + { + st->Get(1, cc->cash); + } + else + { + strError = "Corporation \"" + name + "\" not found in database"; + tr->Rollback(); + printfd(__FILE__, "Corporation '%s' not found in database\n", name.c_str()); + return -1; + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::AddCorp(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("insert into tb_corporations (name, cash), values (?, 0)"); + st->Set(1, name); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::DelCorp(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("delete from tb_corporations where name = ?"); + st->Set(1, name); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/firebird/firebird_store_messages.cpp b/projects/stargazer/plugins/store/firebird/firebird_store_messages.cpp new file mode 100644 index 00000000..53805eb0 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store_messages.cpp @@ -0,0 +1,230 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + +/* + * Messages manipualtion methods + * + * $Revision: 1.10 $ + * $Date: 2009/03/03 16:16:23 $ + * + */ + +#include + +#include "firebird_store.h" +#include "ibpp.h" + +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::AddMessage(STG_MSG * msg, const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_add_message(NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + st->Set(1, login); + st->Set(2, (int32_t)msg->header.ver); + st->Set(3, (int32_t)msg->header.type); + st->Set(4, (int32_t)msg->header.lastSendTime); + st->Set(5, (int32_t)msg->header.creationTime); + st->Set(6, (int32_t)msg->header.showTime); + st->Set(7, msg->header.repeat); + st->Set(8, (int32_t)msg->header.repeatPeriod); + st->Set(9, msg->text); + st->Execute(); + st->Get(1, (int64_t &)msg->header.id); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::EditMessage(const STG_MSG & msg, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_add_message(?, ?, ?, ?, ?, ?, ?, ?, ?)"); + st->Set(1, (int64_t)msg.header.id); + st->Set(2, login); + st->Set(3, (int32_t)msg.header.ver); + st->Set(4, (int32_t)msg.header.type); + st->Set(5, (int32_t)msg.header.lastSendTime); + st->Set(6, (int32_t)msg.header.creationTime); + st->Set(7, (int32_t)msg.header.showTime); + st->Set(8, msg.header.repeat); + st->Set(9, (int32_t)msg.header.repeatPeriod); + st->Set(10, msg.text.c_str()); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::GetMessage(uint64_t id, + STG_MSG * msg, + const string &) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("select * from tb_messages where pk_message = ?"); + st->Set(1, (int64_t)id); + st->Execute(); + if (st->Fetch()) + { + st->Get(1, (int64_t &)msg->header.id); + st->Get(3, (int32_t &)msg->header.ver); + st->Get(4, (int32_t &)msg->header.type); + st->Get(5, (int32_t &)msg->header.lastSendTime); + st->Get(6, (int32_t &)msg->header.creationTime); + st->Get(7, (int32_t &)msg->header.showTime); + st->Get(8, msg->header.repeat); + st->Get(9, (int32_t &)msg->header.repeatPeriod); + st->Get(10, msg->text); + } + else + { + strprintf(&strError, "Message with id = %d not found in database", id); + printfd(__FILE__, "Message with id - %d not found in database\n", id); + tr->Rollback(); + return -1; + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::DelMessage(uint64_t id, const string &) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("delete from tb_messages where pk_message = ?"); + st->Set(1, (int64_t)id); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::GetMessageHdrs(vector * hdrsList, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +STG_MSG_HDR header; + +try + { + tr->Start(); + st->Prepare("select pk_message, ver, msg_type, \ + last_send_time, creation_time, \ + show_time, repeat, repeat_period \ + from tb_messages where \ + fk_user = (select pk_user from tb_users where name = ?)"); + st->Set(1, login); + st->Execute(); + while (st->Fetch()) + { + st->Get(1, (int64_t &)header.id); + st->Get(2, (int32_t &)header.ver); + st->Get(3, (int32_t &)header.type); + st->Get(4, (int32_t &)header.lastSendTime); + st->Get(5, (int32_t &)header.creationTime); + st->Get(6, (int32_t &)header.showTime); + st->Get(7, header.repeat); + st->Get(8, (int32_t &)header.repeatPeriod); + hdrsList->push_back(header); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/firebird/firebird_store_services.cpp b/projects/stargazer/plugins/store/firebird/firebird_store_services.cpp new file mode 100644 index 00000000..01a460e7 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store_services.cpp @@ -0,0 +1,196 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + +/* + * Services manipulation methods + * + * $Revision: 1.6 $ + * $Date: 2009/05/13 13:19:33 $ + * + */ + +#include "firebird_store.h" +#include "ibpp.h" + +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::GetServicesList(vector * servicesList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +string name; + +try + { + tr->Start(); + st->Execute("select name from tb_services"); + while (st->Fetch()) + { + st->Get(1, name); + servicesList->push_back(name); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveService(const SERVICE_CONF & sc) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("update tb_services set \ + comments = ?, \ + cost = ?, \ + pay_day = ? \ + where name = ?"); + st->Set(1, sc.comment); + st->Set(2, sc.cost); + st->Set(3, sc.payDay); + st->Set(4, sc.name); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::RestoreService(SERVICE_CONF * sc, + const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("select * from tb_services where name = ?"); + st->Set(1, name); + st->Execute(); + if (st->Fetch()) + { + st->Get(3, sc->comment); + st->Get(4, sc->cost); + st->Get(5, sc->payDay); + } + else + { + strError = "Service \"" + name + "\" not found in database"; + printfd(__FILE__, "Service '%s' not found in database\n", name.c_str()); + tr->Rollback(); + return -1; + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::AddService(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("insert into tb_services (name, comment, cost, pay_day) \ + values (?, '', 0, 0)"); + st->Set(1, name); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::DelService(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_delete_service(?)"); + st->Set(1, name); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/firebird/firebird_store_tariffs.cpp b/projects/stargazer/plugins/store/firebird/firebird_store_tariffs.cpp new file mode 100644 index 00000000..eaba5b53 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store_tariffs.cpp @@ -0,0 +1,328 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Tariffs manipulation methods + * + * $Revision: 1.5 $ + * $Date: 2007/12/23 13:39:59 $ + * + */ + +#include "firebird_store.h" +#include "ibpp.h" + +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::GetTariffsList(vector * tariffsList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +string name; + +try + { + tr->Start(); + st->Execute("select name from tb_tariffs"); + while (st->Fetch()) + { + st->Get(1, name); + tariffsList->push_back(name); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::AddTariff(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_add_tariff(?, ?)"); + st->Set(1, name); + st->Set(2, DIR_NUM); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::DelTariff(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_delete_tariff(?)"); + st->Set(1, name); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveTariff(const TARIFF_DATA & td, + const string & tariffName) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +int32_t id, i; +double pda, pdb, pna, pnb; +int threshold; + +try + { + tr->Start(); + st->Prepare("select pk_tariff from tb_tariffs where name = ?"); + st->Set(1, tariffName); + st->Execute(); + if (!st->Fetch()) + { + tr->Rollback(); + strprintf(&strError, "Tariff \"%s\" not found in database", tariffName.c_str()); + printfd(__FILE__, "Tariff '%s' not found in database\n", tariffName.c_str()); + return -1; + } + st->Get(1, id); + st->Close(); + st->Prepare("update tb_tariffs set \ + fee = ?, \ + free = ?, \ + passive_cost = ?, \ + traff_type = ? \ + where pk_tariff = ?"); + st->Set(1, td.tariffConf.fee); + st->Set(2, td.tariffConf.free); + st->Set(3, td.tariffConf.passiveCost); + st->Set(4, td.tariffConf.traffType); + st->Set(5, id); + st->Execute(); + st->Close(); + + IBPP::Time tb; + IBPP::Time te; + + for(i = 0; i < DIR_NUM; i++) + { + + tb.SetTime(td.dirPrice[i].hDay, td.dirPrice[i].mDay, 0); + te.SetTime(td.dirPrice[i].hNight, td.dirPrice[i].mNight, 0); + + pda = td.dirPrice[i].priceDayA * 1024 * 1024; + pdb = td.dirPrice[i].priceDayB * 1024 * 1024; + + if (td.dirPrice[i].singlePrice) + { + pna = pda; + pnb = pdb; + } + else + { + pna = td.dirPrice[i].priceNightA; + pnb = td.dirPrice[i].priceNightB; + } + + if (td.dirPrice[i].noDiscount) + { + threshold = 0xffFFffFF; + } + else + { + threshold = td.dirPrice[i].threshold; + } + + st->Prepare("update tb_tariffs_params set \ + price_day_a = ?, \ + price_day_b = ?, \ + price_night_a = ?, \ + price_night_b = ?, \ + threshold = ?, \ + time_day_begins = ?, \ + time_day_ends = ? \ + where fk_tariff = ? and dir_num = ?"); + st->Set(1, pda); + st->Set(2, pdb); + st->Set(3, pna); + st->Set(4, pnb); + st->Set(5, threshold); + st->Set(6, tb); + st->Set(7, te); + st->Set(8, id); + st->Set(9, i); + st->Execute(); + st->Close(); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::RestoreTariff(TARIFF_DATA * td, + const string & tariffName) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +int32_t id; +int16_t dir; +int i; +IBPP::Time tb, te; +int h, m, s; + +td->tariffConf.name = tariffName; + +try + { + tr->Start(); + st->Prepare("select * from tb_tariffs where name = ?"); + st->Set(1, tariffName); + st->Execute(); + if (!st->Fetch()) + { + strError = "Tariff \"" + tariffName + "\" not found in database"; + printfd(__FILE__, "Tariff '%s' not found in database\n", tariffName.c_str()); + tr->Rollback(); + return -1; + } + st->Get(1, id); + st->Get(3, td->tariffConf.fee); + st->Get(4, td->tariffConf.free); + st->Get(5, td->tariffConf.passiveCost); + st->Get(6, td->tariffConf.traffType); + st->Close(); + st->Prepare("select * from tb_tariffs_params where fk_tariff = ?"); + st->Set(1, id); + st->Execute(); + i = 0; + while (st->Fetch()) + { + i++; + if (i > DIR_NUM) + { + strError = "Too mach params for tariff \"" + tariffName + "\""; + printfd(__FILE__, "Too mach params for tariff '%s'\n", tariffName.c_str()); + tr->Rollback(); + return -1; + } + st->Get(3, dir); + st->Get(4, td->dirPrice[dir].priceDayA); + td->dirPrice[dir].priceDayA /= 1024*1024; + st->Get(5, td->dirPrice[dir].priceDayB); + td->dirPrice[dir].priceDayB /= 1024*1024; + st->Get(6, td->dirPrice[dir].priceNightA); + td->dirPrice[dir].priceNightA /= 1024*1024; + st->Get(7, td->dirPrice[dir].priceNightB); + td->dirPrice[dir].priceNightB /= 1024*1024; + st->Get(8, td->dirPrice[dir].threshold); + if (td->dirPrice[dir].priceDayA == td->dirPrice[dir].priceNightA && + td->dirPrice[dir].priceDayB == td->dirPrice[dir].priceNightB) + { + td->dirPrice[dir].singlePrice = true; + } + else + { + td->dirPrice[dir].singlePrice = false; + } + if (td->dirPrice[dir].threshold == (int)0xffFFffFF) + { + td->dirPrice[dir].noDiscount = true; + } + else + { + + td->dirPrice[dir].noDiscount = false; + } + st->Get(9, tb); + st->Get(10, te); + tb.GetTime(h, m, s); + td->dirPrice[dir].hDay = h; + td->dirPrice[dir].mDay = m; + te.GetTime(h, m, s); + td->dirPrice[dir].hNight = h; + td->dirPrice[dir].mNight = m; + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/firebird/firebird_store_users.cpp b/projects/stargazer/plugins/store/firebird/firebird_store_users.cpp new file mode 100644 index 00000000..035ae134 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store_users.cpp @@ -0,0 +1,830 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * User manipulation methods + * + * $Revision: 1.19 $ + * $Date: 2010/01/19 11:07:25 $ + * + */ + +#include "stg_const.h" +#include "firebird_store.h" +#include "ibpp.h" + +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::GetUsersList(vector * usersList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +string name; + +try + { + tr->Start(); + st->Execute("select name from tb_users"); + while (st->Fetch()) + { + st->Get(1, name); + usersList->push_back(name); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::AddUser(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_add_user(?, ?)"); + st->Set(1, name); + st->Set(2, DIR_NUM); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::DelUser(const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_delete_user(?)"); + st->Set(1, login); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveUserStat(const USER_STAT & stat, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +return SaveStat(stat, login); +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveStat(const USER_STAT & stat, + const string & login, + int year, + int month) const +{ + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +IBPP::Timestamp actTime; +IBPP::Timestamp addTime; +IBPP::Date dt; +int i; +int32_t sid, uid; + +try + { + tr->Start(); + st->Prepare("select pk_user from tb_users where name = ?"); + st->Set(1, login); + st->Execute(); + if (!st->Fetch()) + { + strError = "User \"" + login + "\" not found in database"; + printfd(__FILE__, "User '%s' not found in database\n", login.c_str()); + tr->Rollback(); + return -1; + } + st->Get(1, uid); + st->Close(); + st->Prepare("select first 1 pk_stat from tb_stats where fk_user = ? order by stats_date desc"); + st->Set(1, uid); + st->Execute(); + if (!st->Fetch()) + { + tr->Rollback(); + strError = "No stat info for user \"" + login + "\""; + printfd(__FILE__, "No stat info for user '%s'\n", login.c_str()); + return -1; + } + st->Get(1, sid); + st->Close(); + + time_t2ts(stat.lastActivityTime, &actTime); + time_t2ts(stat.lastCashAddTime, &addTime); + if (year != 0) + ym2date(year, month, &dt); + else + dt.Today(); + + st->Prepare("update tb_stats set \ + cash = ?, \ + free_mb = ?, \ + last_activity_time = ?, \ + last_cash_add = ?, \ + last_cash_add_time = ?, \ + passive_time = ?, \ + stats_date = ? \ + where pk_stat = ?"); + + st->Set(1, stat.cash); + st->Set(2, stat.freeMb); + st->Set(3, actTime); + st->Set(4, stat.lastCashAdd); + st->Set(5, addTime); + st->Set(6, (int32_t)stat.passiveTime); + st->Set(7, dt); + st->Set(8, sid); + + st->Execute(); + st->Close(); + + for(i = 0; i < DIR_NUM; i++) + { + st->Prepare("update tb_stats_traffic set \ + upload = ?, \ + download = ? \ + where fk_stat = ? and dir_num = ?"); + st->Set(1, (int64_t)stat.up[i]); + st->Set(2, (int64_t)stat.down[i]); + st->Set(3, sid); + st->Set(4, i); + st->Execute(); + st->Close(); + } + + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveUserConf(const USER_CONF & conf, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +int i; +int32_t uid; +IBPP::Timestamp creditExpire; +vector::const_iterator it; + +try + { + tr->Start(); + st->Prepare("select pk_user from tb_users where name = ?"); + st->Set(1, login); + st->Execute(); + if (!st->Fetch()) + { + strError = "User \"" + login + "\" not found in database"; + printfd(__FILE__, "User '%s' not found in database\n", login.c_str()); + tr->Rollback(); + return -1; + } + st->Get(1, uid); + st->Close(); + + time_t2ts(conf.creditExpire, &creditExpire); + + st->Prepare("update tb_users set \ + address = ?, \ + always_online = ?, \ + credit = ?, \ + credit_expire = ?, \ + disabled = ?, \ + disabled_detail_stat = ?, \ + email = ?, \ + grp = ?, \ + note = ?, \ + passive = ?, \ + passwd = ?, \ + phone = ?, \ + fk_tariff = (select pk_tariff from tb_tariffs \ + where name = ?), \ + fk_tariff_change = (select pk_tariff from tb_tariffs \ + where name = ?), \ + fk_corporation = (select pk_corporation from tb_corporations \ + where name = ?), \ + real_name = ? \ + where pk_user = ?"); + + st->Set(1, conf.address); + st->Set(2, (bool)conf.alwaysOnline); + st->Set(3, conf.credit); + st->Set(4, creditExpire); + st->Set(5, (bool)conf.disabled); + st->Set(6, (bool)conf.disabledDetailStat); + st->Set(7, conf.email); + st->Set(8, conf.group); + st->Set(9, conf.note); + st->Set(10, (bool)conf.passive); + st->Set(11, conf.password); + st->Set(12, conf.phone); + st->Set(13, conf.tariffName); + st->Set(14, conf.nextTariff); + st->Set(15, conf.corp); + st->Set(16, conf.realName); + st->Set(17, uid); + + st->Execute(); + st->Close(); + + st->Prepare("delete from tb_users_services where fk_user = ?"); + st->Set(1, uid); + st->Execute(); + st->Close(); + + st->Prepare("insert into tb_users_services (fk_user, fk_service) \ + values (?, (select pk_service from tb_services \ + where name = ?))"); + for(it = conf.service.begin(); it != conf.service.end(); ++it) + { + st->Set(1, uid); + st->Set(2, *it); + st->Execute(); + } + st->Close(); + + st->Prepare("delete from tb_users_data where fk_user = ?"); + st->Set(1, uid); + st->Execute(); + st->Close(); + + i = 0; + st->Prepare("insert into tb_users_data (fk_user, data, num) values (?, ?, ?)"); + for (it = conf.userdata.begin(); it != conf.userdata.end(); ++it) + { + st->Set(1, uid); + st->Set(2, *it); + st->Set(3, i++); + st->Execute(); + } + st->Close(); + + st->Prepare("delete from tb_allowed_ip where fk_user = ?"); + st->Set(1, uid); + st->Execute(); + + st->Prepare("insert into tb_allowed_ip (fk_user, ip, mask) values (?, ?, ?)"); + for(i = 0; i < conf.ips.Count(); i++) + { + st->Set(1, uid); + st->Set(2, (int32_t)conf.ips[i].ip); + st->Set(3, (int32_t)conf.ips[i].mask); + st->Execute(); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::RestoreUserStat(USER_STAT * stat, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +IBPP::Timestamp actTime, addTime; +int i, dir; +int32_t uid, sid, passiveTime; + +try + { + tr->Start(); + st->Prepare("select pk_user from tb_users where name = ?"); + st->Set(1, login); + st->Execute(); + if (!st->Fetch()) + { + strError = "User \"" + login + "\" not found in database"; + printfd(__FILE__, "User '%s' not found in database\n", login.c_str()); + return -1; + } + st->Get(1, uid); + st->Close(); + + st->Prepare("select first 1 pk_stat, cash, free_mb, last_activity_time, \ + last_cash_add, last_cash_add_time, passive_time from tb_stats \ + where fk_user = ? order by stats_date desc"); + st->Set(1, uid); + st->Execute(); + if (!st->Fetch()) + { + strError = "No stat info for user \"" + login + "\""; + printfd(__FILE__, "No stat info for user '%s'\n", login.c_str()); + tr->Rollback(); + return -1; + } + + st->Get(1, sid); + st->Get(2, stat->cash); + st->Get(3, stat->freeMb); + st->Get(4, actTime); + st->Get(5, stat->lastCashAdd); + st->Get(6, addTime); + st->Get(7, passiveTime); + + stat->passiveTime = passiveTime; + + stat->lastActivityTime = ts2time_t(actTime); + + stat->lastCashAddTime = ts2time_t(addTime); + + st->Close(); + st->Prepare("select * from tb_stats_traffic where fk_stat = ?"); + st->Set(1, sid); + st->Execute(); + for(i = 0; i < DIR_NUM; i++) + { + if (st->Fetch()) + { + st->Get(3, dir); + st->Get(5, (int64_t &)stat->up[dir]); + st->Get(4, (int64_t &)stat->down[dir]); + } + else + { + break; + } + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::RestoreUserConf(USER_CONF * conf, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amRead, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +int32_t uid; +int i; +IBPP::Timestamp timestamp; +IP_MASK im; +string name; +bool test; + +try + { + tr->Start(); + st->Prepare("select tb_users.pk_user, tb_users.address, tb_users.always_online, \ + tb_users.credit, tb_users.credit_expire, tb_users.disabled, \ + tb_users.disabled_detail_stat, tb_users.email, tb_users.grp, \ + tb_users.note, tb_users.passive, tb_users.passwd, tb_users.phone, \ + tb_users.real_name, tf1.name, tf2.name, tb_corporations.name \ + from tb_users left join tb_tariffs tf1 \ + on tf1.pk_tariff = tb_users.fk_tariff \ + left join tb_tariffs tf2 \ + on tf2.pk_tariff = tb_users.fk_tariff_change \ + left join tb_corporations \ + on tb_corporations.pk_corporation = tb_users.fk_corporation \ + where tb_users.name = ?"); + st->Set(1, login); + st->Execute(); + if (!st->Fetch()) + { + strError = "User \"" + login + "\" not found in database"; + printfd(__FILE__, "User '%s' not found in database", login.c_str()); + tr->Rollback(); + return -1; + } + st->Get(1, uid); + // Getting base config + st->Get(2, conf->address); + st->Get(3, test); + conf->alwaysOnline = test; + st->Get(4, conf->credit); + st->Get(5, timestamp); + + conf->creditExpire = ts2time_t(timestamp); + + st->Get(6, test); + conf->disabled = test; + st->Get(7, test); + conf->disabledDetailStat = test; + st->Get(8, conf->email); + st->Get(9, conf->group); + st->Get(10, conf->note); + st->Get(11, test); + conf->passive = test; + st->Get(12, conf->password); + st->Get(13, conf->phone); + st->Get(14, conf->realName); + st->Get(15, conf->tariffName); + st->Get(16, conf->nextTariff); + st->Get(17, conf->corp); + + if (conf->tariffName == "") + conf->tariffName = NO_TARIFF_NAME; + if (conf->corp == "") + conf->corp = NO_CORP_NAME; + + // Services + st->Close(); + st->Prepare("select name from tb_services \ + where pk_service in \ + (select fk_service from tb_users_services \ + where fk_user = ?)"); + st->Set(1, uid); + st->Execute(); + while (st->Fetch()) + { + st->Get(1, name); + conf->service.push_back(name); + } + + // User data + st->Close(); + st->Prepare("select data, num from tb_users_data where fk_user = ? order by num"); + st->Set(1, uid); + st->Execute(); + while (st->Fetch()) + { + st->Get(2, i); + st->Get(1, conf->userdata[i]); + } + + // User IPs + st->Close(); + st->Prepare("select ip, mask from tb_allowed_ip \ + where fk_user = ?"); + st->Set(1, uid); + st->Execute(); + conf->ips.Erase(); + while (st->Fetch()) + { + st->Get(1, (int32_t &)im.ip); + st->Get(2, (int32_t &)im.mask); + conf->ips.Add(im); + } + + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message = "") const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); +IBPP::Timestamp now; +now.Now(); + +string temp = ""; // Composed message for log + +try + { + tr->Start(); + temp += "Admin \"" + admLogin + "\", "; + temp += inet_ntostring(admIP); + temp += ": "; + temp = temp + message; + //---------------------------------------------------------------------------------------- + // Checking and inserting parameters in params table + st->Prepare("select pk_parameter from tb_parameters where name = ?"); + st->Set(1, paramName); + st->Execute(); + if (!st->Fetch()) + { + st->Close(); + st->Prepare("insert into tb_parameters (name) values (?)"); + st->Set(1, paramName); + st->Execute(); + } + st->Close(); + //---------------------------------------------------------------------------------------- + st->Prepare("insert into tb_params_log \ + (fk_user, fk_parameter, event_time, from_val, to_val, comment) \ + values ((select pk_user from tb_users \ + where name = ?), \ + (select pk_parameter from tb_parameters \ + where name = ?), \ + ?, ?, ?, ?)"); + st->Set(1, login); + st->Set(2, paramName); + st->Set(3, now); + st->Set(4, oldValue); + st->Set(5, newValue); + st->Set(6, temp); + st->Execute(); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::WriteUserConnect(const string & login, uint32_t ip) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); +IBPP::Timestamp now; +now.Now(); + +try + { + tr->Start(); + st->Prepare("execute procedure sp_append_session_log(?, ?, 'c', ?)"); + st->Set(1, login); + st->Set(2, now); + st->Set(3, (int32_t)ip); + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); +IBPP::Timestamp now; +now.Now(); + +int32_t id; +int i; + +try + { + tr->Start(); + st->Prepare("execute procedure sp_append_session_log(?, ?, 'd', 0)"); + st->Set(1, login); + st->Set(2, now); + st->Execute(); + st->Get(1, id); + st->Prepare("insert into tb_sessions_data \ + (fk_session_log, dir_num, session_upload, \ + session_download, month_upload, month_download) \ + values (?, ?, ?, ?, ?, ?)"); + for(i = 0; i < DIR_NUM; i++) + { + st->Set(1, id); + st->Set(2, i); + st->Set(3, (int64_t)sessionUp[i]); + st->Set(4, (int64_t)sessionDown[i]); + st->Set(5, (int64_t)up[i]); + st->Set(6, (int64_t)down[i]); + st->Execute(); + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +IBPP::Timestamp statTime, now; +now.Now(); + +time_t2ts(lastStat, &statTime); + +try + { + tr->Start(); + map::const_iterator it; + it = statTree->begin(); + st->Prepare("insert into tb_detail_stats \ + (till_time, from_time, fk_user, dir_num, \ + ip, download, upload, cost) \ + values (?, ?, (select pk_user from tb_users \ + where name = ?), \ + ?, ?, ?, ?, ?)"); + while (it != statTree->end()) + { + st->Set(1, now); + st->Set(2, statTime); + st->Set(3, login); + st->Set(4, it->first.dir); + st->Set(5, (int32_t)it->first.ip); + st->Set(6, (int64_t)it->second.down); + st->Set(7, (int64_t)it->second.up); + st->Set(8, it->second.cash); + st->Execute(); + ++it; + } + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int FIREBIRD_STORE::SaveMonthStat(const USER_STAT & stat, int month, int year, const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, til, tlr); +IBPP::Statement st = IBPP::StatementFactory(db, tr); + +IBPP::Timestamp now; +IBPP::Date nowDate; +nowDate.Today(); +now.Now(); +int32_t id; +int i; + +if (SaveStat(stat, login, year, month)) + { + return -1; + } + +try + { + tr->Start(); + + st->Prepare("execute procedure sp_add_stat(?, 0, 0, ?, 0, ?, 0, ?)"); + st->Set(1, login); + st->Set(2, now); + st->Set(3, now); + st->Set(4, nowDate); + + st->Execute(); + st->Get(1, id); + st->Close(); + + st->Prepare("insert into tb_stats_traffic \ + (fk_stat, dir_num, upload, download) \ + values (?, ?, 0, 0)"); + + for(i = 0; i < DIR_NUM; i++) + { + st->Set(1, id); + st->Set(2, i); + st->Execute(); + } + + tr->Commit(); + } + +catch (IBPP::Exception & ex) + { + tr->Rollback(); + strError = "IBPP exception"; + printfd(__FILE__, ex.what()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/firebird/firebird_store_utils.cpp b/projects/stargazer/plugins/store/firebird/firebird_store_utils.cpp new file mode 100644 index 00000000..9f7fe6b8 --- /dev/null +++ b/projects/stargazer/plugins/store/firebird/firebird_store_utils.cpp @@ -0,0 +1,65 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Vairous utility methods + * + * $Revision: 1.8 $ + * $Date: 2010/03/04 12:20:32 $ + * + */ + +#include + +#include "firebird_store.h" +#include "ibpp.h" + +//----------------------------------------------------------------------------- +time_t FIREBIRD_STORE::ts2time_t(const IBPP::Timestamp & ts) const +{ + char buf[32]; + int year, month, day, hour, min, sec; + struct tm time_tm; + + memset(&time_tm, 0, sizeof(time_tm)); + ts.GetDate(year, month, day); + ts.GetTime(hour, min, sec); + sprintf(buf, "%d-%d-%d %d:%d:%d", year, month, day, hour, min, sec); + stg_strptime(buf, "%Y-%m-%d %H:%M:%S", &time_tm); + + return mktime(&time_tm); +} +//----------------------------------------------------------------------------- +void FIREBIRD_STORE::time_t2ts(time_t t, IBPP::Timestamp * ts) const +{ + struct tm res; + + localtime_r(&t, &res); // Reenterable + + *ts = IBPP::Timestamp(res.tm_year + 1900, res.tm_mon + 1, res.tm_mday, res.tm_hour, res.tm_min, res.tm_sec); +} +//----------------------------------------------------------------------------- +void FIREBIRD_STORE::ym2date(int year, int month, IBPP::Date * date) const +{ + date->SetDate(year + 1900, month + 1, 1); + date->EndOfMonth(); +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/firebird/mod_store_firebird.so b/projects/stargazer/plugins/store/firebird/mod_store_firebird.so new file mode 100755 index 0000000000000000000000000000000000000000..588ec84d719b4b78c6ab3619fdb61326f967417d GIT binary patch literal 1347636 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%0Dl;>G)~T~|G+vALQC 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

-)xFh|hq$)y{qch*Uv;Imd-B7H*>OYHzwtVCn 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 literal 0 HcmV?d00001 diff --git a/projects/stargazer/plugins/store/mysql/mysql_store.cpp b/projects/stargazer/plugins/store/mysql/mysql_store.cpp new file mode 100644 index 00000000..102be151 --- /dev/null +++ b/projects/stargazer/plugins/store/mysql/mysql_store.cpp @@ -0,0 +1,2063 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "user_ips.h" +#include "user_conf.h" +#include "user_stat.h" +#include "mysql_store.h" +#include "blowfish.h" + +#define adm_enc_passwd "cjeifY8m3" +char qbuf[4096]; + +using namespace std; + +const int pt_mega = 1024 * 1024; +const string badSyms = "'`"; +const char repSym = '\"'; +const int RepitTimes = 3; + +int GetInt(const string & str, int * val, int defaultVal) +{ + char *res; + + *val = strtol(str.c_str(), &res, 10); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +int GetDouble(const string & str, double * val, double defaultVal) +{ + char *res; + + *val = strtod(str.c_str(), &res); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +int GetTime(const string & str, time_t * val, time_t defaultVal) +{ + char *res; + + *val = strtol(str.c_str(), &res, 10); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +//----------------------------------------------------------------------------- +string ReplaceStr(string source, const string symlist, const char chgsym) +{ + string::size_type pos=0; + + while( (pos = source.find_first_of(symlist,pos)) != string::npos) + source.replace(pos, 1,1, chgsym); + + return source; +} + +int GetULongLongInt(const string & str, uint64_t * val, uint64_t defaultVal) +{ + char *res; + + *val = strtoull(str.c_str(), &res, 10); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +class MYSQL_STORE_CREATOR +{ +private: + MYSQL_STORE * ms; + +public: + MYSQL_STORE_CREATOR() + : ms(new MYSQL_STORE()) + { + }; + ~MYSQL_STORE_CREATOR() + { + delete ms; + }; + + MYSQL_STORE * GetStore() + { + return ms; + }; +} msc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_STORE * GetStore() +{ +return msc.GetStore(); +} +//----------------------------------------------------------------------------- +MYSQL_STORE_SETTINGS::MYSQL_STORE_SETTINGS() + : settings(NULL) +{ +} +//----------------------------------------------------------------------------- +MYSQL_STORE_SETTINGS::~MYSQL_STORE_SETTINGS() +{ + +} +//----------------------------------------------------------------------------- +int MYSQL_STORE_SETTINGS::ParseParam(const vector & moduleParams, + const string & name, string & result) +{ +PARAM_VALUE pv; +pv.param = name; +vector::const_iterator pvi; +pvi = find(moduleParams.begin(), moduleParams.end(), pv); +if (pvi == moduleParams.end()) + { + errorStr = "Parameter \'" + name + "\' not found."; + return -1; + } + +result = pvi->value[0]; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +if (ParseParam(s.moduleParams, "dbuser", dbUser) < 0) + return -1; +if (ParseParam(s.moduleParams, "rootdbpass", dbPass) < 0) + return -1; +if (ParseParam(s.moduleParams, "dbname", dbName) < 0) + return -1; +if (ParseParam(s.moduleParams, "dbhost", dbHost) < 0) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +const string & MYSQL_STORE_SETTINGS::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBUser() const +{ +return dbUser; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBPassword() const +{ +return dbPass; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBHost() const +{ +return dbHost; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBName() const +{ +return dbName; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +MYSQL_STORE::MYSQL_STORE() +{ +version = "mysql_store v.0.67"; +}; +//----------------------------------------------------------------------------- +MYSQL_STORE::~MYSQL_STORE() +{ +}; +//----------------------------------------------------------------------------- +void MYSQL_STORE::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::MysqlQuery(const char* sQuery,MYSQL * sock) const +{ + int ret,i; + + if( (ret = mysql_query(sock,sQuery)) ) + { + for(i=0; i * ParamList, + const string & table, const string & name) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock=NULL; +unsigned int num,i; + +ParamList->clear(); + +sprintf(qbuf,"SELECT %s FROM %s", name.c_str(), table.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't GetAllParams Query for: "; + errorStr += name + " - " + table + "\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't GetAllParams Results for: "; + errorStr += name + " - " + table + "\n"; + errorStr += mysql_error(sock); + return -1; +} + +num = mysql_num_rows(res); + +for(i=0;ipush_back(row[0]); +} + +mysql_free_result(res); +mysql_close(sock); + +return 0; +} + +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetUsersList(vector * usersList) const +{ +if(GetAllParams(usersList, "users", "login")) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetAdminsList(vector * adminsList) const +{ +if(GetAllParams(adminsList, "admins", "login")) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetTariffsList(vector * tariffsList) const +{ +if(GetAllParams(tariffsList, "tariffs", "name")) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::AddUser(const string & login) const +{ +sprintf(qbuf,"INSERT INTO users SET login='%s'", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add user:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::DelUser(const string & login) const +{ +sprintf(qbuf,"DELETE FROM users WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete user:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreUserConf(USER_CONF * conf, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +string query; + +query = "SELECT login, Password, Passive, Down, DisabledDetailStat, \ + AlwaysOnline, Tariff, Address, Phone, Email, Note, \ + RealName, StgGroup, Credit, TariffChange, "; + +for (int i = 0; i < USERDATA_NUM; i++) +{ + sprintf(qbuf, "Userdata%d, ", i); + query += qbuf; +} + +query += "CreditExpire, IP FROM users WHERE login='"; +query += login + "' LIMIT 1"; + +//sprintf(qbuf,"SELECT * FROM users WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlGetQuery(query.c_str(),sock)) +{ + errorStr = "Couldn't restore Tariff(on query):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore Tariff(on getting result):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +string param; + +conf->password = row[1]; + +if (conf->password.empty()) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' password is blank."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[2],&conf->passive, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter Passive."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[3], &conf->disabled, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter Down."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[4], &conf->disabledDetailStat, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter DisabledDetailStat."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[5], &conf->alwaysOnline, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter AlwaysOnline."; + mysql_close(sock); + return -1; + } + +conf->tariffName = row[6]; + +if (conf->tariffName.empty()) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' tariff is blank."; + mysql_close(sock); + return -1; + } + +conf->address = row[7]; +conf->phone = row[8]; +conf->email = row[9]; +conf->note = row[10]; +conf->realName = row[11]; +conf->group = row[12]; + +if (GetDouble(row[13], &conf->credit, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter Credit."; + mysql_close(sock); + return -1; + } + +conf->nextTariff = row[14]; + +for (int i = 0; i < USERDATA_NUM; i++) + { + conf->userdata[i] = row[15+i]; + } + +GetTime(row[15+USERDATA_NUM], &conf->creditExpire, 0); + +string ipStr = row[16+USERDATA_NUM]; +USER_IPS i; +try + { + i = StrToIPS(ipStr); + } +catch (string s) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter IP address. " + s; + mysql_close(sock); + return -1; + } +conf->ips = i; + +mysql_free_result(res); +mysql_close(sock); + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreUserStat(USER_STAT * stat, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; + +string query; + +query = "SELECT "; + +for (int i = 0; i < DIR_NUM; i++) +{ + sprintf(qbuf, "D%d, U%d, ", i, i); + query += qbuf; +} + +query += "Cash, FreeMb, LastCashAdd, LastCashAddTime, PassiveTime, LastActivityTime \ + FROM users WHERE login = '"; +query += login + "'"; + +//sprintf(qbuf,"SELECT * FROM users WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlGetQuery(query.c_str() ,sock)) +{ + errorStr = "Couldn't restore UserStat(on query):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore UserStat(on getting result):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +unsigned int startPos=0; + +char s[22]; +uint64_t traffU[DIR_NUM]; +uint64_t traffD[DIR_NUM]; + +for (int i = 0; i < DIR_NUM; i++) + { + sprintf(s, "D%d", i); + if (GetULongLongInt(row[startPos+i*2],&traffD[i], 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter " + string(s); + mysql_close(sock); + return -1; + } + stat->down = traffD; + + sprintf(s, "U%d", i); + if (GetULongLongInt(row[startPos+i*2+1], &traffU[i], 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter " + string(s); + mysql_close(sock); + return -1; + } + stat->up = traffU; + }//for + +startPos += (2*DIR_NUM); + +if (GetDouble(row[startPos], &stat->cash, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter Cash"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[startPos+1],&stat->freeMb, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter FreeMb"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[startPos+2], &stat->lastCashAdd, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter LastCashAdd"; + mysql_close(sock); + return -1; + } + +if (GetTime(row[startPos+3], &stat->lastCashAddTime, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter LastCashAddTime"; + mysql_close(sock); + return -1; + } + +if (GetTime(row[startPos+4], &stat->passiveTime, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter PassiveTime"; + mysql_close(sock); + return -1; + } + +if (GetTime(row[startPos+5], &stat->lastActivityTime, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter LastActivityTime"; + mysql_close(sock); + return -1; + } + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveUserConf(const USER_CONF & conf, const string & login) const +{ +string param; +string res; + +strprintf(&res,"UPDATE users SET Password='%s', Passive=%d, Down=%d, DisabledDetailStat = %d, "\ + "AlwaysOnline=%d, Tariff='%s', Address='%s', Phone='%s', Email='%s', "\ + "Note='%s', RealName='%s', StgGroup='%s', Credit=%f, TariffChange='%s', ", + conf.password.c_str(), + conf.passive, + conf.disabled, + conf.disabledDetailStat, + conf.alwaysOnline, + conf.tariffName.c_str(), + (ReplaceStr(conf.address,badSyms,repSym)).c_str(), + (ReplaceStr(conf.phone,badSyms,repSym)).c_str(), + (ReplaceStr(conf.email,badSyms,repSym)).c_str(), + (ReplaceStr(conf.note,badSyms,repSym)).c_str(), + (ReplaceStr(conf.realName,badSyms,repSym)).c_str(), + (ReplaceStr(conf.group,badSyms,repSym)).c_str(), + conf.credit, + conf.nextTariff.c_str() + ); + +for (int i = 0; i < USERDATA_NUM; i++) + { + strprintf(¶m, " Userdata%d='%s',", i, + (ReplaceStr(conf.userdata[i],badSyms,repSym)).c_str()); + res += param; + } + +strprintf(¶m, " CreditExpire=%d,", conf.creditExpire); +res += param; + +stringstream ipStr; +ipStr << conf.ips; + +strprintf(¶m, " IP='%s'", ipStr.str().c_str()); +res += param; + +strprintf(¶m, " WHERE login='%s' LIMIT 1", login.c_str()); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't save user conf:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveUserStat(const USER_STAT & stat, const string & login) const +{ +string param; +string res; + +res = "UPDATE users SET"; + +for (int i = 0; i < DIR_NUM; i++) + { + strprintf(¶m, " D%d=%lld,", i, stat.down[i]); + res += param; + + strprintf(¶m, " U%d=%lld,", i, stat.up[i]); + res += param; + } + +strprintf(¶m, " Cash=%f, FreeMb=%f, LastCashAdd=%f, LastCashAddTime=%d,"\ + " PassiveTime=%d, LastActivityTime=%d", + stat.cash, + stat.freeMb, + stat.lastCashAdd, + stat.lastCashAddTime, + stat.passiveTime, + stat.lastActivityTime + ); +res += param; + +strprintf(¶m, " WHERE login='%s' LIMIT 1", login.c_str()); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't save user stat:\n"; +// errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteLogString(const string & str, const string & login) const +{ +string res, tempStr; +time_t t; +tm * lt; + +t = time(NULL); +lt = localtime(&t); + +MYSQL_RES* result; +MYSQL * sock; +strprintf(&tempStr, "logs_%02d_%4d", lt->tm_mon+1, lt->tm_year+1900); +if (!(sock=MysqlConnect())){ + errorStr = "Couldn't connect to Server"; + return -1; +} +if (!(result=mysql_list_tables(sock,tempStr.c_str() ))) +{ + errorStr = "Couldn't get table " + tempStr + ":\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +unsigned int num_rows = mysql_num_rows(result); + +mysql_free_result(result); + +if (num_rows < 1) +{ + sprintf(qbuf,"CREATE TABLE logs_%02d_%4d (unid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, login VARCHAR(40),text TEXT)", + lt->tm_mon+1, lt->tm_year+1900); + + if(MysqlQuery(qbuf,sock)) + { + errorStr = "Couldn't create WriteDetailedStat table:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; + } +} + +strprintf(&res, "%s -- %s",LogDate(t), str.c_str()); + +string send; + +strprintf(&send,"INSERT INTO logs_%02d_%4d SET login='%s', text='%s'", + lt->tm_mon+1, lt->tm_year+1900, + login.c_str(), (ReplaceStr(res,badSyms,repSym)).c_str()); + +if(MysqlQuery(send.c_str(),sock)) +{ + errorStr = "Couldn't write log string:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} +mysql_close(sock); +return 0; + +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message) const +{ +string userLogMsg = "Admin \'" + admLogin + "\', " + inet_ntostring(admIP) + ": \'" + + paramName + "\' parameter changed from \'" + oldValue + + "\' to \'" + newValue + "\'. " + message; + +return WriteLogString(userLogMsg, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteUserConnect(const string & login, uint32_t ip) const +{ +string logStr = "Connect, " + inet_ntostring(ip); +return WriteLogString(logStr, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const +{ +string logStr = "Disconnect, "; +stringstream sssu; +stringstream sssd; +stringstream ssmu; +stringstream ssmd; +stringstream sscash; + +ssmu << up; +ssmd << down; + +sssu << sessionUp; +sssd << sessionDown; + +sscash << cash; + +logStr += " session upload: \'"; +logStr += sssu.str(); +logStr += "\' session download: \'"; +logStr += sssd.str(); +logStr += "\' month upload: \'"; +logStr += ssmu.str(); +logStr += "\' month download: \'"; +logStr += ssmd.str(); +logStr += "\' cash: \'"; +logStr += sscash.str(); +logStr += "\'"; + +return WriteLogString(logStr, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveMonthStat(const USER_STAT & stat, int month, int year, + const string & login) const +{ +string param, res; + +strprintf(&res, "INSERT INTO stat SET login='%s', month=%d, year=%d,", + login.c_str(), month+1, year+1900); + +for (int i = 0; i < DIR_NUM; i++) + { + strprintf(¶m, " U%d=%lld,", i, stat.up[i]); + res += param; + + strprintf(¶m, " D%d=%lld,", i, stat.down[i]); + res += param; + } + +strprintf(¶m, " cash=%f", stat.cash); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't SaveMonthStat:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//-----------------------------------------------------------------------------*/ +int MYSQL_STORE::AddAdmin(const string & login) const +{ +sprintf(qbuf,"INSERT INTO admins SET login='%s'", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//-----------------------------------------------------------------------------*/ +int MYSQL_STORE::DelAdmin(const string & login) const +{ +sprintf(qbuf,"DELETE FROM admins where login='%s' LIMIT 1", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//-----------------------------------------------------------------------------*/ +int MYSQL_STORE::SaveAdmin(const ADMIN_CONF & ac) const +{ +char passwordE[2 * ADM_PASSWD_LEN + 2]; +char pass[ADM_PASSWD_LEN + 1]; +char adminPass[ADM_PASSWD_LEN + 1]; + +memset(pass, 0, sizeof(pass)); +memset(adminPass, 0, sizeof(adminPass)); + +BLOWFISH_CTX ctx; +EnDecodeInit(adm_enc_passwd, strlen(adm_enc_passwd), &ctx); + +strncpy(adminPass, ac.password.c_str(), ADM_PASSWD_LEN); +adminPass[ADM_PASSWD_LEN - 1] = 0; + +for (int i = 0; i < ADM_PASSWD_LEN/8; i++) + { + EncodeString(pass + 8*i, adminPass + 8*i, &ctx); + } + +pass[ADM_PASSWD_LEN - 1] = 0; +Encode12(passwordE, pass, ADM_PASSWD_LEN); + +sprintf(qbuf,"UPDATE admins SET password='%s', ChgConf=%d, ChgPassword=%d, "\ + "ChgStat=%d, ChgCash=%d, UsrAddDel=%d, ChgTariff=%d, ChgAdmin=%d "\ + "WHERE login='%s' LIMIT 1", + passwordE, + ac.priv.userConf, + ac.priv.userPasswd, + ac.priv.userStat, + ac.priv.userCash, + ac.priv.userAddDel, + ac.priv.tariffChg, + ac.priv.adminChg, + ac.login.c_str() + ); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't save admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreAdmin(ADMIN_CONF * ac, const string & login) const +{ +char pass[ADM_PASSWD_LEN + 1]; +char password[ADM_PASSWD_LEN + 1]; +char passwordE[2*ADM_PASSWD_LEN + 2]; +BLOWFISH_CTX ctx; + +string p; +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +sprintf(qbuf,"SELECT * FROM admins WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't restore admin:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore admin:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if ( mysql_num_rows(res) == 0) +{ + mysql_free_result(res); + errorStr = "Couldn't restore admin as couldn't found him in table.\n"; + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +p = row[1]; +int a; + +if(p.length() == 0) +{ + mysql_free_result(res); + errorStr = "Error in parameter password"; + mysql_close(sock); + return -1; +} + +memset(passwordE, 0, sizeof(passwordE)); +strncpy(passwordE, p.c_str(), 2*ADM_PASSWD_LEN); + +memset(pass, 0, sizeof(pass)); + +if (passwordE[0] != 0) + { + Decode21(pass, passwordE); + EnDecodeInit(adm_enc_passwd, strlen(adm_enc_passwd), &ctx); + + for (int i = 0; i < ADM_PASSWD_LEN/8; i++) + { + DecodeString(password + 8*i, pass + 8*i, &ctx); + } + } +else + { + password[0] = 0; + } + +ac->password = password; + +if (GetInt(row[2], &a, 0) == 0) + ac->priv.userConf = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgConf"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[3], &a, 0) == 0) + ac->priv.userPasswd = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgPassword"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[4], &a, 0) == 0) + ac->priv.userStat = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgStat"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[5], &a, 0) == 0) + ac->priv.userCash = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgCash"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[6], &a, 0) == 0) + ac->priv.userAddDel = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter UsrAddDel"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[7], &a, 0) == 0) + ac->priv.tariffChg = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgTariff"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[8], &a, 0) == 0) + ac->priv.adminChg = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgAdmin"; + mysql_close(sock); + return -1; + } + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::AddTariff(const string & name) const +{ +sprintf(qbuf,"INSERT INTO tariffs SET name='%s'", name.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add tariff:\n"; +// errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::DelTariff(const string & name) const +{ +sprintf(qbuf,"DELETE FROM tariffs WHERE name='%s' LIMIT 1", name.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete tariff: "; +// errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreTariff(TARIFF_DATA * td, const string & tariffName) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +sprintf(qbuf,"SELECT * FROM tariffs WHERE name='%s' LIMIT 1", tariffName.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't restore Tariff:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore Tariff:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +string str; +td->tariffConf.name = tariffName; + +row = mysql_fetch_row(res); + +string param; +for (int i = 0; idirPrice[i].hDay, + td->dirPrice[i].mDay, + td->dirPrice[i].hNight, + td->dirPrice[i].mNight); + + strprintf(¶m, "PriceDayA%d", i); + if (GetDouble(row[1+i*8], &td->dirPrice[i].priceDayA, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceDayA /= (1024*1024); + + strprintf(¶m, "PriceDayB%d", i); + if (GetDouble(row[2+i*8], &td->dirPrice[i].priceDayB, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceDayB /= (1024*1024); + + strprintf(¶m, "PriceNightA%d", i); + if (GetDouble(row[3+i*8], &td->dirPrice[i].priceNightA, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceNightA /= (1024*1024); + + strprintf(¶m, "PriceNightB%d", i); + if (GetDouble(row[4+i*8], &td->dirPrice[i].priceNightB, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceNightB /= (1024*1024); + + strprintf(¶m, "Threshold%d", i); + if (GetInt(row[5+i*8], &td->dirPrice[i].threshold, 0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + + strprintf(¶m, "SinglePrice%d", i); + if (GetInt(row[8+i*8], &td->dirPrice[i].singlePrice, 0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + + strprintf(¶m, "NoDiscount%d", i); + if (GetInt(row[7+i*8], &td->dirPrice[i].noDiscount, 0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + }//main for + +if (GetDouble(row[2+8*DIR_NUM], &td->tariffConf.fee, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter Fee"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[3+8*DIR_NUM], &td->tariffConf.free, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter Free"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[1+8*DIR_NUM], &td->tariffConf.passiveCost, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter PassiveCost"; + mysql_close(sock); + return -1; + } + + str = row[4+8*DIR_NUM]; + param = "TraffType"; + + if (str.length() == 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + +if (!strcasecmp(str.c_str(), "up")) + td->tariffConf.traffType = TRAFF_UP; +else + if (!strcasecmp(str.c_str(), "down")) + td->tariffConf.traffType = TRAFF_DOWN; + else + if (!strcasecmp(str.c_str(), "up+down")) + td->tariffConf.traffType = TRAFF_UP_DOWN; + else + if (!strcasecmp(str.c_str(), "max")) + td->tariffConf.traffType = TRAFF_MAX; + else + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter TraffType incorrect"; + mysql_close(sock); + return -1; + } + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveTariff(const TARIFF_DATA & td, const string & tariffName) const +{ +string param; + +string res="UPDATE tariffs SET"; + +for (int i = 0; i < DIR_NUM; i++) + { + strprintf(¶m, " PriceDayA%d=%f,", i, + td.dirPrice[i].priceDayA * pt_mega); + res += param; + + strprintf(¶m, " PriceDayB%d=%f,", i, + td.dirPrice[i].priceDayB * pt_mega); + res += param; + + strprintf(¶m, " PriceNightA%d=%f,", i, + td.dirPrice[i].priceNightA * pt_mega); + res += param; + + strprintf(¶m, " PriceNightB%d=%f,", i, + td.dirPrice[i].priceNightB * pt_mega); + res += param; + + strprintf(¶m, " Threshold%d=%d,", i, + td.dirPrice[i].threshold); + res += param; + + string s; + strprintf(¶m, " Time%d", i); + + strprintf(&s, "%0d:%0d-%0d:%0d", + td.dirPrice[i].hDay, + td.dirPrice[i].mDay, + td.dirPrice[i].hNight, + td.dirPrice[i].mNight); + + res += (param + "='" + s + "',"); + + strprintf(¶m, " NoDiscount%d=%d,", i, + td.dirPrice[i].noDiscount); + res += param; + + strprintf(¶m, " SinglePrice%d=%d,", i, + td.dirPrice[i].singlePrice); + res += param; + } + +strprintf(¶m, " PassiveCost=%f,", td.tariffConf.passiveCost); +res += param; + +strprintf(¶m, " Fee=%f,", td.tariffConf.fee); +res += param; + +strprintf(¶m, " Free=%f,", td.tariffConf.free); +res += param; + +switch (td.tariffConf.traffType) + { + case TRAFF_UP: + res += " TraffType='up'"; + break; + case TRAFF_DOWN: + res += " TraffType='down'"; + break; + case TRAFF_UP_DOWN: + res += " TraffType='up+down'"; + break; + case TRAFF_MAX: + res += " TraffType='max'"; + break; + } +strprintf(¶m, " WHERE name='%s' LIMIT 1", tariffName.c_str()); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't save admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const +{ +string res, stTime, endTime, tempStr; +time_t t; +tm * lt; + +t = time(NULL); +lt = localtime(&t); + +if (lt->tm_hour == 0 && lt->tm_min <= 5) + { + t -= 3600 * 24; + lt = localtime(&t); + } + +MYSQL_RES* result; +MYSQL * sock; +strprintf(&tempStr, "detailstat_%02d_%4d", lt->tm_mon+1, lt->tm_year+1900); + +if (!(sock=MysqlConnect())){ + mysql_close(sock); + return -1; +} + +if (!(result=mysql_list_tables(sock,tempStr.c_str() ))) +{ + errorStr = "Couldn't get table " + tempStr + ":\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +unsigned int num_rows = mysql_num_rows(result); + +mysql_free_result(result); + +if (num_rows < 1) +{ + sprintf(qbuf,"CREATE TABLE detailstat_%02d_%4d (login VARCHAR(40) DEFAULT '',"\ + "day TINYINT DEFAULT 0,startTime TIME,endTime TIME,"\ + "IP VARCHAR(17) DEFAULT '',dir INT DEFAULT 0,"\ + "down BIGINT DEFAULT 0,up BIGINT DEFAULT 0, cash DOUBLE DEFAULT 0.0, INDEX (login), INDEX(dir), INDEX(day), INDEX(IP))", + lt->tm_mon+1, lt->tm_year+1900); + + if(MysqlQuery(qbuf,sock)) + { + errorStr = "Couldn't create WriteDetailedStat table:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; + } +} + +struct tm * lt1; +struct tm * lt2; + +lt1 = localtime(&lastStat); + +int h1, m1, s1; +int h2, m2, s2; + +h1 = lt1->tm_hour; +m1 = lt1->tm_min; +s1 = lt1->tm_sec; + +lt2 = localtime(&t); + +h2 = lt2->tm_hour; +m2 = lt2->tm_min; +s2 = lt2->tm_sec; + +strprintf(&stTime, "%02d:%02d:%02d", h1, m1, s1); +strprintf(&endTime, "%02d:%02d:%02d", h2, m2, s2); + +strprintf(&res,"INSERT INTO detailstat_%02d_%4d SET login='%s',"\ + "day=%d,startTime='%s',endTime='%s',", + lt->tm_mon+1, lt->tm_year+1900, + login.c_str(), + lt->tm_mday, + stTime.c_str(), + endTime.c_str() + ); + +int retRes; +map::const_iterator stIter; +stIter = statTree->begin(); + +while (stIter != statTree->end()) + { + strprintf(&tempStr,"IP='%s', dir=%d, down=%lld, up=%lld, cash=%f", + inet_ntostring(stIter->first.ip).c_str(), + stIter->first.dir, + stIter->second.down, + stIter->second.up, + stIter->second.cash + ); + + if( (retRes = MysqlQuery((res+tempStr).c_str(),sock)) ) + { + errorStr = "Couldn't insert data in WriteDetailedStat:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; + } + + result=mysql_store_result(sock); + if(result) + mysql_free_result(result); + + ++stIter; + } +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::AddMessage(STG_MSG * msg, const string & login) const +{ +struct timeval tv; + +gettimeofday(&tv, NULL); + +msg->header.id = ((long long)tv.tv_sec) * 1000000 + ((long long)tv.tv_usec); + +sprintf(qbuf,"INSERT INTO messages SET login='%s', id=%lld", + login.c_str(), + (long long)msg->header.id + ); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add message:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return EditMessage(*msg, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::EditMessage(const STG_MSG & msg, const string & login) const +{ +string res; + +strprintf(&res,"UPDATE messages SET type=%d, lastSendTime=%u, creationTime=%u, "\ + "showTime=%u, stgRepeat=%d, repeatPeriod=%u, text='%s' "\ + "WHERE login='%s' AND id=%lld LIMIT 1", + msg.header.type, + msg.header.lastSendTime, + msg.header.creationTime, + msg.header.showTime, + msg.header.repeat, + msg.header.repeatPeriod, + (ReplaceStr(msg.text,badSyms,repSym)).c_str(), + login.c_str(), + (long long)msg.header.id + ); + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't edit message:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetMessage(uint64_t id, STG_MSG * msg, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; + +sprintf(qbuf,"SELECT * FROM messages WHERE login='%s' AND id=%lld LIMIT 1", + login.c_str(), id); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't GetMessage:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't GetMessage:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +if(row[2]&&str2x(row[2], msg->header.type)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[3] && str2x(row[3], msg->header.lastSendTime)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[4] && str2x(row[4], msg->header.creationTime)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[5] && str2x(row[5], msg->header.showTime)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[6] && str2x(row[6], msg->header.repeat)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[7] && str2x(row[7], msg->header.repeatPeriod)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +msg->header.id = id; +msg->text = row[8]; + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::DelMessage(uint64_t id, const string & login) const +{ +sprintf(qbuf,"DELETE FROM messages WHERE login='%s' AND id=%lld LIMIT 1", + login.c_str(),(long long)id); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete Message:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetMessageHdrs(vector * hdrsList, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +sprintf(qbuf,"SELECT * FROM messages WHERE login='%s'", login.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't GetMessageHdrs:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't GetMessageHdrs:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +unsigned int i, num_rows = mysql_num_rows(res); +long long int unsigned id = 0; + +for (i=0; ipush_back(hdr); +} + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- + +int MYSQL_STORE::MysqlSetQuery(const char * Query) const { + + MYSQL * sock; + int ret=MysqlGetQuery(Query,sock); + mysql_close(sock); + return ret; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::MysqlGetQuery(const char * Query,MYSQL * & sock) const { + if (!(sock=MysqlConnect())) { + return -1; + } + return MysqlQuery(Query,sock); +} +//----------------------------------------------------------------------------- +MYSQL * MYSQL_STORE::MysqlConnect() const { + MYSQL * sock; + if ( !(sock=mysql_init(NULL)) ){ + errorStr= "mysql init susck\n"; + return NULL; + } + if (!(sock = mysql_real_connect(sock,storeSettings.GetDBHost().c_str(), + storeSettings.GetDBUser().c_str(),storeSettings.GetDBPassword().c_str(), + 0,0,NULL,0))) + { + errorStr = "Couldn't connect to mysql engine! With error:\n"; + errorStr += mysql_error(sock); + return NULL; + } + else{ + if(mysql_select_db(sock, storeSettings.GetDBName().c_str())){ + errorStr = "Database lost !\n"; + return NULL; + } + } + return sock; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/store/mysql/mysql_store.h b/projects/stargazer/plugins/store/mysql/mysql_store.h new file mode 100644 index 00000000..83a30e6a --- /dev/null +++ b/projects/stargazer/plugins/store/mysql/mysql_store.h @@ -0,0 +1,144 @@ + /* + $Revision: 1.4 $ + $Date: 2010/01/19 11:07:57 $ + */ + + +#ifndef FILE_STORE_H +#define FILE_STORE_H + +#include + +#include "base_settings.h" +#include "base_store.h" +#include "user_traff.h" +#include + +using namespace std; +//----------------------------------------------------------------------------- +extern "C" BASE_STORE * GetStore(); +//----------------------------------------------------------------------------- +class MYSQL_STORE_SETTINGS//: public BASE_SETTINGS +{ +public: + MYSQL_STORE_SETTINGS(); + virtual ~MYSQL_STORE_SETTINGS(); + virtual int ParseSettings(const MODULE_SETTINGS & s); + virtual const string & GetStrError() const; + + string GetDBUser() const; + string GetDBPassword() const; + string GetDBHost() const; + string GetDBName() const; + +private: + const MODULE_SETTINGS * settings; + + int ParseParam(const vector & moduleParams, + const string & name, string & result); + + string errorStr; + + string dbUser; + string dbPass; + string dbName; + string dbHost; +}; +//----------------------------------------------------------------------------- +class MYSQL_STORE: public BASE_STORE +{ +public: + MYSQL_STORE(); + virtual ~MYSQL_STORE(); + virtual const string & GetStrError() const; + + //User + virtual int GetUsersList(vector * usersList) const; + virtual int AddUser(const string & login) const; + virtual int DelUser(const string & login) const; + virtual int SaveUserStat(const USER_STAT & stat, const string & login) const; + virtual int SaveUserConf(const USER_CONF & conf, const string & login) const; + virtual int RestoreUserStat(USER_STAT * stat, const string & login) const; + virtual int RestoreUserConf(USER_CONF * conf, const string & login) const; + virtual int WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message = "") const; + virtual int WriteUserConnect(const string & login, uint32_t ip) const; + virtual int WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const; + + virtual int WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const; + + virtual int AddMessage(STG_MSG * msg, const string & login) const; + virtual int EditMessage(const STG_MSG & msg, const string & login) const; + virtual int GetMessage(uint64_t id, STG_MSG * msg, const string & login) const; + virtual int DelMessage(uint64_t id, const string & login) const; + virtual int GetMessageHdrs(vector * hdrsList, const string & login) const; + + virtual int SaveMonthStat(const USER_STAT & stat, int month, int year, const string & login) const; + + //Admin + virtual int GetAdminsList(vector * adminsList) const; + virtual int AddAdmin(const string & login) const; + virtual int DelAdmin(const string & login) const; + virtual int RestoreAdmin(ADMIN_CONF * ac, const string & login) const; + virtual int SaveAdmin(const ADMIN_CONF & ac) const; + + //Tariff + virtual int GetTariffsList(vector * tariffsList) const; + virtual int AddTariff(const string & name) const; + virtual int DelTariff(const string & name) const; + virtual int SaveTariff(const TARIFF_DATA & td, const string & tariffName) const; + virtual int RestoreTariff(TARIFF_DATA * td, const string & tariffName) const; + + //Corparation + virtual int GetCorpsList(vector *) const {return 0;}; + virtual int SaveCorp(const CORP_CONF &) const {return 0;}; + virtual int RestoreCorp(CORP_CONF *, const string &) const {return 0;}; + virtual int AddCorp(const string &) const {return 0;}; + virtual int DelCorp(const string &) const {return 0;}; + + // Services + virtual int GetServicesList(vector *) const {return 0;}; + virtual int SaveService(const SERVICE_CONF &) const {return 0;}; + virtual int RestoreService(SERVICE_CONF *, const string &) const {return 0;}; + virtual int AddService(const string &) const {return 0;}; + virtual int DelService(const string &) const {return 0;}; + + //virtual BASE_SETTINGS * GetStoreSettings(); + virtual void SetSettings(const MODULE_SETTINGS & s); + virtual int ParseSettings(); + virtual const string & GetVersion() const; + +private: + virtual int WriteLogString(const string & str, const string & login) const; + int GetAllParams(vector * ParamList, const string & table, const string & name) const; + int CheckAllTables(MYSQL * sock); + bool IsTablePresent(const string & str,MYSQL * sock); + mutable string errorStr; +// int Reconnect(); + int MysqlQuery(const char* sQuery,MYSQL * sock) const; + int MysqlGetQuery(const char * Query,MYSQL * & sock) const; + int MysqlSetQuery(const char * Query) const; + MYSQL * MysqlConnect() const ; + string version; + MYSQL_STORE_SETTINGS storeSettings; + MODULE_SETTINGS settings; + //mutable MYSQL mysql; + //mutable MYSQL* sock; +}; +//----------------------------------------------------------------------------- + +#endif //FILE_STORE_H diff --git a/projects/stargazer/plugins/store/postgresql/Makefile b/projects/stargazer/plugins/store/postgresql/Makefile new file mode 100644 index 00000000..522058dd --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/Makefile @@ -0,0 +1,29 @@ +############################################################################### +# $Id: Makefile,v 1.4 2010/04/26 12:44:42 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_store_postgresql.so + +SRCS = ./postgresql_store.cpp \ + ./postgresql_store_admins.cpp \ + ./postgresql_store_corporations.cpp \ + ./postgresql_store_messages.cpp \ + ./postgresql_store_services.cpp \ + ./postgresql_store_tariffs.cpp \ + ./postgresql_store_users.cpp \ + ./postgresql_store_utils.cpp + +STGLIBS = -lstg_common -lstg_crypto + +PG_CFLAGS = $(shell pg_config --includedir) +PG_LDFLAGS = $(shell pg_config --libdir) + +CXXFLAGS += -I $(PG_CFLAGS) +LDFLAGS += -L $(PG_LDFLAGS) + +LIBS += -lpq + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store.cpp new file mode 100644 index 00000000..e316beaf --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store.cpp @@ -0,0 +1,228 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * This file contains a realization of a base postgresql-storage plugin class + * + * v. 1.3 + * FreeMb logging on dosconnects added + * + * v. 1.2 + * Reconnection on faults added + * + * v. 1.1 + * tb_stats removed + * + * v. 1.0 + * Initial implementation + * + * $Revision: 1.5 $ + * $Date: 2010/01/06 10:43:48 $ + * + */ + +#include +#include +#include + +#include + +#include "postgresql_store.h" +#include "postgresql_store_utils.h" +#include "base_settings.h" + +class POSTGRESQL_STORE_CREATOR +{ +public: + POSTGRESQL_STORE_CREATOR() + : pqStore(new POSTGRESQL_STORE()) + { + }; + ~POSTGRESQL_STORE_CREATOR() + { + delete pqStore; + }; + POSTGRESQL_STORE * GetStore() { return pqStore; }; +private: + POSTGRESQL_STORE * pqStore; +} pqStoreeCreator; + +//----------------------------------------------------------------------------- +BASE_STORE * GetStore() +{ +return pqStoreeCreator.GetStore(); +} + +//----------------------------------------------------------------------------- +POSTGRESQL_STORE::POSTGRESQL_STORE() + : connection(NULL) +{ +server = "localhost"; +database = "stargazer"; +user = "stg"; +password = "123456"; +versionString = "postgresql_store v.1.3"; +pthread_mutex_init(&mutex, NULL); +} +//----------------------------------------------------------------------------- +POSTGRESQL_STORE::~POSTGRESQL_STORE() +{ +if (connection) + { + PQfinish(connection); + } +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::ParseSettings() +{ +std::vector::iterator i; +string s; + +for(i = settings.moduleParams.begin(); i != settings.moduleParams.end(); ++i) + { + s = i->param; + std::transform(s.begin(), s.end(), s.begin(), ToLower()); + if (s == "server") + { + server = *(i->value.begin()); + } + if (s == "database") + { + database = *(i->value.begin()); + } + if (s == "user") + { + user = *(i->value.begin()); + } + if (s == "password") + { + password = *(i->value.begin()); + } + } + +clientEncoding = "KOI8"; + +return Connect(); +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::Connect() +{ +std::string params; +params = "host=" + server + " " + + "dbname=" + database + " " + + "user=" + user + " " + + "password=" + password; + +connection = PQconnectdb(params.c_str()); + +if (PQstatus(connection) != CONNECTION_OK) + { + strError = PQerrorMessage(connection); + printfd(__FILE__, "POSTGRESQL_STORE::Connect(): '%s'\n", strError.c_str()); + return 1; + } + +if (PQsetClientEncoding(connection, clientEncoding.c_str())) + { + strError = PQerrorMessage(connection); + printfd(__FILE__, "POSTGRESQL_STORE::Connect(): '%s'\n", strError.c_str()); + return 1; + } + +return CheckVersion(); +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::Reset() const +{ +PQreset(connection); + +if (PQstatus(connection) != CONNECTION_OK) + { + strError = PQerrorMessage(connection); + printfd(__FILE__, "POSTGRESQL_STORE::Reset(): '%s'\n", strError.c_str()); + return 1; + } + +if (PQsetClientEncoding(connection, clientEncoding.c_str())) + { + strError = PQerrorMessage(connection); + printfd(__FILE__, "POSTGRESQL_STORE::Reset(): '%s'\n", strError.c_str()); + return 1; + } + +return CheckVersion(); +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::CheckVersion() const +{ + +if (StartTransaction()) + { + strError = "Failed to start transaction"; + printfd(__FILE__, "POSTGRESQL_STORE::CheckVersion(): '%s'\n", strError.c_str()); + return -1; + } + +PGresult * result = PQexec(connection, "SELECT MAX(version) FROM tb_info"); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::CheckVersion(): '%s'\n"); + RollbackTransaction(); + return -1; + } + +if (str2x(PQgetvalue(result, 0, 0), version)) + { + strError = "Invalid DB version"; + PQclear(result); + RollbackTransaction(); + printfd(__FILE__, "POSTGRESQL_STORE::CheckVersion(): '%s'\n", strError.c_str()); + return -1; + } + +PQclear(result); + +if (version < DB_MIN_VERSION) + { + strError = "DB version too old"; + RollbackTransaction(); + printfd(__FILE__, "POSTGRESQL_STORE::CheckVersion(): '%s'\n", strError.c_str()); + return -1; + } + +if (version < 6) + { + printfd(__FILE__, "POSTGRESQL_STORE::CheckVersion(): I recommend you to upgrade your DB to higher version to support FreeMb logging on disconnect. Current version is %d\n", version); + } + +if (CommitTransaction()) + { + strError = "Failed to commit transaction"; + printfd(__FILE__, "POSTGRESQL_STORE::CheckVersion(): '%s'\n", strError.c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store.h b/projects/stargazer/plugins/store/postgresql/postgresql_store.h new file mode 100644 index 00000000..67119db2 --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store.h @@ -0,0 +1,162 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * PostgreSQL storage class definition + * + * $Revision: 1.8 $ + * $Date: 2010/01/19 11:06:53 $ + * + */ + +#ifndef POSTGRESQL_STORE_H +#define POSTGRESQL_STORE_H + +#include + +#include +#include +#include + +#include "base_store.h" + +// Minimal DB version is 5 +// Recommended DB version is 6 (support FreeMb logging on disconnects) +#define DB_MIN_VERSION 5 + +extern "C" BASE_STORE * GetStore(); + +class POSTGRESQL_STORE : public BASE_STORE { +public: + POSTGRESQL_STORE(); + virtual ~POSTGRESQL_STORE(); + + // Users + int GetUsersList(std::vector * usersList) const; + int AddUser(const std::string & login) const; + int DelUser(const std::string & login) const; + int SaveUserStat(const USER_STAT & stat, const std::string & login) const; + int SaveUserConf(const USER_CONF & conf, const std::string & login) const; + int RestoreUserStat(USER_STAT * stat, const std::string & login) const; + int RestoreUserConf(USER_CONF * conf, const std::string & login) const; + int WriteUserChgLog(const std::string & login, + const std::string & admLogin, + uint32_t admIP, + const std::string & paramName, + const std::string & oldValue, + const std::string & newValue, + const std::string & message) const; + int WriteUserConnect(const std::string & login, uint32_t ip) const; + int WriteUserDisconnect(const std::string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const; + int WriteDetailedStat(const std::map * statTree, + time_t lastStat, + const std::string & login) const; + + // Messages + int AddMessage(STG_MSG * msg, const std::string & login) const; + int EditMessage(const STG_MSG & msg, const std::string & login) const; + int GetMessage(uint64_t id, STG_MSG * msg, const std::string & login) const; + int DelMessage(uint64_t id, const std::string & login) const; + int GetMessageHdrs(std::vector * hdrsList, const std::string & login) const; + + // Stats + int SaveMonthStat(const USER_STAT & stat, int month, int year, const std::string & login) const; + + // Admins + int GetAdminsList(std::vector * adminsList) const; + int SaveAdmin(const ADMIN_CONF & ac) const; + int RestoreAdmin(ADMIN_CONF * ac, const std::string & login) const; + int AddAdmin(const std::string & login) const; + int DelAdmin(const std::string & login) const; + + // Tariffs + int GetTariffsList(std::vector * tariffsList) const; + int AddTariff(const std::string & name) const; + int DelTariff(const string & name) const; + int SaveTariff(const TARIFF_DATA & td, const std::string & tariffName) const; + int RestoreTariff(TARIFF_DATA * td, const std::string & tariffName) const; + + // Corporations + int GetCorpsList(std::vector * corpsList) const; + int SaveCorp(const CORP_CONF & cc) const; + int RestoreCorp(CORP_CONF * cc, const std::string & name) const; + int AddCorp(const std::string & name) const; + int DelCorp(const std::string & name) const; + + // Services + int GetServicesList(std::vector * servicesList) const; + int SaveService(const SERVICE_CONF & sc) const; + int RestoreService(SERVICE_CONF * sc, const std::string & name) const; + int AddService(const std::string & name) const; + int DelService(const std::string & name) const; + + // Settings + inline void SetSettings(const MODULE_SETTINGS & s) { settings = s; }; + int ParseSettings(); + + inline const string & GetStrError() const { return strError; }; + inline const string & GetVersion() const { return versionString; }; +private: + std::string versionString; + mutable std::string strError; + std::string server; + std::string database; + std::string user; + std::string password; + std::string clientEncoding; + MODULE_SETTINGS settings; + mutable pthread_mutex_t mutex; + mutable int version; + + PGconn * connection; + + int StartTransaction() const; + int CommitTransaction() const; + int RollbackTransaction() const; + + int EscapeString(std::string & value) const; + + std::string Int2TS(uint32_t value) const; + uint32_t TS2Int(const std::string & value) const; + + int SaveStat(const USER_STAT & stat, const std::string & login, int year = 0, int month = 0) const; + + int SaveUserServices(uint32_t uid, const std::vector & services) const; + int SaveUserData(uint32_t uid, const std::vector & data) const; + int SaveUserIPs(uint32_t uid, const USER_IPS & ips) const; + + void MakeDate(std::string & date, int year = 0, int month = 0) const; + + int Connect(); + int Reset() const; + int CheckVersion() const; +}; + +extern const volatile time_t stgTime; + +#endif //POSTGRESQL_STORE_H + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_admins.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store_admins.cpp new file mode 100644 index 00000000..28bfbc0c --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_admins.cpp @@ -0,0 +1,452 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Administrators manipulation methods + * + * $Revision: 1.2 $ + * $Date: 2009/06/09 12:32:39 $ + * + */ + +#include +#include +#include + +#include + +#include "postgresql_store.h" +#include "stg_locker.h" +#include "admin_conf.h" +#include "blowfish.h" + +#define adm_enc_passwd "cjeifY8m3" + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::GetAdminsList(std::vector * adminsList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetAdminsList(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::GetAdminsList(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetAdminsList(): 'Failed to start transaction'\n"); + return -1; + } + +result = PQexec(connection, "SELECT login FROM tb_admins"); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::GetAdminsList(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetAdminsList(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + adminsList->push_back(PQgetvalue(result, i, 0)); + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetAdminsList(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveAdmin(const ADMIN_CONF & ac) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Failed to start transaction'\n"); + return -1; + } + +char encodedPass[2 * ADM_PASSWD_LEN + 2]; +char cryptedPass[ADM_PASSWD_LEN + 1]; +char adminPass[ADM_PASSWD_LEN + 1]; +BLOWFISH_CTX ctx; + +memset(cryptedPass, 0, ADM_PASSWD_LEN + 1); +strncpy(adminPass, ac.password.c_str(), ADM_PASSWD_LEN); +EnDecodeInit(adm_enc_passwd, sizeof(adm_enc_passwd), &ctx); + +for (int i = 0; i < ADM_PASSWD_LEN / 8; i++) + EncodeString(cryptedPass + 8 * i, adminPass + 8 * i, &ctx); + +cryptedPass[ADM_PASSWD_LEN] = 0; +Encode12(encodedPass, cryptedPass, ADM_PASSWD_LEN); + +std::string password = encodedPass; +std::string login = ac.login; + +if (EscapeString(password)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Failed to escape password'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(login)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "UPDATE tb_admins SET " + << "passwd = '" << password << "', " + << "chg_conf = " << ac.priv.userConf << ", " + << "chg_password = " << ac.priv.userPasswd << ", " + << "chg_stat = " << ac.priv.userStat << ", " + << "chg_cash = " << ac.priv.userCash << ", " + << "usr_add_del = " << ac.priv.userAddDel << ", " + << "chg_tariff = " << ac.priv.tariffChg << ", " + << "chg_admin = " << ac.priv.adminChg << " " + << "WHERE login = '" << login << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveAdmin(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::RestoreAdmin(ADMIN_CONF * ac, const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Failed to start transaction'\n"); + return -1; + } + +char cryptedPass[ADM_PASSWD_LEN + 1]; +char adminPass[ADM_PASSWD_LEN + 1]; +BLOWFISH_CTX ctx; + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT login, passwd, \ + chg_conf, chg_password, chg_stat, \ + chg_cash, usr_add_del, chg_tariff, \ + chg_admin, chg_service, chg_corporation \ + FROM tb_admins \ + WHERE login = '" << elogin << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch admin's data"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +ac->login = PQgetvalue(result, 0, 0); +ac->password = PQgetvalue(result, 0, 1); + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 2) << " " + << PQgetvalue(result, 0, 3) << " " + << PQgetvalue(result, 0, 4) << " " + << PQgetvalue(result, 0, 5) << " " + << PQgetvalue(result, 0, 6) << " " + << PQgetvalue(result, 0, 7) << " " + << PQgetvalue(result, 0, 8) << " " + << PQgetvalue(result, 0, 9) << " " + << PQgetvalue(result, 0, 10); + +PQclear(result); + +tuple >> ac->priv.userConf + >> ac->priv.userPasswd + >> ac->priv.userStat + >> ac->priv.userCash + >> ac->priv.userAddDel + >> ac->priv.tariffChg + >> ac->priv.adminChg; + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreAdmin(): 'Failed to commit transacion'\n"); + return -1; + } + +if (ac->password == "") + { + return 0; + } + +Decode21(cryptedPass, ac->password.c_str()); +EnDecodeInit(adm_enc_passwd, sizeof(adm_enc_passwd), &ctx); +for (int i = 0; i < ADM_PASSWD_LEN / 8; i++) + { + DecodeString(adminPass + 8 * i, cryptedPass + 8 * i, &ctx); + } +ac->password = adminPass; + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::AddAdmin(const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "INSERT INTO tb_admins \ + (login, passwd, \ + chg_conf, chg_password, chg_stat, \ + chg_cash, usr_add_del, chg_tariff, \ + chg_admin, chg_service, chg_corporation) \ + VALUES " + << "('" << elogin << "', \ + '', 0, 0, 0, 0, 0, 0, 0, 0, 0)"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddAdmin(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::DelAdmin(const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "DELETE FROM tb_admins WHERE login = '" << elogin << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelAdmin(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_corporations.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store_corporations.cpp new file mode 100644 index 00000000..6fa3fd50 --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_corporations.cpp @@ -0,0 +1,374 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Corporations manipulation methods + * + * $Revision: 1.2 $ + * $Date: 2009/06/09 12:32:39 $ + * + */ + +#include +#include +#include + +#include + +#include "postgresql_store.h" +#include "stg_locker.h" + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::GetCorpsList(vector * corpsList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetCorpsList(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::GetCorpsList(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetCorpsList(): 'Failed to start transaction'\n"); + return -1; + } + +result = PQexec(connection, "SELECT name FROM tb_corporations"); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::GetCorpsList(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetCorpsList(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + corpsList->push_back(PQgetvalue(result, i, 0)); + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetCorpsList(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveCorp(const CORP_CONF & cc) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = cc.name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "UPDATE tb_corporations SET " + << "cash = " << cc.cash + << "WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveCorp(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::RestoreCorp(CORP_CONF * cc, const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT cash FROM tb_corporations WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch corp's data"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 0); + +PQclear(result); + +tuple >> cc->cash; + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreCorp(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::AddCorp(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "INSERT INTO tb_corporations \ + (name, cash) \ + VALUES \ + ('" << ename << "', 0)"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddCorp(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::DelCorp(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "DELETE FROM tb_corporations WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelCorp(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_messages.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store_messages.cpp new file mode 100644 index 00000000..9eeea602 --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_messages.cpp @@ -0,0 +1,470 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + +/* + * Messages manipualtion methods + * + * $Revision: 1.6 $ + * $Date: 2009/07/15 11:19:42 $ + * + */ + +#include +#include +#include + +#include + +#include "postgresql_store.h" +#include "stg_locker.h" +#include "stg_message.h" + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::AddMessage(STG_MSG * msg, const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; +std::string etext = msg->text; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(etext)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to escape message text'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT sp_add_message(" + << "'" << elogin << "', " + << "CAST(1 AS SMALLINT), " // Here need to be a version, but, it's uninitiated actually + << "CAST(" << msg->header.type << " AS SMALLINT), " + << "CAST('" << Int2TS(msg->header.lastSendTime) << "' AS TIMESTAMP), " + << "CAST('" << Int2TS(msg->header.creationTime) << "' AS TIMESTAMP), " + << msg->header.showTime << ", " + << "CAST(" << msg->header.repeat << " AS SMALLINT), " + << msg->header.repeatPeriod << ", " + << "'" << etext << "')"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch newlly added message ID"; + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 0); + +PQclear(result); + +tuple >> msg->header.id; + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddMessage(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::EditMessage(const STG_MSG & msg, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; +std::string etext = msg.text; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(etext)) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Failed to escape message text'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "UPDATE tb_messages SET " + << "fk_user = (SELECT pk_user FROM tb_users WHERE name = '" << elogin << "'), " + << "ver = " << msg.header.ver << ", " + << "msg_type = " << msg.header.type << ", " + << "last_send_time = CAST('" << Int2TS(msg.header.lastSendTime) << "' AS TIMESTAMP), " + << "creation_time = CAST('" << Int2TS(msg.header.creationTime) << "' AS TIMESTAMP), " + << "show_time = " << msg.header.showTime << ", " + << "repeat = " << msg.header.repeat << ", " + << "repeat_period = " << msg.header.repeatPeriod << ", " + << "msg_text = '" << etext << "' " + << "WHERE pk_message = " << msg.header.id; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::EditMessage(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::GetMessage(uint64_t id, + STG_MSG * msg, + const string &) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): '%s'\n", strError.c_str()); + return -1; + } + } + +string login; +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): 'Failed to start transaction'\n"); + return -1; + } + +std::stringstream query; +query << "SELECT ver, msg_type, last_send_time, \ + creation_time, show_time, repeat, \ + repeat_period, msg_text \ + FROM tb_messages \ + WHERE pk_message = " << id; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch message data"; + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +/*std::stringstream tuple; + +for (int i = 0; i < 8; ++i) + { + tuple << PQgetvalue(result, 0, i) << " "; + }*/ + +str2x(PQgetvalue(result, 0, 0), msg->header.ver); +str2x(PQgetvalue(result, 0, 1), msg->header.type); +msg->header.lastSendTime = TS2Int(PQgetvalue(result, 0, 2)); +msg->header.creationTime = TS2Int(PQgetvalue(result, 0, 3)); +str2x(PQgetvalue(result, 0, 4), msg->header.showTime); +str2x(PQgetvalue(result, 0, 5), msg->header.repeat); +str2x(PQgetvalue(result, 0, 6), msg->header.repeatPeriod); +msg->text = PQgetvalue(result, 0, 7); + +PQclear(result); + +/*tuple >> msg->header.ver; +tuple >> msg->header.type; +tuple >> msg->header.lastSendTime; +tuple >> msg->header.creationTime; +tuple >> msg->header.showTime; +tuple >> msg->header.repeat; +tuple >> msg->header.repeatPeriod; +tuple >> msg->text;*/ + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessage(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::DelMessage(uint64_t id, const string &) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelMessage(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::DelMessage(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelMessage(): 'Failed to start transaction'\n"); + return -1; + } + +std::stringstream query; +query << "DELETE FROM tb_messages WHERE pk_message = " << id; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::DelMessage(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelMessage(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelMessage(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::GetMessageHdrs(vector * hdrsList, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT pk_message, ver, msg_type, \ + last_send_time, creation_time, show_time, \ + repeat, repeat_period \ + FROM tb_messages \ + WHERE fk_user IN \ + (SELECT pk_user FROM tb_users \ + WHERE name = '" << elogin << "')"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + std::stringstream tuple; + STG_MSG_HDR header; + tuple << PQgetvalue(result, i, 0) << " "; + tuple << PQgetvalue(result, i, 1) << " "; + tuple << PQgetvalue(result, i, 2) << " "; + header.lastSendTime = TS2Int(PQgetvalue(result, i, 3)); + header.creationTime = TS2Int(PQgetvalue(result, i, 4)); + tuple << PQgetvalue(result, i, 5) << " "; + tuple << PQgetvalue(result, i, 6) << " "; + tuple << PQgetvalue(result, i, 7) << " "; + + tuple >> header.id; + tuple >> header.ver; + tuple >> header.type; + tuple >> header.showTime; + tuple >> header.repeat; + tuple >> header.repeatPeriod; + hdrsList->push_back(header); + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetMessageHdrs(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_services.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store_services.cpp new file mode 100644 index 00000000..88b8049c --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_services.cpp @@ -0,0 +1,393 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + +/* + * Services manipulation methods + * + * $Revision: 1.2 $ + * $Date: 2009/06/09 12:32:40 $ + * + */ + +#include +#include +#include + +#include + +#include "postgresql_store.h" +#include "stg_locker.h" + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::GetServicesList(vector * servicesList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetServicesList(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::GetServicesList(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetServicesList(): 'Failed to start transaction'\n"); + return -1; + } + +result = PQexec(connection, "SELECT name FROM tb_services"); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::GetServicesList(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetServicesList(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + servicesList->push_back(PQgetvalue(result, i, 0)); + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetServicesList(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveService(const SERVICE_CONF & sc) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = sc.name; +std::string ecomment = sc.comment; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(ecomment)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Failed to escape comment'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "UPDATE tb_services SET " + << "comment = '" << ecomment << "', " + << "cost = " << sc.cost << ", " + << "pay_day = " << sc.payDay << " " + << "WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveService(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::RestoreService(SERVICE_CONF * sc, + const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT comment, cost, pay_day FROM tb_services WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch service's data"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 0) << " " + << PQgetvalue(result, 0, 1) << " " + << PQgetvalue(result, 0, 2); + +PQclear(result); + +tuple >> sc->comment + >> sc->cost + >> sc->payDay; + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreService(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::AddService(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "INSERT INTO tb_services \ + (name, comment, cost, pay_day) \ + VALUES \ + ('" << ename << "', '', 0, 0)"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddService(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::DelService(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "DELETE FROM tb_services WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelService(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_tariffs.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store_tariffs.cpp new file mode 100644 index 00000000..352dab00 --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_tariffs.cpp @@ -0,0 +1,578 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Tariffs manipulation methods + * + * $Revision: 1.2 $ + * $Date: 2009/06/09 12:32:40 $ + * + */ + +#include +#include +#include + +#include + +#include "postgresql_store.h" +#include "stg_locker.h" + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::GetTariffsList(vector * tariffsList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetTariffsList(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::GetTariffsList(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetTariffsList(): 'Failed to start transaction'\n"); + return -1; + } + +result = PQexec(connection, "SELECT name FROM tb_tariffs"); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::GetTariffsList(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetTariffsList(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + tariffsList->push_back(PQgetvalue(result, i, 0)); + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetTariffsList(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::AddTariff(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT sp_add_tariff('" << ename << "', " << DIR_NUM << ")"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::DelTariff(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelTariff(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::DelTariff(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelTariff(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = name; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "DELETE FROM tb_tariffs WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::DelTariff(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelTariff(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveTariff(const TARIFF_DATA & td, + const string & tariffName) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = tariffName; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int32_t id, i; +double pda, pdb, pna, pnb; +int threshold; + +std::stringstream query; +query << "SELECT pk_tariff FROM tb_tariffs WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch tariff ID"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 0); + +PQclear(result); + +tuple >> id; + +query.str(""); +query << "UPDATE tb_tariffs SET \ + fee = " << td.tariffConf.fee << ", \ + free = " << td.tariffConf.free << ", \ + passive_cost = " << td.tariffConf.passiveCost << ", \ + traff_type = " << td.tariffConf.traffType << " \ + WHERE pk_tariff = " << id; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +for(i = 0; i < DIR_NUM; i++) + { + + pda = td.dirPrice[i].priceDayA * 1024 * 1024; + pdb = td.dirPrice[i].priceDayB * 1024 * 1024; + + if (td.dirPrice[i].singlePrice) + { + pna = pda; + pnb = pdb; + } + else + { + pna = td.dirPrice[i].priceNightA * 1024 * 1024; + pnb = td.dirPrice[i].priceNightB * 1024 * 1024; + } + + if (td.dirPrice[i].noDiscount) + { + threshold = 0xffFFffFF; + } + else + { + threshold = td.dirPrice[i].threshold; + } + + std::stringstream query; + query << "UPDATE tb_tariffs_params SET \ + price_day_a = " << pda << ", \ + price_day_b = " << pdb << ", \ + price_night_a = " << pna << ", \ + price_night_b = " << pnb << ", \ + threshold = " << threshold << ", \ + time_day_begins = CAST('" << td.dirPrice[i].hDay + << ":" + << td.dirPrice[i].mDay << "' AS TIME), \ + time_day_ends = CAST('" << td.dirPrice[i].hNight + << ":" + << td.dirPrice[i].mNight << "' AS TIME) \ + WHERE fk_tariff = " << id << " AND dir_num = " << i; + + result = PQexec(connection, query.str().c_str()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + + PQclear(result); + } + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveTariff(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::RestoreTariff(TARIFF_DATA * td, + const string & tariffName) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Failed to start transaction'\n"); + return -1; + } + +std::string ename = tariffName; + +if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Failed to escape name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +td->tariffConf.name = tariffName; + +std::stringstream query; +query << "SELECT pk_tariff, \ + fee, \ + free, \ + passive_cost, \ + traff_type \ + FROM tb_tariffs WHERE name = '" << ename << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch tariff data"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 0) << " "; +tuple << PQgetvalue(result, 0, 1) << " "; +tuple << PQgetvalue(result, 0, 2) << " "; +tuple << PQgetvalue(result, 0, 3) << " "; +tuple << PQgetvalue(result, 0, 4) << " "; + +int id; +tuple >> id; +tuple >> td->tariffConf.fee; +tuple >> td->tariffConf.free; +tuple >> td->tariffConf.passiveCost; +tuple >> td->tariffConf.traffType; + +PQclear(result); + +query.str(""); +query << "SELECT dir_num, \ + price_day_a, \ + price_day_b, \ + price_night_a, \ + price_night_b, \ + threshold, \ + EXTRACT(hour FROM time_day_begins), \ + EXTRACT(minute FROM time_day_begins), \ + EXTRACT(hour FROM time_day_ends), \ + EXTRACT(minute FROM time_day_ends) \ + FROM tb_tariffs_params \ + WHERE fk_tariff = " << id; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +tuples = PQntuples(result); + +if (tuples != DIR_NUM) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Tariff params count and DIR_NUM does not feet (wanted %d, actually %d)'\n", DIR_NUM, tuples); + } + +for (int i = 0; i < std::min(tuples, DIR_NUM); ++i) + { + std::stringstream tuple; + tuple << PQgetvalue(result, i, 0) << " "; + tuple << PQgetvalue(result, i, 1) << " "; + tuple << PQgetvalue(result, i, 2) << " "; + tuple << PQgetvalue(result, i, 3) << " "; + tuple << PQgetvalue(result, i, 4) << " "; + tuple << PQgetvalue(result, i, 5) << " "; + tuple << PQgetvalue(result, i, 6) << " "; + tuple << PQgetvalue(result, i, 7) << " "; + tuple << PQgetvalue(result, i, 8) << " "; + tuple << PQgetvalue(result, i, 9) << " "; + + int dir; + + tuple >> dir; + tuple >> td->dirPrice[dir].priceDayA; + td->dirPrice[dir].priceDayA /= 1024 * 1024; + tuple >> td->dirPrice[dir].priceDayB; + td->dirPrice[dir].priceDayB /= 1024 * 1024; + tuple >> td->dirPrice[dir].priceNightA; + td->dirPrice[dir].priceNightA /= 1024 * 1024; + tuple >> td->dirPrice[dir].priceNightB; + td->dirPrice[dir].priceNightB /= 1024 * 1024; + tuple >> td->dirPrice[dir].threshold; + tuple >> td->dirPrice[dir].hDay; + tuple >> td->dirPrice[dir].mDay; + tuple >> td->dirPrice[dir].hNight; + tuple >> td->dirPrice[dir].mNight; + + if (td->dirPrice[dir].priceDayA == td->dirPrice[dir].priceNightA && + td->dirPrice[dir].priceDayB == td->dirPrice[dir].priceNightB) + { + td->dirPrice[dir].singlePrice = true; + } + else + { + td->dirPrice[dir].singlePrice = false; + } + if (td->dirPrice[dir].threshold == (int)0xffFFffFF) + { + td->dirPrice[dir].noDiscount = true; + } + else + { + + td->dirPrice[dir].noDiscount = false; + } + + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreTariff(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_users.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store_users.cpp new file mode 100644 index 00000000..6a40979f --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_users.cpp @@ -0,0 +1,1565 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * User manipulation methods + * + * $Revision: 1.14 $ + * $Date: 2010/05/07 07:26:36 $ + * + */ + +#include +#include +#include +#include + +#include + +#include "stg_const.h" +#include "postgresql_store.h" +#include "stg_locker.h" +#include "../../../stg_timer.h" + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::GetUsersList(vector * usersList) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetUsersList(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::GetUsersList(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetUsersList(): 'Failed to start transaction'\n"); + return -1; + } + +result = PQexec(connection, "SELECT name FROM tb_users"); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::GetUsersList(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetUsersList(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + usersList->push_back(PQgetvalue(result, i, 0)); + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::GetUsersList(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::AddUser(const string & name) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = name; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT sp_add_user('" << elogin << "')"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::AddUser(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::DelUser(const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "DELETE FROM tb_users WHERE name = '" << elogin << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::DelUser(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveUserStat(const USER_STAT & stat, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +return SaveStat(stat, login); +} +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveStat(const USER_STAT & stat, + const string & login, + int year, + int month) const +{ +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "UPDATE tb_users SET " + "cash = " << stat.cash << ", " + "free_mb = " << stat.freeMb << ", " + "last_activity_time = CAST('" << Int2TS(stat.lastActivityTime) << "' AS TIMESTAMP), " + "last_cash_add = " << stat.lastCashAdd << ", " + "last_cash_add_time = CAST('" << Int2TS(stat.lastCashAddTime) << "' AS TIMESTAMP), " + "passive_time = " << stat.passiveTime << " " + "WHERE name = '" << elogin << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +std::string date; + +MakeDate(date, year, month); + +for (int dir = 0; dir < DIR_NUM; ++dir) + { + query.str(""); + query << "SELECT sp_add_stats_traffic (" + "'" << elogin << "', " + "CAST('" << date << "' AS DATE), " + "CAST(" << dir << " AS SMALLINT), " + "CAST(" << stat.up[dir] << " AS BIGINT), " + "CAST(" << stat.down[dir] << " AS BIGINT))"; + + result = PQexec(connection, query.str().c_str()); + + if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + + PQclear(result); + } + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveStat(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveUserConf(const USER_CONF & conf, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT pk_user FROM tb_users WHERE name = '" << elogin << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch user's ID"; + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 0); + +PQclear(result); + +uint32_t uid; + +tuple >> uid; + +std::string eaddress = conf.address; +std::string eemail = conf.email; +std::string egroup = conf.group; +std::string enote = conf.note; +std::string epassword = conf.password; +std::string ephone = conf.phone; +std::string erealname = conf.realName; +std::string etariffname = conf.tariffName; +std::string enexttariff = conf.nextTariff; +std::string ecorporation = conf.corp; + +if (EscapeString(eaddress)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape address'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(eemail)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape email'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(egroup)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape group'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(enote)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape note'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(epassword)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape password'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(ephone)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape phone'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(erealname)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape real name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(etariffname)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape tariff name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(enexttariff)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape next tariff name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(ecorporation)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to escape corporation name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +query.str(""); +query << "UPDATE tb_users SET " + "address = '" << eaddress << "', " + "always_online = " << (conf.alwaysOnline ? "'t'" : "'f'") << ", " + "credit = " << conf.credit << ", " + "credit_expire = CAST('" << Int2TS(conf.creditExpire) << "' AS TIMESTAMP), " + "disabled = " << (conf.disabled ? "'t'" : "'f'") << ", " + "disabled_detail_stat = " << (conf.disabledDetailStat ? "'t'" : "'f'") << ", " + "email = '" << eemail << "', " + "grp = '" << egroup << "', " + "note = '" << enote << "', " + "passive = " << (conf.passive ? "'t'" : "'f'") << ", " + "passwd = '" << epassword << "', " + "phone = '" << ephone << "', " + "real_name = '" << erealname << "', " + "fk_tariff = (SELECT pk_tariff " + "FROM tb_tariffs " + "WHERE name = '" << etariffname << "'), " + "fk_tariff_change = (SELECT pk_tariff " + "FROM tb_tariffs " + "WHERE name = '" << enexttariff << "'), " + "fk_corporation = (SELECT pk_corporation " + "FROM tb_corporations " + "WHERE name = '" << ecorporation << "') " + "WHERE pk_user = " << uid; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (SaveUserServices(uid, conf.service)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to save user's services'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (SaveUserData(uid, conf.userdata)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to save user's data'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (SaveUserIPs(uid, conf.ips)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to save user's IPs'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserConf(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::RestoreUserStat(USER_STAT * stat, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT cash, free_mb, " + "last_activity_time, last_cash_add, " + "last_cash_add_time, passive_time " + "FROM tb_users " + "WHERE name = '" << elogin << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch user's stat"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream tuple; +tuple << PQgetvalue(result, 0, 0) << " "; +tuple << PQgetvalue(result, 0, 1) << " "; +stat->lastActivityTime = TS2Int(PQgetvalue(result, 0, 2)); +tuple << PQgetvalue(result, 0, 3) << " "; +stat->lastCashAddTime = TS2Int(PQgetvalue(result, 0, 4)); +tuple << PQgetvalue(result, 0, 5) << " "; + +PQclear(result); + +tuple >> stat->cash + >> stat->freeMb + >> stat->lastCashAdd + >> stat->passiveTime; + +query.str(""); + +query << "SELECT dir_num, upload, download " + "FROM tb_stats_traffic " + "WHERE fk_user IN (SELECT pk_user FROM tb_users WHERE name = '" << elogin << "') AND " + "DATE_TRUNC('month', stats_date) = DATE_TRUNC('month', CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP))"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + std::stringstream tuple; + tuple << PQgetvalue(result, i, 0) << " "; + tuple << PQgetvalue(result, i, 1) << " "; + tuple << PQgetvalue(result, i, 2) << " "; + + int dir; + + tuple >> dir; + tuple >> stat->up[dir]; + tuple >> stat->down[dir]; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::RestoreUserConf(USER_CONF * conf, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin = login; + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT tb_users.pk_user, tb_users.address, tb_users.always_online, " + "tb_users.credit, tb_users.credit_expire, tb_users.disabled, " + "tb_users.disabled_detail_stat, tb_users.email, tb_users.grp, " + "tb_users.note, tb_users.passive, tb_users.passwd, tb_users.phone, " + "tb_users.real_name, tf1.name, tf2.name, tb_corporations.name " + "FROM tb_users LEFT JOIN tb_tariffs AS tf1 " + "ON tf1.pk_tariff = tb_users.fk_tariff " + "LEFT JOIN tb_tariffs AS tf2 " + "ON tf2.pk_tariff = tb_users.fk_tariff_change " + "LEFT JOIN tb_corporations " + "ON tb_corporations.pk_corporation = tb_users.fk_corporation " + "WHERE tb_users.name = '" << elogin << "'"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch user's stat"; + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +uint32_t uid; + +std::stringstream tuple; + +tuple << PQgetvalue(result, 0, 0) << " "; // uid +conf->address = PQgetvalue(result, 0, 1); // address +conf->alwaysOnline = !strncmp(PQgetvalue(result, 0, 2), "t", 1); +tuple << PQgetvalue(result, 0, 3) << " "; // credit +conf->creditExpire = TS2Int(PQgetvalue(result, 0, 4)); // creditExpire +conf->disabled = !strncmp(PQgetvalue(result, 0, 5), "t", 1); +conf->disabledDetailStat = !strncmp(PQgetvalue(result, 0, 6), "t", 1); +conf->email = PQgetvalue(result, 0, 7); // email +conf->group = PQgetvalue(result, 0, 8); // group +conf->note = PQgetvalue(result, 0, 9); // note +conf->passive = !strncmp(PQgetvalue(result, 0, 10), "t", 1); +conf->password = PQgetvalue(result, 0, 11); // password +conf->phone = PQgetvalue(result, 0, 12); // phone +conf->realName = PQgetvalue(result, 0, 13); // realName +conf->tariffName = PQgetvalue(result, 0, 14); // tariffName +conf->nextTariff = PQgetvalue(result, 0, 15); // nextTariff +conf->corp = PQgetvalue(result, 0, 16); // corp + +PQclear(result); + +if (conf->tariffName == "") + conf->tariffName = NO_TARIFF_NAME; +if (conf->corp == "") + conf->corp = NO_CORP_NAME; + +tuple >> uid + >> conf->credit; + +query.str(""); +query << "SELECT name FROM tb_services " + "WHERE pk_service IN (SELECT fk_service " + "FROM tb_users_services " + "WHERE fk_user = " << uid << ")"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + conf->service.push_back(PQgetvalue(result, i, 0)); + } + +PQclear(result); + +query.str(""); +query << "SELECT num, data " + "FROM tb_users_data " + "WHERE fk_user = " << uid; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +tuples = PQntuples(result); + +for (int i = 0; i < tuples; ++i) + { + int num; + if (str2x(PQgetvalue(result, i, 0), num)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to convert string to int'\n"); + } + else + { + conf->userdata[i] = PQgetvalue(result, i, 1); + } + } + +PQclear(result); + +query.str(""); +query << "SELECT host(ip), masklen(ip) " + "FROM tb_allowed_ip " + "WHERE fk_user = " << uid; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +tuples = PQntuples(result); + +conf->ips.Erase(); +for (int i = 0; i < tuples; ++i) + { + IP_MASK ipm; + + int ip, mask; + + ip = inet_strington(PQgetvalue(result, i, 0)); + + if (str2x(PQgetvalue(result, i, 1), mask)) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to fetch mask'\n"); + continue; + } + + ipm.ip = ip; + ipm.mask = mask; + + conf->ips.Add(ipm); + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::RestoreUserConf(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message = "") const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin(login); +std::string eadminLogin(admLogin); +std::string eparam(paramName); +std::string eold(oldValue); +std::string enew(newValue); +std::string emessage(message); + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(eadminLogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to escape admin's login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(eparam)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to escape param's name'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(eold)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to escape old value'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +if (EscapeString(enew)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to escape new value'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +query << "SELECT sp_add_param_log_entry(" + "'" << elogin << "', " + "'" << eadminLogin << "', CAST('" + << inet_ntostring(admIP) << "/24' AS INET), " + "'" << eparam << "', " + "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), " + "'" << eold << "', " + "'" << enew << "', " + "'" << emessage << "')"; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserChgLog(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::WriteUserConnect(const string & login, uint32_t ip) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin(login); + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +if (version < 6) + { + query << "SELECT sp_add_session_log_entry(" + "'" << elogin << "', " + "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), " + "'c', CAST('" + << inet_ntostring(ip) << "/32' AS INET), 0)"; + } +else + { + query << "SELECT sp_add_session_log_entry(" + "'" << elogin << "', " + "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), " + "'c', CAST('" + << inet_ntostring(ip) << "/32' AS INET), 0, 0, '')"; + } + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserConnect(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb, + const std::string & reason) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin(login); + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::string ereason(reason); + +if (EscapeString(ereason)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to escape reason'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +std::stringstream query; +if (version < 6) + { + // Old database version - no freeMb logging support + query << "SELECT sp_add_session_log_entry(" + "'" << elogin << "', " + "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), " + "'d', CAST('0.0.0.0/0' AS INET), " + << cash << ")"; + } +else + { + query << "SELECT sp_add_session_log_entry(" + "'" << elogin << "', " + "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), " + "'d', CAST('0.0.0.0/0' AS INET), " + << cash << ", " << freeMb << ", '" << ereason << "')"; + } + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +int tuples = PQntuples(result); + +if (tuples != 1) + { + strError = "Failed to fetch session's log ID"; + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Invalid number of tuples. Wanted 1, actulally %d'\n", tuples); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +uint32_t lid; + +if (str2x(PQgetvalue(result, 0, 0), lid)) + { + strError = "Failed to convert string to int"; + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): '%s'\n", strError.c_str()); + PQclear(result); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +PQclear(result); + +for (int i = 0; i < DIR_NUM; ++i) + { + std::stringstream query; + query << "INSERT INTO tb_sessions_data " + "(fk_session_log, " + "dir_num, " + "session_upload, " + "session_download, " + "month_upload, " + "month_download)" + "VALUES (" + << lid << ", " + << i << ", " + << sessionUp[i] << ", " + << sessionDown[i] << ", " + << up[i] << ", " + << down[i] << ")"; + + result = PQexec(connection, query.str().c_str()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to rollback transaction'\n"); + } + return -1; + } + + PQclear(result); + } + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteUserDisconnect(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (PQstatus(connection) != CONNECTION_OK) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): 'Connection lost. Trying to reconnect...'\n", strError.c_str()); + if (Reset()) + { + strError = "Connection lost"; + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): '%s'\n", strError.c_str()); + return -1; + } + } + +PGresult * result; + +if (StartTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): 'Failed to start transaction'\n"); + return -1; + } + +std::string elogin(login); + +if (EscapeString(elogin)) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): 'Failed to escape login'\n"); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + +map::const_iterator it; +time_t currTime = time(NULL); + +for (it = statTree->begin(); it != statTree->end(); ++it) + { + std::stringstream query; + query << "INSERT INTO tb_detail_stats " + "(till_time, from_time, fk_user, " + "dir_num, ip, download, upload, cost) " + "VALUES (" + "CAST('" << Int2TS(currTime) << "' AS TIMESTAMP), " + "CAST('" << Int2TS(lastStat) << "' AS TIMESTAMP), " + "(SELECT pk_user FROM tb_users WHERE name = '" << elogin << "'), " + << it->first.dir << ", " + << "CAST('" << inet_ntostring(it->first.ip) << "' AS INET), " + << it->second.down << ", " + << it->second.up << ", " + << it->second.cash << ")"; + + result = PQexec(connection, query.str().c_str()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): '%s'\n", strError.c_str()); + if (RollbackTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): 'Failed to rollback transaction'\n"); + } + return -1; + } + + PQclear(result); + } + +if (CommitTransaction()) + { + printfd(__FILE__, "POSTGRESQL_STORE::WriteDetailedStat(): 'Failed to commit transaction'\n"); + return -1; + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveMonthStat(const USER_STAT & stat, int month, int year, const string & login) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +return SaveStat(stat, login, year, month); +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveUserServices(uint32_t uid, + const std::vector & services) const +{ +PGresult * result; + +std::stringstream query; +query << "DELETE FROM tb_users_services WHERE fk_user = " << uid; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserServices(): '%s'\n", strError.c_str()); + return -1; + } + +PQclear(result); + +std::vector::const_iterator it; + +for (it = services.begin(); it != services.end(); ++it) + { + std::string ename = *it; + + if (EscapeString(ename)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserServices(): 'Failed to escape service name'\n"); + return -1; + } + + std::stringstream query; + query << "INSERT INTO tb_users_services " + "(fk_user, fk_service) " + "VALUES " + "(" << uid << ", " + "(SELECT pk_service " + "FROM tb_services " + "WHERE name = '" << ename << "'))"; + + result = PQexec(connection, query.str().c_str()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserServices(): '%s'\n", strError.c_str()); + return -1; + } + + PQclear(result); + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveUserIPs(uint32_t uid, + const USER_IPS & ips) const +{ +PGresult * result; + +std::stringstream query; +query << "DELETE FROM tb_allowed_ip WHERE fk_user = " << uid; + +result = PQexec(connection, query.str().c_str()); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserIPs(): '%s'\n", strError.c_str()); + return -1; + } + +PQclear(result); + +for (int i = 0; i < ips.Count(); ++i) + { + std::stringstream query; + query << "INSERT INTO tb_allowed_ip " + "(fk_user, ip) " + "VALUES " + "(" << uid << ", CAST('" + << inet_ntostring(ips[i].ip) << "/" + << static_cast(ips[i].mask) << "' AS INET))"; + + result = PQexec(connection, query.str().c_str()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserIPs(): '%s'\n", strError.c_str()); + return -1; + } + + PQclear(result); + } + +return 0; +} + +//----------------------------------------------------------------------------- +int POSTGRESQL_STORE::SaveUserData(uint32_t uid, + const std::vector & data) const +{ +for (unsigned i = 0; i < data.size(); ++i) + { + std::string edata = data[i]; + + if (EscapeString(edata)) + { + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserData(): 'Failed to escape userdata field'\n"); + return -1; + } + + PGresult * result; + + std::stringstream query; + query << "SELECT sp_set_user_data(" + << uid << ", " + << "CAST(" << i << " AS SMALLINT), " + << "'" << edata << "')"; + + result = PQexec(connection, query.str().c_str()); + + if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::SaveUserData(): '%s'\n", strError.c_str()); + return -1; + } + + PQclear(result); + } + +return 0; +} + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_utils.cpp b/projects/stargazer/plugins/store/postgresql/postgresql_store_utils.cpp new file mode 100644 index 00000000..28a17895 --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_utils.cpp @@ -0,0 +1,176 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + * Vairous utility methods + * + * $Revision: 1.3 $ + * $Date: 2009/10/22 10:01:08 $ + * + */ + +#include +#include + +#include + +#include "common.h" + +#include "postgresql_store.h" + +int POSTGRESQL_STORE::StartTransaction() const +{ +PGresult * result = PQexec(connection, "BEGIN"); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::StartTransaction(): '%s'\n", strError.c_str()); + return -1; + } + +PQclear(result); + +return 0; +} + +int POSTGRESQL_STORE::CommitTransaction() const +{ +PGresult * result = PQexec(connection, "COMMIT"); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::CommitTransaction(): '%s'\n", strError.c_str()); + return -1; + } + +PQclear(result); + +return 0; +} + +int POSTGRESQL_STORE::RollbackTransaction() const +{ +PGresult * result = PQexec(connection, "ROLLBACK"); + +if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + strError = PQresultErrorMessage(result); + PQclear(result); + printfd(__FILE__, "POSTGRESQL_STORE::RollbackTransaction(): '%s'\n", strError.c_str()); + return -1; + } + +PQclear(result); + +return 0; +} + +int POSTGRESQL_STORE::EscapeString(std::string & value) const +{ +int error = 0; +char * buf = new char[(value.length() << 1) + 1]; + +PQescapeStringConn(connection, + buf, + value.c_str(), + value.length(), + &error); + +if (error) + { + strError = PQerrorMessage(connection); + printfd(__FILE__, "POSTGRESQL_STORE::EscapeString(): '%s'\n", strError.c_str()); + delete buf; + return -1; + } + +value = buf; + +delete[] buf; +return 0; +} + +std::string POSTGRESQL_STORE::Int2TS(uint32_t ts) const +{ +char buf[32]; +struct tm brokenTime; +time_t tt = ts; + +brokenTime.tm_wday = 0; +brokenTime.tm_yday = 0; +brokenTime.tm_isdst = 0; + +gmtime_r(&tt, &brokenTime); + +strftime(buf, 32, "%Y-%m-%d %H:%M:%S", &brokenTime); + +return buf; +} + +uint32_t POSTGRESQL_STORE::TS2Int(const std::string & ts) const +{ +struct tm brokenTime; + +brokenTime.tm_wday = 0; +brokenTime.tm_yday = 0; +brokenTime.tm_isdst = 0; + +stg_strptime(ts.c_str(), "%Y-%m-%d %H:%M:%S", &brokenTime); + +return stg_timegm(&brokenTime); +} + +void POSTGRESQL_STORE::MakeDate(std::string & date, int year, int month) const +{ +struct tm brokenTime; + +brokenTime.tm_wday = 0; +brokenTime.tm_yday = 0; +brokenTime.tm_isdst = 0; + +if (year) + { + brokenTime.tm_hour = 0; + brokenTime.tm_min = 0; + brokenTime.tm_sec = 0; + brokenTime.tm_year = year; + brokenTime.tm_mon = month; + } +else + { + time_t curTime = stgTime; + /*time(&curTime);*/ + + localtime_r(&curTime, &brokenTime); + } + +brokenTime.tm_mday = DaysInMonth(brokenTime.tm_year + 1900, brokenTime.tm_mon); + +char buf[32]; + +strftime(buf, 32, "%Y-%m-%d", &brokenTime); + +date = buf; +} + diff --git a/projects/stargazer/plugins/store/postgresql/postgresql_store_utils.h b/projects/stargazer/plugins/store/postgresql/postgresql_store_utils.h new file mode 100644 index 00000000..e09cccd2 --- /dev/null +++ b/projects/stargazer/plugins/store/postgresql/postgresql_store_utils.h @@ -0,0 +1,11 @@ +#ifndef POSTGRESQL_UTILS_STORE_H +#define POSTGRESQL_UTILS_STORE_H + +#include + +struct ToLower : public std::unary_function +{ +char operator() (char c) const { return std::tolower(c); } +}; + +#endif diff --git a/projects/stargazer/sandboxstart b/projects/stargazer/sandboxstart new file mode 100755 index 00000000..348244d1 --- /dev/null +++ b/projects/stargazer/sandboxstart @@ -0,0 +1,17 @@ +#!/bin/bash + +sandbox_dir=$1 + +export LD_LIBRARY_PATH=$sandbox_dir/libs/ + +$sandbox_dir/sbin/stargazer $sandbox_dir/etc/stargazer + +if [ $? == 0 ] +then + echo "Start successfull" + exit 0 +else + echo "Start failed" + exit 1 +fi + diff --git a/projects/stargazer/script_executer.cpp b/projects/stargazer/script_executer.cpp new file mode 100644 index 00000000..dfc737e2 --- /dev/null +++ b/projects/stargazer/script_executer.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common.h" + +using namespace std; + +#define MAX_SCRIPT_LEN (256) + +static int msgid; +static bool nonstop; + +//----------------------------------------------------------------------------- +struct SCRIPT_DATA +{ + long mtype; + char script[MAX_SCRIPT_LEN]; +} sd; +//----------------------------------------------------------------------------- +static void CatchUSR1Executer(int) +{ +//printfd(__FILE__, "CatchUSR1Executer\n"); +exit(0); +nonstop = false; +} +//----------------------------------------------------------------------------- +int ScriptExec(const string & str) +{ +if (str.length() >= MAX_SCRIPT_LEN) + return -1; + +/*printfd(__FILE__, "2 Script schedule: %s\n", str.c_str()); + +if (access(str.c_str(), X_OK)) + return -1;*/ + +struct msgbuf * mbuf; +int ret; +strncpy(sd.script, str.c_str(), MAX_SCRIPT_LEN); +sd.mtype = 1; +mbuf = (struct msgbuf * )&sd; +ret = msgsnd(msgid, mbuf, MAX_SCRIPT_LEN, 0); +if (ret < 0) + { + printfd(__FILE__, "snd ERROR!\n"); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +void Executer(int msgKey, int msgID, pid_t pid, char * procName) +{ +msgid = msgID; +if (pid) + return; +nonstop = true; +//printfd(__FILE__, "close(pipeOut) %d pid=%d\n", pipeOut, getpid()); + +//printfd(__FILE__, "Executer started %d\n", getpid()); +#ifdef LINUX +memset(procName, 0, strlen(procName)); +strcpy(procName, "stg-exec"); +#else +setproctitle("stg-exec"); +#endif + +struct sigaction newsa, oldsa; +sigset_t sigmask; + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGTERM); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGTERM, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGINT, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGUSR1); +newsa.sa_handler = CatchUSR1Executer; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGUSR1, &newsa, &oldsa); + +int ret; + +SCRIPT_DATA sd; + +while (nonstop) + { + sd.mtype = 1; + //printfd(__FILE__, "Waiting for msgrcv... msgid=%d pid=%d\n", msgid, getpid()); + ret = msgrcv(msgid, &sd, MAX_SCRIPT_LEN, 0, 0); + + if (ret < 0) + { + usleep(20000); + continue; + } + //printfd(__FILE__, "Script execute: %s\n", sd.script); + system(sd.script); + } +exit(0); +} +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/scripts/clean_db b/projects/stargazer/scripts/clean_db new file mode 100755 index 00000000..31a85a97 --- /dev/null +++ b/projects/stargazer/scripts/clean_db @@ -0,0 +1,44 @@ +#!/bin/bash + +# Этот скрипт производит очистку файловой БД stargazer-а. +# Его можно вызывать вручную или покрону, к примеру раз в неделю или раз в месяц. + + +# Эта переменная задает сколько месяцев детальной статистики оставить в БД +SAVE_MONTHS=3 + +# Эта переменная задает сколько строк оставить в логах юзеров +MAX_LOG_LINES=5000 + +# Тут определяется путь к БД +DB=/var/stargazer/ + + + + +declare -i NOW=`date +%s` +declare -i DT=SAVE_MONTHS*31*24*3600 +declare -i stat_time=0 + +for usr in $DB/users/* +do + echo cleaning `basename $usr` + for ys in $usr/detail_stat/* + do + year=`basename $ys` + + for ms in $ys/* + do + month=`basename $ms` + stat_time=`date --date="$year/$month/01" +%s` + + if (( $NOW - $stat_time > $DT )) + then + rm -fr $ms + fi + done + done + tail -n $MAX_LOG_LINES $usr/log > /tmp/stg_usr_log.`basename $usr` + mv -f /tmp/stg_usr_log.`basename $usr` $usr/log +done + diff --git a/projects/stargazer/scripts/monitor b/projects/stargazer/scripts/monitor new file mode 100755 index 00000000..1f608a71 --- /dev/null +++ b/projects/stargazer/scripts/monitor @@ -0,0 +1,66 @@ +#!/bin/bash + +# Данный скрипт производит мониторинг СТГ-сервера на зависание и в +# случае его зависания перезапускает. +# Для работы скрипта в настройках СТГ должен быть указан параметер +# MonitorDir +# Скрипт отрабатывает один раз и выходит. Т.е. он не работает постоянно +# и следит за СТГ. Его нужно вызывать по крону или как-то еще с нужной +# периодичностью!!! + + +# Путь к файлам монитора. Должен совпадать со значением MonitorDir +# в настройках сервера +MONITOR_DIR=/var/stargazer/monitor/ + + +# Максимальная задержка обновления файлов монитора в секундах. +# При привышении этого значения сервер считается зависшим и будет +# перезапущен +DT=300 + + + + +declare -i now=`date +%s` +declare -i DT=300 +declare -i file_time=0 + +stg_running=`ps ax | grep stargazer` +if [ -z "$stg_running" ] +then + echo "Stargazer is not running" + exit 0 +fi + +#wakeuper for traffcounter +ping -c 1 127.0.0.1 > /dev/null +sleep 1 + +for mon in $MONITOR_DIR/* +do + if [ ! -r $mon ] + then + echo "no monitor files" + exit 0 + fi + file_time=`stat -c%Y $mon` + + if (( $now - $file_time > $DT )) + then + echo "Stargazer is deadlocked!" + + # Команда остаовки СТГ + killall -KILL stargazer + + rm -f $MONITOR_DIR/* + sleep 15 + + # Команда запуска СТГ + /etc/init.d/stargazer start + + fi + +done + + diff --git a/projects/stargazer/scripts/shaper/OnConnect b/projects/stargazer/scripts/shaper/OnConnect new file mode 100755 index 00000000..67bf32bf --- /dev/null +++ b/projects/stargazer/scripts/shaper/OnConnect @@ -0,0 +1,54 @@ +#!/bin/bash + +int_iface=eth1 + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to connect +DIRS=$5 + +default_speed=32kbit + +# =========== shaping by tariff =========== +#tariff=$(grep -i "^tariff=" /var/stargazer/users/$LOGIN/conf | cut -f 2 -d"=") +#echo "tariff=$tariff" > /var/stargazer/users/$LOGIN/connect.log +#case $tariff in +# minimum) speedkb=128kbit;; # 128 kbit +# middle) speedkb=256kbit;; # 256 kbi +# maximum) speedkb=512kbit;; # 512 kbit +# *) speedkb=$default_speed;; # default speed +#esac +# ========= shaping by tariff end ========= + +# ========= shaping by userdata0 ========== +speedR=$(grep -i "^Userdata0=" /var/stargazer/users/$LOGIN/conf | cut -f 2 -d"=") +speed=$(echo $speedR | grep "^[0-9]*[0-9]$") + +if [ -z "$speed" ] +then + speedkb=$default_speed +else + speedkb="$speed"kbit +fi +# ======= shaping by userdata0 end ======== + +declare -i mark=$ID+10 + +echo "$mark" > /var/stargazer/users/$LOGIN/shaper_mark +echo "$speedkb" > /var/stargazer/users/$LOGIN/shaper_rate + +iptables -t mangle -A FORWARD -d $IP -j MARK --set-mark $mark + +tc class add dev $int_iface parent 1:1 classid 1:$mark htb rate $speedkb burst 40k +tc filter add dev $int_iface parent 1: protocol ip prio 3 handle $mark fw classid 1:$mark + diff --git a/projects/stargazer/scripts/shaper/OnDisconnect b/projects/stargazer/scripts/shaper/OnDisconnect new file mode 100755 index 00000000..e015eec0 --- /dev/null +++ b/projects/stargazer/scripts/shaper/OnDisconnect @@ -0,0 +1,37 @@ +#!/bin/bash + +int_iface=eth1 + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to disconnect +DIRS=$4 + +mark=$(cat /var/stargazer/users/$LOGIN/shaper_mark) +rate=$(cat /var/stargazer/users/$LOGIN/shaper_rate) + +if [ -n "$mark" ] +then + iptables -t mangle -D FORWARD -d $IP -j MARK --set-mark $mark + while [ $? == 0 ] + do + iptables -t mangle -D FORWARD -d $IP -j MARK --set-mark $mark + done +fi + +tc filter del dev $int_iface parent 1: protocol ip prio 3 handle $mark fw classid 1:$mark +tc class del dev $int_iface parent 1:1 classid 1:$mark htb rate $rate burst 40k + +#echo "D `date +%Y.%m.%d-%H.%M.%S` $IP $CASH" >> /var/stargazer/users/$LOGIN/connect.log + + diff --git a/projects/stargazer/scripts/shaper/Readme.txt b/projects/stargazer/scripts/shaper/Readme.txt new file mode 100644 index 00000000..a5fd925e --- /dev/null +++ b/projects/stargazer/scripts/shaper/Readme.txt @@ -0,0 +1,59 @@ +Настройка шейпера для STG в Linux. + +По мотивам форума: +http://local.com.ua/forum/index.php?showtopic=7920 + +Настройка сводится к указанию сетевого интерфейса, обращенного к пользователю +в скриптах shaper.sh, shaper.stop.sh, OnConnect и OnDisconnect, и уточнению +скоростоей и тарифов в скрипте OnConnect (если нужно). + +Скрипты сделаны для БД на файлах, однако, сделать их для БД на Firebird или +MySQL не составит большого труда. + +В OnConnect есть два типа шейпинга. +1. На основании тарифа. Т.е. для каждого тарифа у задана скорость и задано +дефолтное значение, на случай отсутсвия тарифа в списке скоростей или +забывчивости админа. +2. На основании Userdata0. В этом поле просто прописывается число равное +скорости в kbit/sec. Также есть дефолтное значение скорости в 32 kbit/sec +на случай отсутсвия в Userdata0 корректного значения. + +В скрипте первый способ закомментирован. Для того чтобы выбрать один из них нужно +либо удалить, либо закомментировать строчики между + +# ========= shaping by tariff ========== +......... +# ======= shaping by tariff end ======== + +и + +# ========= shaping by userdata0 ========== +......... +# ======= shaping by userdata0 end ======== + + +и нужную часть расскоментировать, если она закомментрована. + +Скрипт shaper.sh должен быть выполнен один раз при загрузке системы. + +Интерфейс обращенный к пользователю определяется в переменной +int_iface= +(присутствует во всех 4-х файлах shaper.sh, shaper.stop.sh, OnConnect и +OnDisconnect !!!) + +Скорость по умолчанию в OnConnect в переменной default_speed + +Зависимость скорости от тарифа задается в следующем фрагменте кода: +case $tariff in + minimum) speedkb=128kbit;; + middle) speedkb=256kbit;; + maximum) speedkb=512kbit;; + *) speedkb=$default_speed;; +esac + +Т.е. тут нужно вместо minimum, ... maximum подставить имена ваших тарифов +и соотв. скорость. Пользователи с тарифами не указанными в списке будут иметь +дефолтную скорость. + +Скорость ограничевается только для входящего тарафика, однако расширить +эти скрипты для исходящего не составит труда. diff --git a/projects/stargazer/scripts/shaper/shaper.sh b/projects/stargazer/scripts/shaper/shaper.sh new file mode 100755 index 00000000..2b4f042a --- /dev/null +++ b/projects/stargazer/scripts/shaper/shaper.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +int_iface=eth1 + +iptables -t mangle --flush + +tc qdisc add dev $int_iface root handle 1: htb +tc class add dev $int_iface parent 1: classid 1:1 htb rate 100mbit ceil 100mbit burst 200k + diff --git a/projects/stargazer/scripts/shaper/shaper.stop.sh b/projects/stargazer/scripts/shaper/shaper.stop.sh new file mode 100755 index 00000000..993a13ab --- /dev/null +++ b/projects/stargazer/scripts/shaper/shaper.stop.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +int_iface=eth1 + +#iptables -t mangle --flush + +tc qdisc del dev $int_iface root handle 1: htb + diff --git a/projects/stargazer/scripts/shaper_vpn_radius/Readme b/projects/stargazer/scripts/shaper_vpn_radius/Readme new file mode 100644 index 00000000..772ddddc --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/Readme @@ -0,0 +1,58 @@ +Настройка такой конфигурации происходит в 3 этапа: +1. Настройка VPN с использованием pptpd; +2. Настройка авторизации VPN через FreeRADIUS; +3. Настройка шейпера; + +1. Настройка VPN + +Необходимо установить пакеты ppp и pptpd. Также необходима поддержка PPP в ядре: + Device Drivers ---> + Network device support ---> + PPP (point-to-point protocol) support + PPP support for sync tty ports + PPP Deflate compression + PPP BSD-Compress compression + PPP MPPE compression (encryption) (EXPERIMENTAL) + PPP over Ethernet (EXPERIMENTAL) +В файле /etc/pptpd.conf прописываем файл настроек PPP (параметр option). Также +указываем адреса сервера внутри сети VPN (параметр localip) и диапазон адресов +клиентов (параметр remoteip). См. пример файла конфигурации. +В настройках PPP указываем имя сервера (параметр name), параметры шифрования. +Для использования шифрования MPPE необходима его поддержка в ядре. Кроме того, +в процессе аутентификации MPPE использует MS-CHAPv2. По этому при +использовании шифрования MPPE также необходимо указать необходимость +поддержки MS-CHAPv2 клиентом. По желанию указываем proxyarp (для того чтобы +клиенты в сети VPN "видели" друг друга) и defaultroute. Прописываем в файле +/etc/ppp/chap-secrets тестового пользователя и проверяем работоспособность +VPN. + +2. Настройка авторизации VPN через FreeRADIUS + +Необходимо установить пакет freeradius. +Настройку сервера (файл radiusd.conf) проводим в соответствии с документацией +на модуль rlm_stg.so (см. документацию на систему Stargazer). +Настройку Stargazer с плагином для FreeRADIUS проводим в соответствиии с +документацией на модуль mod_radius.so (см. документацию на систему Stargazer). +В файле clients.conf, расположенном в дирректории с конфигурационными файлами +FreeRADIUS, описываем, какие клиенты могут использовать FreeRADIUS. +Рекомендуется заменить стандартный пароль, которым шифруется обмен информации +с клиентом, "testing123" на что-то более приемлимое с точки зрения безопасности. +После этого запускаем FreeRADIUS и удостоверяемся, что он работает строчкой +"Mon Mar 31 16:06:17 2008 : Info: Ready to process requests." в журнале +(обычно, /var/log/radius/radius.log). Если журнал FreeRADIUS не позволяет +определить проблему - можно запустить сервер в отладочном режме с ключем -X. +В этом режиме более детальное журналирование проводится в консоль. +Если на данном этапе все работает - в файл насттроек PPP прописываем строчку +plugin radius.so. После этого VPN должен нормально авторизоваться +пользователями системы Stargazer. + +3. Настройка шейпера + +Собственно настройки шейпер не требует. Всё, что нужно прописано в скриптах +OnConnect и OnDisconnect. Шейпер предназначен для работы с хранилищем на +файлах, однако, переделать скипт под MySQL или Firebird не составит труда. +Скорость для пользоватлея задается в поле Userdata0 в kbit/sec. В этом поле +должно быть прописано просто число без всяких kbit/sec и т.п. Если в этом поле +у пользователя нет данных или стоит некорректное значение, пользователь будет +ограничен скоростью определенной в переменной default_speed в скрипте +OnConnect. diff --git a/projects/stargazer/scripts/shaper_vpn_radius/firewall/firewall b/projects/stargazer/scripts/shaper_vpn_radius/firewall/firewall new file mode 100755 index 00000000..36a27032 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/firewall/firewall @@ -0,0 +1,94 @@ +#!/bin/bash + +#adsl-start + +modprobe ip_queue + +int_addr=10.0.0.2 +ext_addr=192.168.1.34 + +int_net=10.0.0.0/16 +ext_net=192.168.1.0/24 + +echo 1 > /proc/sys/net/ipv4/ip_forward + +iptables -P INPUT DROP +iptables -P OUTPUT ACCEPT +iptables -P FORWARD ACCEPT + +iptables -t nat -F +iptables -t filter -F + +# +#iptables -A INPUT -d $ip1 -j ACCEPT +#iptables -A OUTPUT -s $ip1 -j ACCEPT + +# Разрешам говорить самому с собой +iptables -A INPUT -d 127.0.0.1 -j ACCEPT +iptables -A OUTPUT -s 127.0.0.1 -j ACCEPT + +#iptables -A INPUT -d $ip4 -j ACCEPT +#iptables -A INPUT -s $ip4 -j ACCEPT +#iptables -A OUTPUT -s $ip4 -j ACCEPT +#iptables -A OUTPUT -d $ip4 -j ACCEPT + +iptables -A INPUT -p icmp -j ACCEPT +iptables -A OUTPUT -p icmp -j ACCEPT + +iptables -A INPUT -p 47 -j ACCEPT +iptables -A FORWARD -p 47 -j ACCEPT +iptables -A OUTPUT -p 47 -j ACCEPT + +#SSH On this machine +iptables -A INPUT -p tcp -d $int_addr --dport 22 -j ACCEPT +iptables -A OUTPUT -p tcp -s $int_addr --sport 22 -j ACCEPT +iptables -A INPUT -p tcp -d $ext_addr --dport 22 -j ACCEPT +iptables -A OUTPUT -p tcp -s $ext_addr --sport 22 -j ACCEPT + +#WEB On this machine +#iptables -A INPUT -p tcp -d $ip2 --dport 80 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip2 --sport 80 -j ACCEPT +#iptables -A INPUT -p tcp -d $ip3 --dport 80 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip3 --sport 80 -j ACCEPT + +#PPTP +iptables -A INPUT -p tcp --dport 1723 -j ACCEPT +iptables -A OUTPUT -p tcp --sport 1723 -j ACCEPT +iptables -A INPUT -p udp --dport 1723 -j ACCEPT +iptables -A OUTPUT -p udp --sport 1723 -j ACCEPT + +#FTP +#iptables -A INPUT -p tcp -d $ip2 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip2 -j ACCEPT +#iptables -A INPUT -p tcp -d $ip3 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip3 -j ACCEPT + +#iptables -A INPUT -p tcp -d $ip2 --dport 20:21 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip2 --sport 20:21 -j ACCEPT +#iptables -A INPUT -p tcp -d $ip3 --dport 20:21 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip3 --sport 20:21 -j ACCEPT + +#iptables -A INPUT -p tcp -d $ip2 --dport 1024:65535 --sport 1024:65535 -j ACCEPT +#iptables -A INPUT -p tcp -d $ip3 --dport 1024:65535 --sport 1024:65535 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip2 --sport 1024:65535 --dport 1024:65535 -j ACCEPT +#iptables -A OUTPUT -p tcp -s $ip3 --sport 1024:65535 --dport 1024:65535 -j ACCEPT + +#DNS +iptables -A INPUT -p tcp --sport 53 -j ACCEPT +iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT +iptables -A INPUT -p udp --sport 53 -j ACCEPT +iptables -A OUTPUT -p udp --dport 53 -j ACCEPT + +#iptables -t nat -A PREROUTING -p tcp -d $ip1 --dport 80 -j ACCEPT +#iptables -t nat -A PREROUTING -p tcp -d $ip2 --dport 80 -j ACCEPT +#iptables -t nat -A PREROUTING -p tcp -d $ip3 --dport 80 -j ACCEPT +#iptables -t nat -A PREROUTING -p tcp -d $ip4 --dport 80 -j ACCEPT + +#iptables -t nat -A PREROUTING -p tcp -s 192.168.1.7 -j ACCEPT +#iptables -t nat -A PREROUTING -p tcp -s 192.168.1.16 -j ACCEPT +#iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3128 + +iptables -t nat -A POSTROUTING -d 0.0.0.0/0 -s 192.168.2.0/24 -j MASQUERADE + + + diff --git a/projects/stargazer/scripts/shaper_vpn_radius/freeradius/clients.conf b/projects/stargazer/scripts/shaper_vpn_radius/freeradius/clients.conf new file mode 100644 index 00000000..a855b2d5 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/freeradius/clients.conf @@ -0,0 +1,78 @@ +# +# clients.conf - client configuration directives +# +####################################################################### + +####################################################################### +# +# Definition of a RADIUS client (usually a NAS). +# +# The information given here over rides anything given in the +# 'clients' file, or in the 'naslist' file. The configuration here +# contains all of the information from those two files, and allows +# for more configuration items. +# +# The "shortname" is be used for logging. The "nastype", "login" and +# "password" fields are mainly used for checkrad and are optional. +# + +# +# Defines a RADIUS client. The format is 'client [hostname|ip-address]' +# +# '127.0.0.1' is another name for 'localhost'. It is enabled by default, +# to allow testing of the server after an initial installation. If you +# are not going to be permitting RADIUS queries from localhost, we suggest +# that you delete, or comment out, this entry. +# +client 127.0.0.1 { + # + # The shared secret use to "encrypt" and "sign" packets between + # the NAS and FreeRADIUS. You MUST change this secret from the + # default, otherwise it's not a secret any more! + # + # The secret can be any string, up to 31 characters in length. + # + secret = testing123 + + # + # The short name is used as an alias for the fully qualified + # domain name, or the IP address. + # + shortname = localhost + + # + # the following three fields are optional, but may be used by + # checkrad.pl for simultaneous use checks + # + + # + # The nastype tells 'checkrad.pl' which NAS-specific method to + # use to query the NAS for simultaneous use. + # + # Permitted NAS types are: + # + # cisco + # computone + # livingston + # max40xx + # multitech + # netserver + # pathras + # patton + # portslave + # tc + # usrhiper + # other # for all other types + + # + nastype = other # localhost isn't usually a NAS... + + # + # The following two configurations are for future use. + # The 'naspasswd' file is currently used to store the NAS + # login name and password, which is used by checkrad.pl + # when querying the NAS for simultaneous use. + # +# login = !root +# password = someadminpas +} diff --git a/projects/stargazer/scripts/shaper_vpn_radius/freeradius/radiusd.conf b/projects/stargazer/scripts/shaper_vpn_radius/freeradius/radiusd.conf new file mode 100644 index 00000000..c41d28d7 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/freeradius/radiusd.conf @@ -0,0 +1,1119 @@ +## +## radiusd.conf -- FreeRADIUS server configuration file. +## +## http://www.freeradius.org/ +## $Id: radiusd.conf,v 1.1 2008/03/31 13:54:59 faust Exp $ +## + +# The location of other config files and +# logfiles are declared in this file +# +# Also general configuration for modules can be done +# in this file, it is exported through the API to +# modules that ask for it. +# +# The configuration variables defined here are of the form ${foo} +# They are local to this file, and do not change from request to +# request. +# +# The per-request variables are of the form %{Attribute-Name}, and +# are taken from the values of the attribute in the incoming +# request. See 'doc/variables.txt' for more information. + +prefix = /usr +exec_prefix = /usr +sysconfdir = /etc +localstatedir = /var +sbindir = ${exec_prefix}/sbin +logdir = /var/log/freeradius +raddbdir = /etc/freeradius +radacctdir = ${logdir}/radacct + +# Location of config and logfiles. +confdir = ${raddbdir} +run_dir = ${localstatedir}/run/freeradius + +# +# The logging messages for the server are appended to the +# tail of this file. +# +log_file = ${logdir}/radius.log + +# +# libdir: Where to find the rlm_* modules. +# +# This should be automatically set at configuration time. +# +# If the server builds and installs, but fails at execution time +# with an 'undefined symbol' error, then you can use the libdir +# directive to work around the problem. +# +# The cause is usually that a library has been installed on your +# system in a place where the dynamic linker CANNOT find it. When +# executing as root (or another user), your personal environment MAY +# be set up to allow the dynamic linker to find the library. When +# executing as a daemon, FreeRADIUS MAY NOT have the same +# personalized configuration. +# +# To work around the problem, find out which library contains that symbol, +# and add the directory containing that library to the end of 'libdir', +# with a colon separating the directory names. NO spaces are allowed. +# +# e.g. libdir = /usr/local/lib:/opt/package/lib +# +# You can also try setting the LD_LIBRARY_PATH environment variable +# in a script which starts the server. +# +# If that does not work, then you can re-configure and re-build the +# server to NOT use shared libraries, via: +# +# ./configure --disable-shared +# make +# make install +# +libdir = /usr/lib/freeradius + +# pidfile: Where to place the PID of the RADIUS server. +# +# The server may be signalled while it's running by using this +# file. +# +# This file is written when ONLY running in daemon mode. +# +# e.g.: kill -HUP `cat /var/run/freeradius/freeradius.pid` +# +pidfile = ${run_dir}/freeradius.pid + + +# user/group: The name (or #number) of the user/group to run radiusd as. +# +# If these are commented out, the server will run as the user/group +# that started it. In order to change to a different user/group, you +# MUST be root ( or have root privleges ) to start the server. +# +# We STRONGLY recommend that you run the server with as few permissions +# as possible. That is, if you're not using shadow passwords, the +# user and group items below should be set to 'nobody'. +# +# On SCO (ODT 3) use "user = nouser" and "group = nogroup". +# +# NOTE that some kernels refuse to setgid(group) when the value of +# (unsigned)group is above 60000; don't use group nobody on these systems! +# +# On systems with shadow passwords, you might have to set 'group = shadow' +# for the server to be able to read the shadow password file. If you can +# authenticate users while in debug mode, but not in daemon mode, it may be +# that the debugging mode server is running as a user that can read the +# shadow info, and the user listed below can not. +# +user = freerad +group = freerad + +# max_request_time: The maximum time (in seconds) to handle a request. +# +# Requests which take more time than this to process may be killed, and +# a REJECT message is returned. +# +# WARNING: If you notice that requests take a long time to be handled, +# then this MAY INDICATE a bug in the server, in one of the modules +# used to handle a request, OR in your local configuration. +# +# This problem is most often seen when using an SQL database. If it takes +# more than a second or two to receive an answer from the SQL database, +# then it probably means that you haven't indexed the database. See your +# SQL server documentation for more information. +# +# Useful range of values: 5 to 120 +# +max_request_time = 30 + +# delete_blocked_requests: If the request takes MORE THAN 'max_request_time' +# to be handled, then maybe the server should delete it. +# +# If you're running in threaded, or thread pool mode, this setting +# should probably be 'no'. Setting it to 'yes' when using a threaded +# server MAY cause the server to crash! +# +delete_blocked_requests = no + +# cleanup_delay: The time to wait (in seconds) before cleaning up +# a reply which was sent to the NAS. +# +# The RADIUS request is normally cached internally for a short period +# of time, after the reply is sent to the NAS. The reply packet may be +# lost in the network, and the NAS will not see it. The NAS will then +# re-send the request, and the server will respond quickly with the +# cached reply. +# +# If this value is set too low, then duplicate requests from the NAS +# MAY NOT be detected, and will instead be handled as seperate requests. +# +# If this value is set too high, then the server will cache too many +# requests, and some new requests may get blocked. (See 'max_requests'.) +# +# Useful range of values: 2 to 10 +# +cleanup_delay = 5 + +# max_requests: The maximum number of requests which the server keeps +# track of. This should be 256 multiplied by the number of clients. +# e.g. With 4 clients, this number should be 1024. +# +# If this number is too low, then when the server becomes busy, +# it will not respond to any new requests, until the 'cleanup_delay' +# time has passed, and it has removed the old requests. +# +# If this number is set too high, then the server will use a bit more +# memory for no real benefit. +# +# If you aren't sure what it should be set to, it's better to set it +# too high than too low. Setting it to 1000 per client is probably +# the highest it should be. +# +# Useful range of values: 256 to infinity +# +max_requests = 1024 + +# bind_address: Make the server listen on a particular IP address, and +# send replies out from that address. This directive is most useful +# for machines with multiple IP addresses on one interface. +# +# It can either contain "*", or an IP address, or a fully qualified +# Internet domain name. The default is "*" +# +# As of 1.0, you can also use the "listen" directive. See below for +# more information. +# +bind_address = * + +# port: Allows you to bind FreeRADIUS to a specific port. +# +# The default port that most NAS boxes use is 1645, which is historical. +# RFC 2138 defines 1812 to be the new port. Many new servers and +# NAS boxes use 1812, which can create interoperability problems. +# +# The port is defined here to be 0 so that the server will pick up +# the machine's local configuration for the radius port, as defined +# in /etc/services. +# +# If you want to use the default RADIUS port as defined on your server, +# (usually through 'grep radius /etc/services') set this to 0 (zero). +# +# A port given on the command-line via '-p' over-rides this one. +# +# As of 1.0, you can also use the "listen" directive. See below for +# more information. +# +port = 0 + +# +# By default, the server uses "bind_address" to listen to all IP's +# on a machine, or just one IP. The "port" configuration is used +# to select the authentication port used when listening on those +# addresses. +# +# If you want the server to listen on additional addresses, you can +# use the "listen" section. A sample section (commented out) is included +# below. This "listen" section duplicates the functionality of the +# "bind_address" and "port" configuration entries, but it only listens +# for authentication packets. +# +# If you comment out the "bind_address" and "port" configuration entries, +# then it becomes possible to make the server accept only accounting, +# or authentication packets. Previously, it always listened for both +# types of packets, and it was impossible to make it listen for only +# one type of packet. +# +#listen { + # IP address on which to listen. + # Allowed values are: + # dotted quad (1.2.3.4) + # hostname (radius.example.com) + # wildcard (*) +# ipaddr = * + + # Port on which to listen. + # Allowed values are: + # integer port number (1812) + # 0 means "use /etc/services for the proper port" +# port = 0 + + # Type of packets to listen for. + # Allowed values are: + # auth listen for authentication packets + # acct listen for accounting packets + # +# type = auth +#} + + +# hostname_lookups: Log the names of clients or just their IP addresses +# e.g., www.freeradius.org (on) or 206.47.27.232 (off). +# +# The default is 'off' because it would be overall better for the net +# if people had to knowingly turn this feature on, since enabling it +# means that each client request will result in AT LEAST one lookup +# request to the nameserver. Enabling hostname_lookups will also +# mean that your server may stop randomly for 30 seconds from time +# to time, if the DNS requests take too long. +# +# Turning hostname lookups off also means that the server won't block +# for 30 seconds, if it sees an IP address which has no name associated +# with it. +# +# allowed values: {no, yes} +# +hostname_lookups = no + +# Core dumps are a bad thing. This should only be set to 'yes' +# if you're debugging a problem with the server. +# +# allowed values: {no, yes} +# +allow_core_dumps = no + +# Regular expressions +# +# These items are set at configure time. If they're set to "yes", +# then setting them to "no" turns off regular expression support. +# +# If they're set to "no" at configure time, then setting them to "yes" +# WILL NOT WORK. It will give you an error. +# +regular_expressions = yes +extended_expressions = yes + +# Log the full User-Name attribute, as it was found in the request. +# +# allowed values: {no, yes} +# +log_stripped_names = no + +# Log authentication requests to the log file. +# +# allowed values: {no, yes} +# +log_auth = no + +# Log passwords with the authentication requests. +# log_auth_badpass - logs password if it's rejected +# log_auth_goodpass - logs password if it's correct +# +# allowed values: {no, yes} +# +log_auth_badpass = no +log_auth_goodpass = no + +# usercollide: Turn "username collision" code on and off. See the +# "doc/duplicate-users" file +# +# WARNING +# !!!!!!! Setting this to "yes" may result in the server behaving +# !!!!!!! strangely. The "username collision" code will ONLY work +# !!!!!!! with clear-text passwords. Even then, it may not do what +# !!!!!!! you want, or what you expect. +# !!!!!!! +# !!!!!!! We STRONGLY RECOMMEND that you do not use this feature, +# !!!!!!! and that you find another way of acheiving the same goal. +# !!!!!!! +# !!!!!!! e,g. module fail-over. See 'doc/configurable_failover' +# WARNING +# +usercollide = no + +# lower_user / lower_pass: +# Lower case the username/password "before" or "after" +# attempting to authenticate. +# +# If "before", the server will first modify the request and then try +# to auth the user. If "after", the server will first auth using the +# values provided by the user. If that fails it will reprocess the +# request after modifying it as you specify below. +# +# This is as close as we can get to case insensitivity. It is the +# admin's job to ensure that the username on the auth db side is +# *also* lowercase to make this work +# +# Default is 'no' (don't lowercase values) +# Valid values = "before" / "after" / "no" +# +lower_user = no +lower_pass = no + +# nospace_user / nospace_pass: +# +# Some users like to enter spaces in their username or password +# incorrectly. To save yourself the tech support call, you can +# eliminate those spaces here: +# +# Default is 'no' (don't remove spaces) +# Valid values = "before" / "after" / "no" (explanation above) +# +nospace_user = no +nospace_pass = no + +# The program to execute to do concurrency checks. +checkrad = ${sbindir}/checkrad + +# SECURITY CONFIGURATION +# +# There may be multiple methods of attacking on the server. This +# section holds the configuration items which minimize the impact +# of those attacks +# +security { + # + # max_attributes: The maximum number of attributes + # permitted in a RADIUS packet. Packets which have MORE + # than this number of attributes in them will be dropped. + # + # If this number is set too low, then no RADIUS packets + # will be accepted. + # + # If this number is set too high, then an attacker may be + # able to send a small number of packets which will cause + # the server to use all available memory on the machine. + # + # Setting this number to 0 means "allow any number of attributes" + max_attributes = 200 + + # + # reject_delay: When sending an Access-Reject, it can be + # delayed for a few seconds. This may help slow down a DoS + # attack. It also helps to slow down people trying to brute-force + # crack a users password. + # + # Setting this number to 0 means "send rejects immediately" + # + # If this number is set higher than 'cleanup_delay', then the + # rejects will be sent at 'cleanup_delay' time, when the request + # is deleted from the internal cache of requests. + # + # Useful ranges: 1 to 5 + reject_delay = 1 + + # + # status_server: Whether or not the server will respond + # to Status-Server requests. + # + # Normally this should be set to "no", because they're useless. + # See: http://www.freeradius.org/rfc/rfc2865.html#Keep-Alives + # + # However, certain NAS boxes may require them. + # + # When sent a Status-Server message, the server responds with + # an Access-Accept packet, containing a Reply-Message attribute, + # which is a string describing how long the server has been + # running. + # + status_server = no +} + +# PROXY CONFIGURATION +# +# proxy_requests: Turns proxying of RADIUS requests on or off. +# +# The server has proxying turned on by default. If your system is NOT +# set up to proxy requests to another server, then you can turn proxying +# off here. This will save a small amount of resources on the server. +# +# If you have proxying turned off, and your configuration files say +# to proxy a request, then an error message will be logged. +# +# To disable proxying, change the "yes" to "no", and comment the +# $INCLUDE line. +# +# allowed values: {no, yes} +# +proxy_requests = yes +$INCLUDE ${confdir}/proxy.conf + + +# CLIENTS CONFIGURATION +# +# Client configuration is defined in "clients.conf". +# + +# The 'clients.conf' file contains all of the information from the old +# 'clients' and 'naslist' configuration files. We recommend that you +# do NOT use 'client's or 'naslist', although they are still +# supported. +# +# Anything listed in 'clients.conf' will take precedence over the +# information from the old-style configuration files. +# +$INCLUDE ${confdir}/clients.conf + + +# SNMP CONFIGURATION +# +# Snmp configuration is only valid if SNMP support was enabled +# at compile time. +# +# To enable SNMP querying of the server, set the value of the +# 'snmp' attribute to 'yes' +# +snmp = no +$INCLUDE ${confdir}/snmp.conf + + +# THREAD POOL CONFIGURATION +# +# The thread pool is a long-lived group of threads which +# take turns (round-robin) handling any incoming requests. +# +# You probably want to have a few spare threads around, +# so that high-load situations can be handled immediately. If you +# don't have any spare threads, then the request handling will +# be delayed while a new thread is created, and added to the pool. +# +# You probably don't want too many spare threads around, +# otherwise they'll be sitting there taking up resources, and +# not doing anything productive. +# +# The numbers given below should be adequate for most situations. +# +thread pool { + # Number of servers to start initially --- should be a reasonable + # ballpark figure. + start_servers = 5 + + # Limit on the total number of servers running. + # + # If this limit is ever reached, clients will be LOCKED OUT, so it + # should NOT BE SET TOO LOW. It is intended mainly as a brake to + # keep a runaway server from taking the system with it as it spirals + # down... + # + # You may find that the server is regularly reaching the + # 'max_servers' number of threads, and that increasing + # 'max_servers' doesn't seem to make much difference. + # + # If this is the case, then the problem is MOST LIKELY that + # your back-end databases are taking too long to respond, and + # are preventing the server from responding in a timely manner. + # + # The solution is NOT do keep increasing the 'max_servers' + # value, but instead to fix the underlying cause of the + # problem: slow database, or 'hostname_lookups=yes'. + # + # For more information, see 'max_request_time', above. + # + max_servers = 32 + + # Server-pool size regulation. Rather than making you guess + # how many servers you need, FreeRADIUS dynamically adapts to + # the load it sees, that is, it tries to maintain enough + # servers to handle the current load, plus a few spare + # servers to handle transient load spikes. + # + # It does this by periodically checking how many servers are + # waiting for a request. If there are fewer than + # min_spare_servers, it creates a new spare. If there are + # more than max_spare_servers, some of the spares die off. + # The default values are probably OK for most sites. + # + min_spare_servers = 3 + max_spare_servers = 10 + + # There may be memory leaks or resource allocation problems with + # the server. If so, set this value to 300 or so, so that the + # resources will be cleaned up periodically. + # + # This should only be necessary if there are serious bugs in the + # server which have not yet been fixed. + # + # '0' is a special value meaning 'infinity', or 'the servers never + # exit' + max_requests_per_server = 0 +} + +# MODULE CONFIGURATION +# +# The names and configuration of each module is located in this section. +# +# After the modules are defined here, they may be referred to by name, +# in other sections of this configuration file. +# +modules { + # + # Each module has a configuration as follows: + # + # name [ instance ] { + # config_item = value + # ... + # } + # + # The 'name' is used to load the 'rlm_name' library + # which implements the functionality of the module. + # + # The 'instance' is optional. To have two different instances + # of a module, it first must be referred to by 'name'. + # The different copies of the module are then created by + # inventing two 'instance' names, e.g. 'instance1' and 'instance2' + # + # The instance names can then be used in later configuration + # INSTEAD of the original 'name'. See the 'radutmp' configuration + # below for an example. + # + + # PAP module to authenticate users based on their stored password + # + # Supports multiple encryption schemes + # clear: Clear text + # crypt: Unix crypt + # md5: MD5 ecnryption + # sha1: SHA1 encryption. + # DEFAULT: crypt + pap { + encryption_scheme = crypt + } + + # CHAP module + # + # To authenticate requests containing a CHAP-Password attribute. + # + chap { + authtype = CHAP + } + + # Extensible Authentication Protocol + # + # For all EAP related authentications. + # Now in another file, because it is very large. + # +$INCLUDE ${confdir}/eap.conf + + # Microsoft CHAP authentication + # + # This module supports MS-CHAP and MS-CHAPv2 authentication. + # It also enforces the SMB-Account-Ctrl attribute. + # + mschap { + # + # As of 0.9, the mschap module does NOT support + # reading from /etc/smbpasswd. + # + # If you are using /etc/smbpasswd, see the 'passwd' + # module for an example of how to use /etc/smbpasswd + + # if use_mppe is not set to no mschap will + # add MS-CHAP-MPPE-Keys for MS-CHAPv1 and + # MS-MPPE-Recv-Key/MS-MPPE-Send-Key for MS-CHAPv2 + # + use_mppe = yes + authtype = MS-CHAP + + # if mppe is enabled require_encryption makes + # encryption moderate + # + #require_encryption = yes + + # require_strong always requires 128 bit key + # encryption + # + #require_strong = yes + + # Windows sends us a username in the form of + # DOMAIN\user, but sends the challenge response + # based on only the user portion. This hack + # corrects for that incorrect behavior. + # + #with_ntdomain_hack = no + + # The module can perform authentication itself, OR + # use a Windows Domain Controller. This configuration + # directive tells the module to call the ntlm_auth + # program, which will do the authentication, and return + # the NT-Key. Note that you MUST have "winbindd" and + # "nmbd" running on the local machine for ntlm_auth + # to work. See the ntlm_auth program documentation + # for details. + # + # Be VERY careful when editing the following line! + # + #ntlm_auth = "/path/to/ntlm_auth --request-nt-key --username=%{Stripped-User-Name:-%{User-Name:-None}} --challenge=%{mschap:Challenge:-00} --nt-response=%{mschap:NT-Response:-00}" + } + + # Preprocess the incoming RADIUS request, before handing it off + # to other modules. + # + # This module processes the 'huntgroups' and 'hints' files. + # In addition, it re-writes some weird attributes created + # by some NASes, and converts the attributes into a form which + # is a little more standard. + # + preprocess { + huntgroups = ${confdir}/huntgroups + hints = ${confdir}/hints + + # This hack changes Ascend's wierd port numberings + # to standard 0-??? port numbers so that the "+" works + # for IP address assignments. + with_ascend_hack = no + ascend_channels_per_line = 23 + + # Windows NT machines often authenticate themselves as + # NT_DOMAIN\username + # + # If this is set to 'yes', then the NT_DOMAIN portion + # of the user-name is silently discarded. + # + # This configuration entry SHOULD NOT be used. + # See the "realms" module for a better way to handle + # NT domains. + with_ntdomain_hack = no + + # Specialix Jetstream 8500 24 port access server. + # + # If the user name is 10 characters or longer, a "/" + # and the excess characters after the 10th are + # appended to the user name. + # + # If you're not running that NAS, you don't need + # this hack. + with_specialix_jetstream_hack = no + + # Cisco (and Quintum in Cisco mode) sends it's VSA attributes + # with the attribute name *again* in the string, like: + # + # H323-Attribute = "h323-attribute=value". + # + # If this configuration item is set to 'yes', then + # the redundant data in the the attribute text is stripped + # out. The result is: + # + # H323-Attribute = "value" + # + # If you're not running a Cisco or Quintum NAS, you don't + # need this hack. + with_cisco_vsa_hack = no + } + + # Write a detailed log of all accounting records received. + # + detail { + # Note that we do NOT use NAS-IP-Address here, as + # that attribute MAY BE from the originating NAS, and + # NOT from the proxy which actually sent us the + # request. The Client-IP-Address attribute is ALWAYS + # the address of the client which sent us the + # request. + # + # The following line creates a new detail file for + # every radius client (by IP address or hostname). + # In addition, a new detail file is created every + # day, so that the detail file doesn't have to go + # through a 'log rotation' + # + # If your detail files are large, you may also want + # to add a ':%H' (see doc/variables.txt) to the end + # of it, to create a new detail file every hour, e.g.: + # + # ..../detail-%Y%m%d:%H + # + # This will create a new detail file for every hour. + # + detailfile = ${radacctdir}/%{Client-IP-Address}/detail-%Y%m%d + + # + # The Unix-style permissions on the 'detail' file. + # + # The detail file often contains secret or private + # information about users. So by keeping the file + # permissions restrictive, we can prevent unwanted + # people from seeing that information. + detailperm = 0600 + + # + # Certain attributes such as User-Password may be + # "sensitive", so they should not be printed in the + # detail file. This section lists the attributes + # that should be suppressed. + # + # The attributes should be listed one to a line. + # + #suppress { + # User-Password + #} + } + + # + # Create a unique accounting session Id. Many NASes re-use + # or repeat values for Acct-Session-Id, causing no end of + # confusion. + # + # This module will add a (probably) unique session id + # to an accounting packet based on the attributes listed + # below found in the packet. See doc/rlm_acct_unique for + # more information. + # + acct_unique { + key = "User-Name, Acct-Session-Id, NAS-IP-Address, Client-IP-Address, NAS-Port" + } + + # Write a 'utmp' style file, of which users are currently + # logged in, and where they've logged in from. + # + # This file is used mainly for Simultaneous-Use checking, + # and also 'radwho', to see who's currently logged in. + # + radutmp { + # Where the file is stored. It's not a log file, + # so it doesn't need rotating. + # + filename = ${logdir}/radutmp + + # The field in the packet to key on for the + # 'user' name, If you have other fields which you want + # to use to key on to control Simultaneous-Use, + # then you can use them here. + # + # Note, however, that the size of the field in the + # 'utmp' data structure is small, around 32 + # characters, so that will limit the possible choices + # of keys. + # + # You may want instead: %{Stripped-User-Name:-%{User-Name}} + username = %{User-Name} + + + # Whether or not we want to treat "user" the same + # as "USER", or "User". Some systems have problems + # with case sensitivity, so this should be set to + # 'no' to enable the comparisons of the key attribute + # to be case insensitive. + # + case_sensitive = yes + + # Accounting information may be lost, so the user MAY + # have logged off of the NAS, but we haven't noticed. + # If so, we can verify this information with the NAS, + # + # If we want to believe the 'utmp' file, then this + # configuration entry can be set to 'no'. + # + check_with_nas = yes + + # Set the file permissions, as the contents of this file + # are usually private. + perm = 0600 + + callerid = "yes" + } + + # "Safe" radutmp - does not contain caller ID, so it can be + # world-readable, and radwho can work for normal users, without + # exposing any information that isn't already exposed by who(1). + # + # This is another 'instance' of the radutmp module, but it is given + # then name "sradutmp" to identify it later in the "accounting" + # section. + radutmp sradutmp { + filename = ${logdir}/sradutmp + perm = 0644 + callerid = "no" + } + + # attr_filter - filters the attributes received in replies from + # proxied servers, to make sure we send back to our RADIUS client + # only allowed attributes. + attr_filter { + attrsfile = ${confdir}/attrs + } + + # counter module: + # This module takes an attribute (count-attribute). + # It also takes a key, and creates a counter for each unique + # key. The count is incremented when accounting packets are + # received by the server. The value of the increment depends + # on the attribute type. + # If the attribute is Acct-Session-Time or of an integer type we add the + # value of the attribute. If it is anything else we increase the + # counter by one. + # + # The 'reset' parameter defines when the counters are all reset to + # zero. It can be hourly, daily, weekly, monthly or never. + # + # hourly: Reset on 00:00 of every hour + # daily: Reset on 00:00:00 every day + # weekly: Reset on 00:00:00 on sunday + # monthly: Reset on 00:00:00 of the first day of each month + # + # It can also be user defined. It should be of the form: + # num[hdwm] where: + # h: hours, d: days, w: weeks, m: months + # If the letter is ommited days will be assumed. In example: + # reset = 10h (reset every 10 hours) + # reset = 12 (reset every 12 days) + # + # + # The check-name attribute defines an attribute which will be + # registered by the counter module and can be used to set the + # maximum allowed value for the counter after which the user + # is rejected. + # Something like: + # + # DEFAULT Max-Daily-Session := 36000 + # Fall-Through = 1 + # + # You should add the counter module in the instantiate + # section so that it registers check-name before the files + # module reads the users file. + # + # If check-name is set and the user is to be rejected then we + # send back a Reply-Message and we log a Failure-Message in + # the radius.log + # If the count attribute is Acct-Session-Time then on each login + # we send back the remaining online time as a Session-Timeout attribute + # + # The counter-name can also be used instead of using the check-name + # like below: + # + # DEFAULT Daily-Session-Time > 3600, Auth-Type = Reject + # Reply-Message = "You've used up more than one hour today" + # + # The allowed-servicetype attribute can be used to only take + # into account specific sessions. For example if a user first + # logs in through a login menu and then selects ppp there will + # be two sessions. One for Login-User and one for Framed-User + # service type. We only need to take into account the second one. + # + # The module should be added in the instantiate, authorize and + # accounting sections. Make sure that in the authorize + # section it comes after any module which sets the + # 'check-name' attribute. + # + counter daily { + filename = ${raddbdir}/db.daily + key = User-Name + count-attribute = Acct-Session-Time + reset = daily + counter-name = Daily-Session-Time + check-name = Max-Daily-Session + allowed-servicetype = Framed-User + cache-size = 5000 + } + + # + # The "always" module is here for debugging purposes. Each + # instance simply returns the same result, always, without + # doing anything. + always fail { + rcode = fail + } + always reject { + rcode = reject + } + always ok { + rcode = ok + simulcount = 0 + mpp = no + } + + stg { + local_port = 6667 + server = localhost + port = 6666 + password = 123456 + } + +} + +# Instantiation +# +# This section orders the loading of the modules. Modules +# listed here will get loaded BEFORE the later sections like +# authorize, authenticate, etc. get examined. +# +# This section is not strictly needed. When a section like +# authorize refers to a module, it's automatically loaded and +# initialized. However, some modules may not be listed in any +# of the following sections, so they can be listed here. +# +# Also, listing modules here ensures that you have control over +# the order in which they are initalized. If one module needs +# something defined by another module, you can list them in order +# here, and ensure that the configuration will be OK. +# +instantiate { + stg +} + +# Authorization. First preprocess (hints and huntgroups files), +# then realms, and finally look in the "users" file. +# +# The order of the realm modules will determine the order that +# we try to find a matching realm. +# +# Make *sure* that 'preprocess' comes before any realm if you +# need to setup hints for the remote radius server +authorize { + # + # The preprocess module takes care of sanitizing some bizarre + # attributes in the request, and turning them into attributes + # which are more standard. + # + # It takes care of processing the 'raddb/hints' and the + # 'raddb/huntgroups' files. + # + # It also adds the %{Client-IP-Address} attribute to the request. + preprocess + + # + # The chap module will set 'Auth-Type := CHAP' if we are + # handling a CHAP request and Auth-Type has not already been set + chap + + # + # If the users are logging in with an MS-CHAP-Challenge + # attribute for authentication, the mschap module will find + # the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP' + # to the request, which will cause the server to then use + # the mschap module for authentication. + mschap + + # + # This module takes care of EAP-MD5, EAP-TLS, and EAP-LEAP + # authentication. + # + # It also sets the EAP-Type attribute in the request + # attribute list to the EAP type from the packet. + eap + + stg +} + + +# Authentication. +# +# +# This section lists which modules are available for authentication. +# Note that it does NOT mean 'try each module in order'. It means +# that a module from the 'authorize' section adds a configuration +# attribute 'Auth-Type := FOO'. That authentication type is then +# used to pick the apropriate module from the list below. +# + +# In general, you SHOULD NOT set the Auth-Type attribute. The server +# will figure it out on its own, and will do the right thing. The +# most common side effect of erroneously setting the Auth-Type +# attribute is that one authentication method will work, but the +# others will not. +# +# The common reasons to set the Auth-Type attribute by hand +# is to either forcibly reject the user, or forcibly accept him. +# +authenticate { + # + # PAP authentication, when a back-end database listed + # in the 'authorize' section supplies a password. The + # password can be clear-text, or encrypted. + Auth-Type PAP { + stg + pap + } + + # + # Most people want CHAP authentication + # A back-end database listed in the 'authorize' section + # MUST supply a CLEAR TEXT password. Encrypted passwords + # won't work. + Auth-Type CHAP { + stg + chap + } + + # + # MSCHAP authentication. + Auth-Type MS-CHAP { + stg + mschap + } + + # + # Allow EAP authentication. + eap +} + + +# +# Pre-accounting. Decide which accounting type to use. +# +preacct { + preprocess + + # + # Ensure that we have a semi-unique identifier for every + # request, and many NAS boxes are broken. + acct_unique +} + +# +# Accounting. Log the accounting data. +# +accounting { + # + # Create a 'detail'ed log of the packets. + # Note that accounting requests which are proxied + # are also logged in the detail file. + detail +# daily + + # + # For Simultaneous-Use tracking. + # + # Due to packet losses in the network, the data here + # may be incorrect. There is little we can do about it. + radutmp + + stg + +} + + +# Session database, used for checking Simultaneous-Use. Either the radutmp +# or rlm_sql module can handle this. +# The rlm_sql module is *much* faster +session { + radutmp +} + + +# Post-Authentication +# Once we KNOW that the user has been authenticated, there are +# additional steps we can take. +post-auth { + stg +} + +# +# When the server decides to proxy a request to a home server, +# the proxied request is first passed through the pre-proxy +# stage. This stage can re-write the request, or decide to +# cancel the proxy. +# +# Only a few modules currently have this method. +# +pre-proxy { +} + +# +# When the server receives a reply to a request it proxied +# to a home server, the request may be massaged here, in the +# post-proxy stage. +# +post-proxy { + # + # If you are proxying LEAP, you MUST configure the EAP + # module, and you MUST list it here, in the post-proxy + # stage. + # + # You MUST also use the 'nostrip' option in the 'realm' + # configuration. Otherwise, the User-Name attribute + # in the proxied request will not match the user name + # hidden inside of the EAP packet, and the end server will + # reject the EAP request. + # + eap +} diff --git a/projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-down.d/stg b/projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-down.d/stg new file mode 100755 index 00000000..e9c7eafd --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-down.d/stg @@ -0,0 +1,3 @@ +#!/bin/sh + +rm -f /var/stargazer/ifaces/$PPP_REMOTE \ No newline at end of file diff --git a/projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-up.d/stg b/projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-up.d/stg new file mode 100755 index 00000000..6a0b6452 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/ppp/ip-up.d/stg @@ -0,0 +1,3 @@ +#!/bin/sh + +echo $PPP_IFACE > /var/stargazer/ifaces/$PPP_REMOTE \ No newline at end of file diff --git a/projects/stargazer/scripts/shaper_vpn_radius/ppp/pptpd-options b/projects/stargazer/scripts/shaper_vpn_radius/ppp/pptpd-options new file mode 100644 index 00000000..af58ba40 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/ppp/pptpd-options @@ -0,0 +1,97 @@ +############################################################################### +# $Id: pptpd-options,v 1.1 2008/03/31 13:55:20 faust Exp $ +# +# Sample Poptop PPP options file /etc/ppp/pptpd-options +# Options used by PPP when a connection arrives from a client. +# This file is pointed to by /etc/pptpd.conf option keyword. +# Changes are effective on the next connection. See "man pppd". +# +# You are expected to change this file to suit your system. As +# packaged, it requires PPP 2.4.2 and the kernel MPPE module. +############################################################################### + + +# Authentication + +# Name of the local system for authentication purposes +# (must match the second field in /etc/ppp/chap-secrets entries) +name pptpd + +# Optional: domain name to use for authentication +# domain mydomain.net + +# Strip the domain prefix from the username before authentication. +# (applies if you use pppd with chapms-strip-domain patch) +#chapms-strip-domain + + +# Encryption +# Debian: on systems with a kernel built with the package +# kernel-patch-mppe >= 2.4.2 and using ppp >= 2.4.2, ... +# {{{ +refuse-pap +refuse-chap +refuse-mschap +# Require the peer to authenticate itself using MS-CHAPv2 [Microsoft +# Challenge Handshake Authentication Protocol, Version 2] authentication. +require-mschap-v2 +# Require MPPE 128-bit encryption +# (note that MPPE requires the use of MSCHAP-V2 during authentication) +require-mppe-128 +# }}} + + + + +# Network and Routing + +# If pppd is acting as a server for Microsoft Windows clients, this +# option allows pppd to supply one or two DNS (Domain Name Server) +# addresses to the clients. The first instance of this option +# specifies the primary DNS address; the second instance (if given) +# specifies the secondary DNS address. +# Attention! This information may not be taken into account by a Windows +# client. See KB311218 in Microsoft's knowledge base for more information. +#ms-dns 10.0.0.1 +#ms-dns 10.0.0.2 + +# If pppd is acting as a server for Microsoft Windows or "Samba" +# clients, this option allows pppd to supply one or two WINS (Windows +# Internet Name Services) server addresses to the clients. The first +# instance of this option specifies the primary WINS address; the +# second instance (if given) specifies the secondary WINS address. +#ms-wins 10.0.0.3 +#ms-wins 10.0.0.4 + +# Add an entry to this system's ARP [Address Resolution Protocol] +# table with the IP address of the peer and the Ethernet address of this +# system. This will have the effect of making the peer appear to other +# systems to be on the local ethernet. +# (you do not need this if your PPTP server is responsible for routing +# packets to the clients -- James Cameron) +proxyarp + +# Debian: do not replace the default route +defaultroute + + +# Logging + +# Enable connection debugging facilities. +# (see your syslog configuration for where pppd sends to) +#debug + +# Print out all the option values which have been set. +# (often requested by mailing list to verify options) +#dump + + +# Miscellaneous + +# Create a UUCP-style lock file for the pseudo-tty to ensure exclusive +# access. +lock + +# Disable BSD-Compress compression +nobsdcomp +plugin radius.so \ No newline at end of file diff --git a/projects/stargazer/scripts/shaper_vpn_radius/pptpd.conf b/projects/stargazer/scripts/shaper_vpn_radius/pptpd.conf new file mode 100644 index 00000000..5c64b837 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/pptpd.conf @@ -0,0 +1,82 @@ +############################################################################### +# $Id: pptpd.conf,v 1.1 2008/03/31 13:54:13 faust Exp $ +# +# Sample Poptop configuration file /etc/pptpd.conf +# +# Changes are effective when pptpd is restarted. +############################################################################### + +# TAG: ppp +# Path to the pppd program, default '/usr/sbin/pppd' on Linux +# +#ppp /usr/sbin/pppd + +# TAG: option +# Specifies the location of the PPP options file. +# By default PPP looks in '/etc/ppp/options' +# +option /etc/ppp/pptpd-options + +# TAG: debug +# Turns on (more) debugging to syslog +# +#debug + +# TAG: stimeout +# Specifies timeout (in seconds) on starting ctrl connection +# +# stimeout 10 + +# TAG: noipparam +# Suppress the passing of the client's IP address to PPP, which is +# done by default otherwise. +# +#noipparam + +# TAG: logwtmp +# Use wtmp(5) to record client connections and disconnections. +# +logwtmp + +# TAG: bcrelay +# Turns on broadcast relay to clients from interface +# +#bcrelay eth1 + +# TAG: localip +# TAG: remoteip +# Specifies the local and remote IP address ranges. +# +# Any addresses work as long as the local machine takes care of the +# routing. But if you want to use MS-Windows networking, you should +# use IP addresses out of the LAN address space and use the proxyarp +# option in the pppd options file, or run bcrelay. +# +# You can specify single IP addresses seperated by commas or you can +# specify ranges, or both. For example: +# +# 192.168.0.234,192.168.0.245-249,192.168.0.254 +# +# IMPORTANT RESTRICTIONS: +# +# 1. No spaces are permitted between commas or within addresses. +# +# 2. If you give more IP addresses than MAX_CONNECTIONS, it will +# start at the beginning of the list and go until it gets +# MAX_CONNECTIONS IPs. Others will be ignored. +# +# 3. No shortcuts in ranges! ie. 234-8 does not mean 234 to 238, +# you must type 234-238 if you mean this. +# +# 4. If you give a single localIP, that's ok - all local IPs will +# be set to the given one. You MUST still give at least one remote +# IP for each simultaneous client. +# +# (Recommended) +#localip 192.168.0.1 +#remoteip 192.168.0.234-238,192.168.0.245 +# or +#localip 192.168.0.234-238,192.168.0.245 +#remoteip 192.168.1.234-238,192.168.1.245 +localip 192.168.2.1 +remoteip 192.168.2.2-254 diff --git a/projects/stargazer/scripts/shaper_vpn_radius/radiusclient/servers b/projects/stargazer/scripts/shaper_vpn_radius/radiusclient/servers new file mode 100644 index 00000000..03a1d946 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/radiusclient/servers @@ -0,0 +1 @@ +localhost testing123 diff --git a/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnChange b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnChange new file mode 100755 index 00000000..a9272287 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnChange @@ -0,0 +1,6 @@ +login=$1 +param=$2 +oldValue=$3 +newValue=$4 + +#echo "User: '$login'. Parameter $param changed from '$oldValue' to '$newValue'" >> /var/stargazer/users.chg.log \ No newline at end of file diff --git a/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnConnect b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnConnect new file mode 100755 index 00000000..099a2194 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnConnect @@ -0,0 +1,64 @@ +#!/bin/bash + +#Этот скрипт вызывается в момент, когда пользователь +#успешно прошел авторизацию на сервере. Задача скрипта - перестроить +#файрвол так, что бы пользователь получил доступ в интернет + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to connect +DIRS=$5 + +iptables -A INPUT -s $IP -j QUEUE +iptables -A OUTPUT -d $IP -j QUEUE +iptables -A FORWARD -s $IP -j QUEUE +iptables -A FORWARD -d $IP -j QUEUE + +# shaper + +default_speed=32 + +speedR=$(grep -i "^Userdata0=" /var/stargazer/users/$LOGIN/conf | cut -f 2 -d"=") +#echo "speedR=$speedR" >> /var/stargazer/users/$LOGIN/connect.log +speed=$(echo $speedR | grep "^[0-9]*[0-9]$") + +if [ -z "$speed" ] +then + speed=$default_speed +fi + +speedkbit=$speed"kbit" + +#echo "speed=$speedkbit" >> /var/stargazer/users/$LOGIN/connect.log +declare -i mark=$ID+1 + +iptables -t mangle -A FORWARD -d $IP -j MARK --set-mark $mark + +sleep 1 + +if [ -f "/var/stargazer/ifaces/$IP" ] +then + #echo "1" >> /var/stargazer/users/$LOGIN/connect.log + ppp_iface=$(cat /var/stargazer/ifaces/$IP) +else + #echo "2" >> /var/stargazer/users/$LOGIN/connect.log + exit 0 +fi + +tc qdisc add dev $ppp_iface root handle 1: htb +tc class add dev $ppp_iface parent 1: classid 1:1 htb rate 100mbit ceil 100mbit burst 200k +tc class add dev $ppp_iface parent 1:1 classid 1:10 htb rate $speedkbit burst 20k +tc filter add dev $ppp_iface parent 1: protocol ip prio 3 handle $mark fw classid 1:10 + +#echo "C `date +%Y.%m.%d-%H.%M.%S` $IP $CASH $ID $mark $speed $ppp_iface" >> /var/stargazer/users/$LOGIN/connect.log + diff --git a/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnDisconnect b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnDisconnect new file mode 100755 index 00000000..f6b387bc --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnDisconnect @@ -0,0 +1,69 @@ +#!/bin/bash + +# Этот скрипт вызывается в момент, когда пользователь +# желает отключится от интернета или вышел таймаут у пользователя +# и сервер сам отключает пользователя +# Задача скрипта подобна задаче скрипта OnConnect - перестроить +# файрвол так, что бы пользователю закрыть доступ в интернет + +# Login +LOGIN=$1 + +#user IP +IP=$2 + +#cash +CASH=$3 + +#user ID +ID=$4 + +#Selected dirs to disconnect +DIRS=$4 + +#echo "D `date +%Y.%m.%d-%H.%M.%S` $IP $CASH" >> /var/stargazer/users/$LOGIN/connect.log + +iptables -D INPUT -s $IP -j QUEUE +while [ $? == 0 ] +do + iptables -D INPUT -s $IP -j QUEUE +done + +iptables -D OUTPUT -d $IP -j QUEUE +while [ $? == 0 ] +do + iptables -D OUTPUT -d $IP -j QUEUE +done + +iptables -D FORWARD -s $IP -j QUEUE +while [ $? == 0 ] +do + iptables -D FORWARD -s $IP -j QUEUE +done + +iptables -D FORWARD -d $IP -j QUEUE +while [ $? == 0 ] +do + iptables -D FORWARD -d $IP -j QUEUE +done + + + +declare -i mark=$ID+1 + +iptables -t mangle -D FORWARD -d $IP -j MARK --set-mark $mark +while [ $? == 0 ] +do + iptables -t mangle -D FORWARD -d $IP -j MARK --set-mark $mark +done + + +if [ -f /var/stargazer/ifaces/$IP ] + ppp_iface=$(cat /var/stargazer/ifaces/$IP) +else + exit 0 +fi + +tc qdisc del dev $ppp_iface root + + diff --git a/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserAdd b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserAdd new file mode 100755 index 00000000..a3ee3a9f --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserAdd @@ -0,0 +1,12 @@ +# Использование (неиспользование) этого скрипта дело вкуса. +# Он не выполняет критических функций. Его задача автматизировать +# действия характерные при добавлении пользователя сети, например добавлекние +# пользователю почты + +# Login +login=$1 + +#echo "added user $login" >> /var/stargazer/add_del.log + + + diff --git a/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserDel b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserDel new file mode 100755 index 00000000..3be6046e --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/OnUserDel @@ -0,0 +1,5 @@ +# Login +login=$1 + +#echo "deleted user $login" >> /var/stargazer/add_del.log + diff --git a/projects/stargazer/scripts/shaper_vpn_radius/stargazer/rules b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/rules new file mode 100644 index 00000000..3fb48284 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/rules @@ -0,0 +1,3 @@ +ALL 192.168.0.0/16 DIR1 +ALL 10.0.0.0/8 DIR2 +ALL 0.0.0.0/0 DIR0 \ No newline at end of file diff --git a/projects/stargazer/scripts/shaper_vpn_radius/stargazer/stargazer.conf b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/stargazer.conf new file mode 100644 index 00000000..221c85c9 --- /dev/null +++ b/projects/stargazer/scripts/shaper_vpn_radius/stargazer/stargazer.conf @@ -0,0 +1,298 @@ + +# Имя лог-файла куда пишутся события +LogFile = /var/log/stargazer.log + + + +# Имя файла в котором определяются правила подсчета трафика +Rules = /etc/stargazer/rules + + + +# Время через которое пишется d БД детальная статистика пользователя +# Значения: 1, 1/2, 1/4, 1/6. +# 1 - раз в чаc, 1/2 - раз в пол часа, 1/4 - раз в 15 мин, 1/6 - раз в 10 мин +DetailStatWritePeriod=1/6 + + + +# Периодичность записи записи в БД информации о статистике пользователя (минуты) +# При большом кол-ве пользователей эту величину стоит увеличить, т.к. +# запись в БД может занимать длительное время. +# Значения: 1...1440 (минуты) +StatWritePeriod = 10 + + + +# День снятия абонплаты +# Значения: 0...31. 0 - Последний день месяца +DayFee = 1 + + + +# Абонплата снимается в последний (yes) или первый (no) день учетного периода. +# Это влияет на то, как будет снята абонплата (АП) при переходе на новый тариф. +# Если у пользователя был тариф A с АП=100 и он хочет перейти на тариф B с АП=200, +# то при переходе на новый тариф со счета пользователя снимется 100, если +# DayFeeIsLastDay = yes и 200, если DayFeeIsLastDay = no +DayFeeIsLastDay = yes + + + +# День сброса данных о трафике за месяц и день перехода пользователей на новые тарифы +# Значения: 0...31. 0 - Последний день месяца +DayResetTraff = 1 + + + +# "Размазанное" снятие абонплаты. Снятие АП не раз в месяц, а каждый +# день 1/30 или 1/31 части АП +# Значения: yes, no +SpreadFee = no + + + +# Данная опция определяет может ли пользователь получить доступ в интерент +# если у него на счету нет денег, но остался предоплаченный трафик +# Значения: yes, no +FreeMbAllowInet = no + + + +# Эта опция определяет что будет писаться в стоимость трафика в detail_stat. +# Если у пользователя еще есть предоплаченный трафик и WriteFreeMbTraffCost = no, +# то в detail_stat стоимость будет 0. Если у пользователя уже нет +# предоплаченного трафика и WriteFreeMbTraffCost = no, то в detail_stat +# будет записана стоиость трафика. При WriteFreeMbTraffCost = yes стоимость +# трафика будет записана в любом случае. +WriteFreeMbTraffCost = no + + + +# Необязательный параметр. Указывает снимать полную абонплату у пользователя даже +# если он быз заморожен только часть учетного периода. +# По умолчанию установлен в no +# FullFee=no + +# Необязательный параметр указывающий показывать на счету и позволять +# использовать пользователю абонплату. По умолчанию установлен в yes +# ShowFeeInCash=yes + + + +# Названия направлений. Направления без названий не будут отображаться в +# авторизаторе и конфигураторе. Названия состоящие из нескольких слов должны +# быть взяты в кавычки + + DirName0 = Local + DirName1 = City + DirName2 = World + DirName3 = + DirName4 = + DirName5 = + DirName6 = + DirName7 = + DirName8 = + DirName9 = + + + + +# Кол-во запускаемых процессов stg-exec. +# Эти процессы отвечают за выполнение скриптов OnConnect, OnDisconnect, ... +# Кол-во процессов означает сколько скриптов могут выполнятся одновременно. +# Значения: 1...1024 +ExecutersNum = 2 + + + +# Message Key для stg-exec. +# Идентификатор очереди сообщений для выполнятеля скриптов. +# Его изменение может понадобится если есть необходимость запустить несколько +# экземпляров stg. Если вы не понимаете, что это, не трогайте этот параметр! +# Значения: 0...2^32 +# Значение по умолчанию: 5555 +# ExecMsgKey = 5555 + + + +# Путь к директории, в которой находятся модули сервера +ModulesPath = /usr/lib/stg + +# Определяет директорию, в которой будут находится файлы "монитора" +# работы сервера. В этой директории будут созданы пустые файлы, время +# модификации которых будет меняться примерно раз в минуту. Если какой-то +# компонент сервера зависнет, файл(ы) перестанет обновлятся, и по этому +# признаку можно определить сбой в работе сервера и при надобности +# перезапустить. Если параметр не указан или пустой, мониторинг производится +# не будет. Параметр не является обязательным, по умолчанию пустой. +# MonitorDir=/var/stargazer/monitor + + +################################################################################ +# Store module +# Настройки плагина работающего с БД сервера + +# Второй параметр - это имя модуля без mod_ в начале и .so в конце +# Т.е. полное имя модуля mod_store_files.so + + + # Рабочая директория сервера, тут содержатся данные о тарифах, пользователях, + # администраторах и т.д. + WorkDir = /var/stargazer + + + # Владелец, группа и права доступа на файлы статистики (stat) пользователя + ConfOwner = root + ConfGroup = root + ConfMode = 600 + + + # Владелец, группа и права доступа на файлы конфигурации (conf) пользователя + StatOwner = root + StatGroup = root + StatMode = 640 + + # Владелец, группа и права доступа на лог-файлы (log) пользователя + UserLogOwner = root + UserLogGroup = root + UserLogMode = 640 + + + +# +# # Адрес сервера БД +# server=localhost +# +# # Путь к БД на сервере или ее алиас +# path=/var/stg/stargazer.fdb +# +# # Имя пользователя БД +# user=stg +# +# # Пароль пользователя БД +# password=123456 +# + +# +# # Имя пользователя БД +# dbuser = stg +# +# # Пароль пользователя БД +# rootdbpass = 123456 +# +# # Имя БД на сервере +# dbname = stg +# +# # Адрес сервера БД +# dbhost = localhost +# + + + +################################################################################ +# Прочие модули + + + + # Настройки плагина авторизации Always Online "mod_auth_ao.so" + # Второй параметр - это имя модуля без mod_ в начале и .so в конце + # Т.е. полное имя модуля mod_auth_ao.so + # + # + + + + # Настройки плагина авторизации InetAccess "mod_auth_ia.so" + # Второй параметр - это имя модуля без mod_ в начале и .so в конце + # Т.е. полное имя модуля mod_auth_ia.so + # + # Port = 5555 + # UserDelay = 15 + # UserTimeout = 65 + # FreeMb = 0 + # + + + + # Настройки модуля конфигурации SgConfig "mod_conf_sg.so" + # Второй параметр - это имя модуля без mod_ в начале и .so в конце + + + # Порт по которому сервер взаимодействует с конфигуратором + # Значения: 1...65535 + Port = 5555 + + + + + + # Модуль захвата трафика "mod_cap_ether.so" + # Второй параметер - это имя модуля без mod_ в начале и .so в конце + # Без параметров. Только имя модуля. + + # Модуль без параметров + + + + + # Настройки модуля пингующего пользователей "mod_ping.so" + # Второй параметр - это имя модуля без mod_ в начале и .so в конце + + + # Время, в секундах, между пингами одного и того же пользователя + # Значения: 10...3600 + PingDelay = 15 + + + + + Password = 123456 + ServerIP = 127.0.0.1 + Port = 6666 + AuthServices = Login-User + AcctServices = Framed-User + + +# # Настройки модуля для удаленного выполнения скриптов OnConnect и +# # OnDisconnect "mod_remote_script.so" +# # Второй параметр - это имя модуля без mod_ в начале и .so в конце +# +# +# # Время, в секундах, между посылками подтверждений, того, что пользователь +# # всё еще онлайн +# # Значения: 10...600 +# SendPeriod = 15 +# +# # Соответствие подсетей, в которой находится пользователь и +# # соответствующего роутера. Первая часть строки - подсеть, заданная +# # как IP-адрес и маска, через пробел - IP-адрес роутера на котором +# # должны выполняться скрипты +# # Например эта запись "192.168.1.0/24 192.168.1.1" означает, что для +# # всех пользователей из подсети 192.168.1.0/24, скрипты будут +# # выполняться на роутере с адресом 192.168.1.1 +# # Subnet0...Subnet100 +# Subnet0 = 192.168.1.0/24 192.168.1.7 +# Subnet1 = 192.168.2.0/24 192.168.2.5 +# Subnet2 = 192.168.3.0/24 192.168.2.5 +# Subnet3 = 192.168.4.0/24 192.168.2.5 +# +# # Пароль для шифрования пакетов между stg-сервером и сервером, +# # выполняющим скрипты +# Password = 123456 +# +# # Этот параметр определяет какие параметры пользователя передаются +# # на удаленный сервер +# # Cash, FreeMb, Passive, Disabled, AlwaysOnline, TariffName, NextTariff, Address, +# # Note, Group, Email, RealName, Credit, EnabledDirs, Userdata0...Userdata9 +# UserParams=Cash Tariff EnabledDirs +# +# # Порт по которому сервер отсылает сообщения на роутер +# # Значения: 1...65535 +# Port = 9999 +# +# + + +################################################################################ + diff --git a/projects/stargazer/settings.cpp b/projects/stargazer/settings.cpp new file mode 100644 index 00000000..9fcacc01 --- /dev/null +++ b/projects/stargazer/settings.cpp @@ -0,0 +1,558 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* +$Revision: 1.45 $ +$Date: 2010/08/19 13:42:30 $ +$Author: faust $ +*/ + +#include +#include +#include +#include +#include + +using namespace std; + +#include "settings.h" +#include "common.h" + +//----------------------------------------------------------------------------- +SETTINGS::SETTINGS() + : confDir("/etc/stargazer"), + scriptDir("/etc/stargazer"), + pidFile("/var/run/stargazer.pid"), + monitoring(false), + detailStatWritePeriod(dsPeriod_1_6), + statWritePeriod(10), + stgExecMsgKey(5555), + executersNum(1), + fullFee(false), + dayFee(0), + dayResetTraff(0), + spreadFee(false), + freeMbAllowInet(false), + dayFeeIsLastDay(false), + writeFreeMbTraffCost(false), + showFeeInCash(true), + logger(GetStgLogger()) +{ +} +//----------------------------------------------------------------------------- +SETTINGS::SETTINGS(const std::string & cd) + : confDir(cd), + scriptDir(cd), + pidFile(), + monitoring(false), + detailStatWritePeriod(dsPeriod_1_6), + statWritePeriod(10), + stgExecMsgKey(5555), + executersNum(1), + fullFee(false), + dayFee(0), + dayResetTraff(0), + spreadFee(false), + freeMbAllowInet(false), + dayFeeIsLastDay(false), + writeFreeMbTraffCost(false), + showFeeInCash(true), + logger(GetStgLogger()) +{ +} +//----------------------------------------------------------------------------- +SETTINGS::SETTINGS(const SETTINGS & rval) + : confDir(rval.confDir), + scriptDir(rval.scriptDir), + detailStatWritePeriod(dsPeriod_1_6), + statWritePeriod(10), + dayFee(0), + dayResetTraff(0), + freeMbAllowInet(false), + dayFeeIsLastDay(false), + writeFreeMbTraffCost(false), + logger(GetStgLogger()) +{ +spreadFee = rval.spreadFee; +pidFile = rval.pidFile; +stgExecMsgKey = rval.stgExecMsgKey; +executersNum = rval.executersNum; +showFeeInCash = rval.showFeeInCash; +fullFee = rval.fullFee; +monitoring = rval.monitoring; +} +//----------------------------------------------------------------------------- +SETTINGS::~SETTINGS() +{ +} +//----------------------------------------------------------------------------- +int SETTINGS::ParseYesNo(const string & value, bool * val) +{ +if (0 == strcasecmp(value.c_str(), "yes")) + { + *val = true; + return 0; + } +if (0 == strcasecmp(value.c_str(), "no")) + { + *val = false; + return 0; + } + +strError = "Incorrect value \'" + value + "\'."; +return -1; +} +//----------------------------------------------------------------------------- +int SETTINGS::ParseInt(const string & value, int * val) +{ +/*char *res; +*val = strtol(value.c_str(), &res, 10);*/ +if (str2x(value, *val)) + { + strError = "Cannot convert \'" + value + "\' to integer."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int SETTINGS::ParseIntInRange(const string & value, int min, int max, int * val) +{ +if (ParseInt(value, val) != 0) + return -1; + +if (*val < min || *val > max) + { + strError = "Value \'" + value + "\' out of range."; + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int SETTINGS::ParseModuleSettings(const DOTCONFDocumentNode * node, vector * params) +{ +/*if (!node) + return 0;*/ +const DOTCONFDocumentNode * childNode; +PARAM_VALUE pv; +const char * value; + +pv.param = node->getName(); + +if (node->getValue(1)) + { + strError = "Unexpected value \'" + string(node->getValue(1)) + "\'."; + return -1; + } + +value = node->getValue(0); + +if (!value) + { + strError = "Module name expected."; + return -1; + } + +childNode = node->getChildNode(); +while (childNode) + { + pv.param = childNode->getName(); + int i = 0; + while ((value = childNode->getValue(i)) != NULL) + { + //printfd(__FILE__, "--> param=\'%s\' value=\'%s\'\n", childNode->getName(), value); + pv.value.push_back(value); + i++; + } + params->push_back(pv); + pv.value.clear(); + childNode = childNode->getNextNode(); + } + +/*for (unsigned i = 0; i < params->size(); i++) + { + printfd(__FILE__, "param \'%s\'\n", (*params)[i].param.c_str()); + for (unsigned j = 0; j < (*params)[i].value.size(); j++) + { + printfd(__FILE__, "value \'%s\'\n", (*params)[i].value[j].c_str()); + } + }*/ + +return 0; +} +//----------------------------------------------------------------------------- +string SETTINGS::GetStrError() const +{ +return strError; +} +//----------------------------------------------------------------------------- +void SETTINGS::ErrorCallback(void * data, const char * buf) +{ + printfd(__FILE__, buf); + SETTINGS * settings = static_cast(data); + settings->logger(buf); +} +//----------------------------------------------------------------------------- +int SETTINGS::ReadSettings() +{ +const char * requiredOptions[] = { + "ModulesPath", + "Modules", + "StoreModule", + "Rules", + "LogFile", + "DetailStatWritePeriod", + "DayFee", + "DayResetTraff", + "SpreadFee", + "FreeMbAllowInet", + "DayFeeIsLastDay", + "WriteFreeMbTraffCost", + NULL + }; +int storeModulesCount = 0; +modulesSettings.clear(); + +DOTCONFDocument conf(DOTCONFDocument::CASEINSENSITIVE); +conf.setErrorCallback(SETTINGS::ErrorCallback, this); +conf.setRequiredOptionNames(requiredOptions); +string confFile = confDir + "/stargazer.conf"; + +//printfd(__FILE__, "Conffile: %s\n", confFile.c_str()); + +if(conf.setContent(confFile.c_str()) != 0) + { + strError = "Cannot read file " + confFile; + return -1; + } + +const DOTCONFDocumentNode * node = conf.getFirstNode(); + +while (node) + { + if (strcasecmp(node->getName(), "ScriptDir") == 0) + { + scriptDir = node->getValue(0); + //printfd(__FILE__, "LogFile: %s\n", logFile.c_str()); + } + + if (strcasecmp(node->getName(), "LogFile") == 0) + { + logFile = node->getValue(0); + //printfd(__FILE__, "LogFile: %s\n", logFile.c_str()); + } + + if (strcasecmp(node->getName(), "PIDFile") == 0) + { + pidFile = node->getValue(0); + //printfd(__FILE__, "PIDFile: %s\n", pidFile.c_str()); + } + + if (strcasecmp(node->getName(), "ModulesPath") == 0) + { + modulesPath = node->getValue(0); + //printfd(__FILE__, "ModulesPath: %s\n", logFile.c_str()); + } + + if (strcasecmp(node->getName(), "Rules") == 0) + { + rules = node->getValue(0); + //printfd(__FILE__, "Rules: %s\n", rules.c_str()); + } + + if (strcasecmp(node->getName(), "DetailStatWritePeriod") == 0) + { + if (ParseDetailStatWritePeriod(node->getValue(0)) != 0) + { + strError = "Incorrect DetailStatWritePeriod value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "DetailStatWritePeriod: %d\n", detailStatWritePeriod); + } + + if (strcasecmp(node->getName(), "StatWritePeriod") == 0) + { + if (ParseIntInRange(node->getValue(0), 1, 1440, &statWritePeriod) != 0) + { + strError = "Incorrect StatWritePeriod value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "StatWritePeriod: %d\n", statWritePeriod); + } + + if (strcasecmp(node->getName(), "ExecMsgKey") == 0) + { + if (ParseInt(node->getValue(0), &stgExecMsgKey) != 0) + { + strError = "Incorrect ExecMsgKey value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + } + + if (strcasecmp(node->getName(), "ExecutersNum") == 0) + { + if (ParseIntInRange(node->getValue(0), 1, 1024, &executersNum) != 0) + { + strError = "Incorrect ExecutersNum value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "DayResetTraff: %d\n", dayResetTraff); + } + + /*if (strcasecmp(node->getName(), "ExecutersWaitTimeout") == 0) + { + if (ParseIntInRange(node->getValue(0), 1, 600, &executersWaitTimeout) != 0) + { + strError = "Incorrect ExecutersWaitTimeout value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "DayResetTraff: %d\n", dayResetTraff); + }*/ + + if (strcasecmp(node->getName(), "DayFee") == 0) + { + if (ParseIntInRange(node->getValue(0), 0, 31, &dayFee) != 0) + { + strError = "Incorrect DayFee value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "DayFee: %d\n", dayFee); + } + + if (strcasecmp(node->getName(), "FullFee") == 0) + { + if (ParseYesNo(node->getValue(0), &fullFee) != 0) + { + strError = "Incorrect FullFee value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "DayFee: %d\n", dayFee); + } + + if (strcasecmp(node->getName(), "DayResetTraff") == 0) + { + if (ParseIntInRange(node->getValue(0), 0, 31, &dayResetTraff) != 0) + { + strError = "Incorrect DayResetTraff value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "DayResetTraff: %d\n", dayResetTraff); + } + + if (strcasecmp(node->getName(), "SpreadFee") == 0) + { + if (ParseYesNo(node->getValue(0), &spreadFee) != 0) + { + strError = "Incorrect SpreadFee value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "SpreadFee: %d\n", spreadFee); + } + + if (strcasecmp(node->getName(), "FreeMbAllowInet") == 0) + { + if (ParseYesNo(node->getValue(0), &freeMbAllowInet) != 0) + { + strError = "Incorrect FreeMbAllowInet value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "FreeMbAllowInet: %d\n", freeMbAllowInet); + } + + if (strcasecmp(node->getName(), "DayFeeIsLastDay") == 0) + { + if (ParseYesNo(node->getValue(0), &dayFeeIsLastDay) != 0) + { + strError = "Incorrect DayFeeIsLastDay value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "DayFeeIsLastDay: %d\n", dayFeeIsLastDay); + } + + if (strcasecmp(node->getName(), "WriteFreeMbTraffCost") == 0) + { + if (ParseYesNo(node->getValue(0), &writeFreeMbTraffCost) != 0) + { + strError = "Incorrect WriteFreeMbTraffCost value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "WriteFreeMbTraffCost: %d\n", writeFreeMbTraffCost); + } + + if (strcasecmp(node->getName(), "ShowFeeInCash") == 0) + { + if (ParseYesNo(node->getValue(0), &showFeeInCash) != 0) + { + strError = "Incorrect ShowFeeInCash value: \'" + string(node->getValue(0)) + "\'"; + return -1; + } + //printfd(__FILE__, "ShowFeeInCash: %d\n", showFeeInCash); + } + + if (strcasecmp(node->getName(), "MonitorDir") == 0) + { + monitorDir = node->getValue(0); + struct stat stat; + monitoring = false; + + if (!lstat(monitorDir.c_str(), &stat) && S_ISDIR(stat.st_mode)) + { + monitoring = true; + } + } + + if (strcasecmp(node->getName(), "DirNames") == 0) + { + // íÙ ×ÎÕÔÒÉ ÓÅËÃÉÉ DirNames + const DOTCONFDocumentNode * child = node->getChildNode(); + if (child) + { + const DOTCONFDocumentNode * dirNameNode; + for (int i = 0; i < DIR_NUM; i++) + { + char strDirName[12]; + sprintf(strDirName, "DirName%d", i); + dirNameNode = conf.findNode(strDirName, node); + if (dirNameNode && dirNameNode->getValue(0)) + { + dirName[i] = dirNameNode->getValue(0); + //printfd(__FILE__, "dirName[%d]: %s\n", i, dirName[i].c_str()); + } + } + } + } + + if (strcasecmp(node->getName(), "StoreModule") == 0) + { + // íÙ ×ÎÕÔÒÉ ÓÅËÃÉÉ StoreModule + //printfd(__FILE__, "StoreModule\n"); + + if (node->getValue(1)) + { + // StoreModule ÄÏÌÖÅÎ ÉÍÅÔØ 1 ÁÔÒÉÂÕÔ + strError = "Unexpected \'" + string(node->getValue(1)) + "\'."; + return -1; + } + + if (storeModulesCount) + { + // äÏÌÖÅÎ ÂÙÔØ ÔÏÌØËÏ ÏÄÉÎ ÍÏÄÕÌØ StoreModule! + strError = "Should be only one StoreModule."; + return -1; + } + storeModulesCount++; + + //storeModuleSettings.clear(); //TODO To make constructor + //printfd(__FILE__, "StoreModule %s\n", node->getValue()); + storeModuleSettings.moduleName = node->getValue(0); + ParseModuleSettings(node, &storeModuleSettings.moduleParams); + } + + // þÉÔÁÅÍ ÎÁÓÔÒÏÊËÉ ×ÓÅÈ ÏÓÔÁ×ÛÉÈÓÑ ÍÏÄÕÌÅÊ. + if (strcasecmp(node->getName(), "Modules") == 0) + { + // íÙ ×ÎÕÔÒÉ ÓÅËÃÉÉ Modules + if (node->getValue(0)) + { + // Modules ÎÅ ÄÏÌÖÅÎ ÉÍÅÔØ ÁÔÒÉÂÕÏ× + strError = "Unexpected \'" + string(node->getValue(0)) + "\'."; + return -1; + } + const DOTCONFDocumentNode * child = node->getChildNode(); + while (child) + { + // íÙ ×ÎÕÔÒÉ ÓÅËÃÉÉ + //printfd(__FILE__, "Module \'%s\'\n", child->getValue(0)); + if (strcasecmp(child->getName(), "Module") != 0) + { + child = child->getNextNode(); + continue; + } + MODULE_SETTINGS modSettings; + modSettings.moduleParams.clear(); + modSettings.moduleName = child->getValue(); + + ParseModuleSettings(child, &modSettings.moduleParams); + + modulesSettings.push_back(modSettings); + + child = child->getNextNode(); + } + } + + node = node->getNextNode(); + } + +//sort(modulesSettings.begin(), modulesSettings.end()); +//modulesSettings.erase(unique(modulesSettings.begin(), modulesSettings.end()), modulesSettings.end()); + +return 0; +} +//----------------------------------------------------------------------------- +int SETTINGS::ParseDetailStatWritePeriod(const string & detailStatPeriodStr) +{ +if (detailStatPeriodStr == "1") + { + detailStatWritePeriod = dsPeriod_1; + return 0; + } +else if (detailStatPeriodStr == "1/2") + { + detailStatWritePeriod = dsPeriod_1_2; + return 0; + } +else if (detailStatPeriodStr == "1/4") + { + detailStatWritePeriod = dsPeriod_1_4; + return 0; + } +else if (detailStatPeriodStr == "1/6") + { + detailStatWritePeriod = dsPeriod_1_6; + return 0; + } + +return -1; +} +//----------------------------------------------------------------------------- +int SETTINGS::Reload () +{ +return ReadSettings(); +} +//----------------------------------------------------------------------------- +const MODULE_SETTINGS & SETTINGS::GetStoreModuleSettings() const +{ +return storeModuleSettings; +} +//----------------------------------------------------------------------------- +const vector & SETTINGS::GetModulesSettings() const +{ +return modulesSettings; +} +//----------------------------------------------------------------------------- +/*int SETTINGS::GetExecutersWaitTimeout() const +{ +return executersWaitTimeout; +}*/ +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/settings.h b/projects/stargazer/settings.h new file mode 100644 index 00000000..a9256cc9 --- /dev/null +++ b/projects/stargazer/settings.h @@ -0,0 +1,142 @@ + /* + $Revision: 1.27 $ + $Date: 2010/08/19 13:42:30 $ + $Author: faust $ + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.27 $ + $Date: 2010/08/19 13:42:30 $ + */ + + +#ifndef settingsh_h +#define settingsh_h 1 + +#include +#include +#include + +#include "common.h" +#include "base_settings.h" +#include "stg_logger.h" + +using namespace std; + +//----------------------------------------------------------------------------- +enum DETAIL_STAT_PERIOD +{ +dsPeriod_1, +dsPeriod_1_2, +dsPeriod_1_4, +dsPeriod_1_6, +}; +//----------------------------------------------------------------------------- +class SETTINGS +{ +public: + SETTINGS(); + SETTINGS(const std::string &); + SETTINGS(const SETTINGS &); + virtual ~SETTINGS(); + int Reload(); + int ReadSettings(); + + string GetStrError() const; + + int GetExecMsgKey() const { return stgExecMsgKey; }; + int GetExecutersNum() const { return executersNum; }; + //int GetExecutersWaitTimeout() const; + const string & GetDirName(int num) const { return dirName[num]; }; + const string & GetConfDir() const { return confDir; }; + const string & GetScriptDir() const { return scriptDir; }; + const string & GetRulesFileName() const { return rules; }; + const string & GetLogFileName() const { return logFile; }; + const string & GetPIDFileName() const { return pidFile; }; + int GetDetailStatWritePeriod() const + { return detailStatWritePeriod; }; + int GetStatWritePeriod() const { return statWritePeriod * 60; }; + int GetDayFee() const { return dayFee; }; + bool GetFullFee() const { return fullFee; }; + int GetDayResetTraff() const { return dayResetTraff; }; + bool GetSpreadFee() const { return spreadFee; }; + bool GetFreeMbAllowInet() const { return freeMbAllowInet; }; + bool GetDayFeeIsLastDay() const { return dayFeeIsLastDay; }; + bool GetWriteFreeMbTraffCost() const + { return writeFreeMbTraffCost; }; + bool GetShowFeeInCash() const { return showFeeInCash; }; + const string & GetMonitorDir() const { return monitorDir; }; + bool GetMonitoring() const { return monitoring; }; + + const string & GetModulesPath() const { return modulesPath; }; + const MODULE_SETTINGS & GetStoreModuleSettings() const; + const vector & GetModulesSettings() const; + +private: + + int ParseInt(const string & value, int * val); + int ParseIntInRange(const string & value, int min, int max, int * val); + int ParseYesNo(const string & value, bool * val); + int ParseDetailStatWritePeriod(const string & detailStatPeriodStr); + + int ParseModuleSettings(const DOTCONFDocumentNode * dirNameNode, vector * params); + + static void ErrorCallback(void * data, const char * buf); + + string strError; + //////////settings + string modulesPath; + string dirName[DIR_NUM]; + string confDir; + string scriptDir; + string rules; + string logFile; + string pidFile; + string monitorDir; + bool monitoring; + int detailStatWritePeriod; + int statWritePeriod; + int stgExecMsgKey; + int executersNum; + //int executersWaitTimeout; + bool fullFee; + int dayFee; // ÄÅÎØ ÓÎÑÔÉÑ ÁÂÏÎÐÌÁÔÙ + int dayResetTraff; // îÁÞÁÌÏ ÕÞÅÔÎÏÇÏ ÐÅÒÉÏÄÁ: ÄÅÎØ ÏÂÎÕÌÅÎÉÑ ÔÒÁÆÉËÁ É ÓÍÅÎÙ ÔÁÒÉÆÁ + bool spreadFee; + bool freeMbAllowInet; + bool dayFeeIsLastDay; // áð ÓÎÉÍÁÅÔÓÑ × ËÏÎÃÅ ÍÅÓÑÃÁ (true) ÉÌÉ × ÎÁÞÁÌÅ (false) + bool writeFreeMbTraffCost; // ðÉÓÁÔØ × ÄÅÔÁÌØÎÕÀ ÓÔÁÔÉÓÔÉËÕ ÓÔÏÉÍÏÓÔØ ÔÒÁÆÉËÁ, ÅÓÌÉ ÅÝÅ ÅÓÔØ ÐÒÅÄÏÐÌÁÞÅÎÎÙÊ ÔÒÁÆÉË + bool showFeeInCash; // ðÏËÁÚÙ×ÁÔØ ÐÏÌØÚÏ×ÁÔÅÌÀ áð ÎÅ ÓÞÅÔÕ É ÐÏÚ×ÏÌÑÔØ ÅÅ ÉÓÐÏÌØÚÏ×ÁÔØ + + vector modulesSettings; + MODULE_SETTINGS storeModuleSettings; + + STG_LOGGER & logger; +}; +//----------------------------------------------------------------------------- +#endif + diff --git a/projects/stargazer/stargazer.vpj b/projects/stargazer/stargazer.vpj new file mode 100644 index 00000000..e41648df --- /dev/null +++ b/projects/stargazer/stargazer.vpj @@ -0,0 +1,319 @@ + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/stargazer/stargazer.vpw b/projects/stargazer/stargazer.vpw new file mode 100644 index 00000000..4f2962ff --- /dev/null +++ b/projects/stargazer/stargazer.vpw @@ -0,0 +1,7 @@ + + + + + + + diff --git a/projects/stargazer/stargazer.vpwhistu b/projects/stargazer/stargazer.vpwhistu new file mode 100644 index 00000000..f409a0d7 --- /dev/null +++ b/projects/stargazer/stargazer.vpwhistu @@ -0,0 +1,52 @@ +[Global] +CurrentProject=stargazer.vpj +[ProjectDates] +../../stglibs/srvconf.lib/servconf.vpj=20080818103030000 +stargazer.vpj=20080818104412000 +[ActiveConfig] +stargazer.vpj=,Debug +../../stglibs/srvconf.lib/servconf.vpj=,Debug +[TreeExpansion] +stargazer.vpj 1 1 1 0 0 1 +[State] +SCREEN: 1680 1050 0 0 1680 980 0 0 N 0 0 0 0 1353 829 +CWD: plugins/configuration/sgconfig/ +BUFFER: BN="plugins/configuration/sgconfig/parser.cpp" +BI: MA=1 74 1 TABS=1 5 WWS=1 IWT=0 ST=0 IN=2 BW=0 US=32000 RO=0 SE=1 SN=0 BIN=0 MN=C HM=0 MF=616 TL=0 MLL=0 ASE=0 LNL=6 LCF=0 CAPS=0 E=0 ESBU2=-1 +VIEW: LN=.0 CL=1 LE=0 CX=0 CY=0 WI=5 BI=32 HT=0 HN=0 HF=0 HC=4 +WINDOW: 200 200 913 559 0 0 M WF=0 WT=2 "misc-nil,14,0,1" +BUFFER: BN="plugins/configuration/sgconfig/parser.cpp" +VIEW: LN=.8617 CL=34 LE=0 CX=33 CY=19 WI=157 BI=32 HT=0 HN=0 HF=0 HC=4 +FILEHIST: 9 +tariff.h +../../stglibs/stg_logger.lib/stg_logger.h +../../stglibs/stg_logger.lib/stg_logger.cpp +tariff.cpp +traffcounter.cpp +../../include/user_conf.h +../../../../tmp/OLYMP.CPP +../../../../tmp/olymp.cpp +plugins/configuration/sgconfig/parser.cpp +DEBUG: 0 0 0 0 3 0 +printf +strcpy +std::* +[TreeExpansion2] ++ ../../stglibs/srvconf.lib/servconf.vpj +- stargazer.vpj + - Source Files + - Plugin Sources + - auth + + store + + other + + configuration + + capture + + debug_cap + + ipq_cap + + divert_cap + - Header Files + + Plugin Headers + + Resource Files + + Bitmaps + + Other Files +scroll:23 diff --git a/projects/stargazer/startstg b/projects/stargazer/startstg new file mode 100755 index 00000000..7a0b3b8d --- /dev/null +++ b/projects/stargazer/startstg @@ -0,0 +1,13 @@ +#!/bin/bash + +LD_LIBRARY_PATH=../../lib ./stargazer /etc/stargazer +#./stargazer /etc/stargazer.a + +if [ $? == 0 ] +then + echo "Start successfull" +else + echo "Start failed" +fi + +#./stargazer /etc/stargazer \ No newline at end of file diff --git a/projects/stargazer/stg_timer.cpp b/projects/stargazer/stg_timer.cpp new file mode 100644 index 00000000..79844ba3 --- /dev/null +++ b/projects/stargazer/stg_timer.cpp @@ -0,0 +1,120 @@ +#include +#include + +#include + +#include "common.h" + +static int nonstop; +static pthread_t thrStgTimer; +static bool isTimerRunning = false; +volatile time_t stgTime; + +const int TIME_SPEED = 1; +/* + 1 - 1x speed + 2 - 2x speed + 5 - 5x speed + 10 - 10x speed + */ + +const int START_TIME = 0; +/* + 0 - as is + 1 - start before new day (3 min before) 29.11.2005 23:57:00 + 2 - start before new month (3 min before) 30.11.2005 23:57:00 + */ + +//----------------------------------------------------------------------------- +void * StgTimer(void *) +{ +#ifdef STG_TIMER_DEBUG +struct tm lt; +memset(<, 0, sizeof(lt)); + +lt.tm_year = 2007 - 1900; // 2005 +lt.tm_mon = 10 - 1; // Nov +lt.tm_hour = 23; // 23 h +lt.tm_min = 57; // 50 min +lt.tm_sec = 0; // 00 sec + +switch (START_TIME) + { + case 0: + stgTime = time(NULL); + break; + + case 1: + lt.tm_mday = 29; + stgTime = mktime(<); + break; + + case 2: + lt.tm_mday = 30; + stgTime = mktime(<); + break; + } +#endif + +nonstop = 1; +isTimerRunning = true; +while (nonstop) + { + #ifdef STG_TIMER_DEBUG + usleep(1000000 / TIME_SPEED); + stgTime++; + #else + stgTime = time(NULL); + usleep(500000); + #endif + } +isTimerRunning = false; + +return NULL; +} +//----------------------------------------------------------------------------- +int RunStgTimer() +{ +static int a = 0; +isTimerRunning = false; + +if (a == 0) + if (pthread_create(&thrStgTimer, NULL, StgTimer, NULL)) + { + isTimerRunning = false; + return -1; + } + +a = 1; +return 0; +} +//----------------------------------------------------------------------------- +void StopStgTimer() +{ +nonstop = 0; +pthread_join(thrStgTimer, NULL); // Cleanup thread resources +printfd(__FILE__, "STG_TIMER stopped\n"); +} +//----------------------------------------------------------------------------- +bool IsStgTimerRunning() +{ +return isTimerRunning; +} +//----------------------------------------------------------------------------- +int stgUsleep(unsigned long t) +{ +#ifdef STG_TIMER_DEBUG +return usleep(t / TIME_SPEED); +#else +return usleep(t); +#endif +} +//----------------------------------------------------------------------------- +void WaitTimer() +{ + for (int i = 0; i < 5 && !isTimerRunning; i++) + stgUsleep(200000); +} +//----------------------------------------------------------------------------- + + diff --git a/projects/stargazer/stg_timer.h b/projects/stargazer/stg_timer.h new file mode 100644 index 00000000..c57008e5 --- /dev/null +++ b/projects/stargazer/stg_timer.h @@ -0,0 +1,21 @@ + /* + $Revision: 1.9 $ + $Date: 2010/11/03 10:37:52 $ + $Author: faust $ + */ + +#ifndef STG_TIMER_H +#define STG_TIMER_H + +#include + +extern volatile const time_t stgTime; +int RunStgTimer(); +void StopStgTimer(); +void WaitTimer(); +bool IsStgTimerRunning(); +int stgUsleep(unsigned long t); + +#endif //STG_TIMER_H + + diff --git a/projects/stargazer/store_loader.cpp b/projects/stargazer/store_loader.cpp new file mode 100644 index 00000000..56bd01ac --- /dev/null +++ b/projects/stargazer/store_loader.cpp @@ -0,0 +1,127 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.6 $ + $Date: 2010/03/04 12:24:19 $ + $Author: faust $ + */ + +/* + * An implementation of RAII store plugin loader + */ + +#include + +#include "store_loader.h" +#include "common.h" + +STORE_LOADER::STORE_LOADER(const SETTINGS & settings) + : isLoaded(false), + handle(NULL), + plugin(NULL), + errorStr(), + storeSettings(settings.GetStoreModuleSettings()), + pluginFileName(settings.GetModulesPath() + "/mod_" + storeSettings.moduleName + ".so") +{ +} + +STORE_LOADER::~STORE_LOADER() +{ +Unload(); +} + +bool STORE_LOADER::Load() +{ +printfd(__FILE__, "STORE_LOADER::Load()\n"); +if (isLoaded) + { + return false; + } + +if (pluginFileName.empty()) + { + errorStr = "Empty store plugin filename"; + printfd(__FILE__, "STORE_LOADER::Load - %s\n", errorStr.c_str()); + return true; + } + +handle = dlopen(pluginFileName.c_str(), RTLD_NOW); + +if (!handle) + { + errorStr = "Error loading plugin '" + + pluginFileName + "': '" + dlerror() + "'"; + printfd(__FILE__, "STORE_LOADER::Load - %s\n", errorStr.c_str()); + return true; + } + +isLoaded = true; + +BASE_STORE * (*GetStore)(); +GetStore = (BASE_STORE * (*)())dlsym(handle, "GetStore"); +if (!GetStore) + { + errorStr = "GetStore not found."; + printfd(__FILE__, "STORE_LOADER::Load - %s\n", errorStr.c_str()); + return true; + } + +plugin = GetStore(); + +if (!plugin) + { + errorStr = "NULL store plugin"; + printfd(__FILE__, "STORE_LOADER::Load - %s\n"); + return true; + } + +plugin->SetSettings(storeSettings); +if (plugin->ParseSettings()) + { + errorStr = plugin->GetStrError(); + printfd(__FILE__, "Failed to parse settings. Plugin reports: '%s'\n", errorStr.c_str()); + return true; + } + +return false; +} + +bool STORE_LOADER::Unload() +{ +printfd(__FILE__, "STORE_LOADER::Unload()\n"); +if (!isLoaded) + { + return false; + } + +if (dlclose(handle)) + { + errorStr = "Failed to unload plugin: '"; + errorStr += dlerror(); + errorStr += "'"; + printfd(__FILE__, "STORE_LOADER::Unload - %s\n", errorStr.c_str()); + return true; + } + +isLoaded = false; + +return false; +} diff --git a/projects/stargazer/store_loader.h b/projects/stargazer/store_loader.h new file mode 100644 index 00000000..a5066926 --- /dev/null +++ b/projects/stargazer/store_loader.h @@ -0,0 +1,62 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.3 $ + $Date: 2010/03/04 12:24:19 $ + $Author: faust $ + */ + +/* + * Header file for RAII store plugin loader + */ + +#ifndef __STORE_LOADER_H__ +#define __STORE_LOADER_H__ + +#include + +#include "base_store.h" +#include "base_settings.h" +#include "settings.h" +#include "noncopyable.h" + +class STORE_LOADER : private NONCOPYABLE +{ +public: + STORE_LOADER(const SETTINGS & settings); + ~STORE_LOADER(); + + bool Load(); + bool Unload(); + + BASE_STORE * GetStore() { return plugin; }; + + const std::string & GetStrError() const { return errorStr; }; +private: + bool isLoaded; + void * handle; + BASE_STORE * plugin; + std::string errorStr; + MODULE_SETTINGS storeSettings; + std::string pluginFileName; +}; + +#endif diff --git a/projects/stargazer/tariff.cpp b/projects/stargazer/tariff.cpp new file mode 100644 index 00000000..088cb53d --- /dev/null +++ b/projects/stargazer/tariff.cpp @@ -0,0 +1,201 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 07.11.2007 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.11 $ + $Date: 2010/10/07 16:57:21 $ + $Author: faust $ + */ + +#include +#include // std::max + +#include "tariff.h" +#include "stg_timer.h" + +//----------------------------------------------------------------------------- +TARIFF & TARIFF::operator=(const TARIFF_DATA & td) +{ +tariffData = td; +return *this; +} +//----------------------------------------------------------------------------- +TARIFF & TARIFF::operator=(const TARIFF & t) +{ +tariffData = t.tariffData; +return *this; +} +//----------------------------------------------------------------------------- +double TARIFF::GetPriceWithTraffType(uint64_t up, + uint64_t down, + int dir, + time_t t) const +{ +return GetPriceWithoutFreeMb(dir, GetTraffByType(up, down) / (1024 * 1024), t); +} +//----------------------------------------------------------------------------- +int64_t TARIFF::GetTraffByType(uint64_t up, uint64_t down) const +{ +switch (tariffData.tariffConf.traffType) + { + case TRAFF_UP: + return up; + + case TRAFF_DOWN: + return down; + + case TRAFF_MAX: + return std::max(up, down); + + default: //TRAFF_UP_DOWN: + return up + down; + } +} +//----------------------------------------------------------------------------- +int TARIFF::GetThreshold(int dir) const +{ + return tariffData.dirPrice[dir].threshold; +} +//----------------------------------------------------------------------------- +void TARIFF::PrintTariff() const +{ +//printfd(__FILE__, "Traiff name: %s\n", tariffConf.name.c_str()); +//printfd(__FILE__, "Price: %8.3f %8.3f \n", dirPrice[0].GetPrice(0, 0), dirPrice[0].GetPrice(1, 0)); +//printfd(__FILE__, "Price: %8.3f %8.3f Thr:%d\n", dirPrice[1].GetPrice(0), dirPrice[1].GetPrice(1), dirPrice[1].GetThreshold()); +//printfd(__FILE__, "Price: %8.3f %8.3f Thr:%d\n", dirPrice[2].GetPrice(0), dirPrice[2].GetPrice(1), dirPrice[2].GetThreshold()); +//printfd(__FILE__, "Price: %8.3f %8.3f Thr:%d\n", dirPrice[3].GetPrice(0), dirPrice[3].GetPrice(1), dirPrice[3].GetThreshold()); +//printfd(__FILE__, "Free: %8.3f\n", tariffConf.free); +} +//----------------------------------------------------------------------------- +void TARIFF::GetDirPrice(int dir, DIRPRICE_DATA * dd) const +{ +*dd = tariffData.dirPrice[dir]; +} +//----------------------------------------------------------------------------- +void TARIFF::GetTariffData(TARIFF_DATA * td) const +{ +*td = tariffData; +} +//----------------------------------------------------------------------------- +int TARIFF::Interval(int dir, time_t t) const +{ +// Start of the day (and end of the night) in sec from 00:00:00 +int s1 = tariffData.dirPrice[dir].hDay * 3600 + + tariffData.dirPrice[dir].mDay * 60; +// Start of the night (and end of the day) in sec from 00:00:00 +int s2 = tariffData.dirPrice[dir].hNight * 3600 + + tariffData.dirPrice[dir].mNight * 60; + +struct tm * lt; + +lt = localtime(&t); + +// Position of time t in sec from 00:00:00 +// Ignoring seconds due to minute precision +int lts = lt->tm_hour * 3600 + lt->tm_min * 60; + +if (s1 < s2) + { + // Normal situation (00:00:00 is a night) + if (lts > s1 && lts < s2) + return TARIFF_DAY; + else + return TARIFF_NIGHT; + } +else + { + // Not so common but possible situation (00:00:00 is a day) + if (lts < s1 && lts > s2) + return TARIFF_NIGHT; + else + return TARIFF_DAY; + } +} +//----------------------------------------------------------------------------- +double TARIFF::GetPriceWithoutFreeMb(int dir, int mb, time_t t) const +{ +int interval = Interval(dir, t); + +/* + * 0011 - NB + * *01* - NA + * 0**1 - DB + * **** - DA + */ + +bool nd = tariffData.dirPrice[dir].noDiscount; +bool sp = tariffData.dirPrice[dir].singlePrice; +bool th = (interval == TARIFF_NIGHT); +bool tr = (mb > tariffData.dirPrice[dir].threshold); + +if (!nd && !sp && th && tr) + return tariffData.dirPrice[dir].priceNightB; +else if (!nd && tr) + return tariffData.dirPrice[dir].priceDayB; +else if (!sp && th) + return tariffData.dirPrice[dir].priceNightA; +else + return tariffData.dirPrice[dir].priceDayA; + +/*if (tariffData.dirPrice[dir].noDiscount && tariffData.dirPrice[dir].singlePrice) + { + return tariffData.dirPrice[dir].priceDayA; + } +else + { + if (tariffData.dirPrice[dir].noDiscount) + { + // Without threshold + if (interval == TARIFF_DAY) + return tariffData.dirPrice[dir].priceDayA; + else + return tariffData.dirPrice[dir].priceNightA; + } + + if (tariffData.dirPrice[dir].singlePrice) + { + // Without day/night + if (mb < tariffData.dirPrice[dir].threshold) + return tariffData.dirPrice[dir].priceDayA; + else + return tariffData.dirPrice[dir].priceDayB; + } + + if (mb < tariffData.dirPrice[dir].threshold) + { + if (interval == TARIFF_DAY) + return tariffData.dirPrice[dir].priceDayA; + else + return tariffData.dirPrice[dir].priceNightA; + } + else + { + if (interval == TARIFF_DAY) + return tariffData.dirPrice[dir].priceDayB; + else + return tariffData.dirPrice[dir].priceNightB; + } + }*/ +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/tariff.h b/projects/stargazer/tariff.h new file mode 100644 index 00000000..830863eb --- /dev/null +++ b/projects/stargazer/tariff.h @@ -0,0 +1,93 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 07.11.2007 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.9 $ + $Date: 2010/10/07 17:53:39 $ + $Author: faust $ + */ + +#ifndef TARIFF_H +#define TARIFF_H + +#include +#include + +#include "os_int.h" +#include "tariff_conf.h" + +#define TARIFF_DAY 0 +#define TARIFF_NIGHT 1 + +class TARIFF +{ +public: + TARIFF() + : tariffData() + {}; + TARIFF(const std::string & name) + : tariffData(name) + {}; + TARIFF(const TARIFF_DATA & td) + : tariffData(td) + {}; + TARIFF(const TARIFF & t) + : tariffData(t.tariffData) + {}; + ~TARIFF() {}; + + double GetPriceWithTraffType(uint64_t up, + uint64_t down, + int dir, + time_t t) const; + double GetFreeMb() const { return tariffData.tariffConf.free; }; + void GetDirPrice(int dir, DIRPRICE_DATA * dd) const; + double GetPassiveCost() const { return tariffData.tariffConf.passiveCost; }; + double GetFee() const { return tariffData.tariffConf.fee; }; + double GetFree() const { return tariffData.tariffConf.free; }; + + void PrintTariff() const; + + const std::string & GetName() const { return tariffData.tariffConf.name; }; + void SetName(const std::string & name) { tariffData.tariffConf.name = name; }; + + int GetTraffType() const { return tariffData.tariffConf.traffType; }; + int64_t GetTraffByType(uint64_t up, uint64_t down) const; + int GetThreshold(int dir) const; + void GetTariffData(TARIFF_DATA * td) const; + + TARIFF & operator=(const TARIFF_DATA & td); + TARIFF & operator=(const TARIFF & t); + bool operator==(const TARIFF & rhs) const { return GetName() == rhs.GetName(); }; + bool operator!=(const TARIFF & rhs) const { return GetName() != rhs.GetName(); }; + +private: + TARIFF_DATA tariffData; + + double GetPriceWithoutFreeMb(int dir, int mb, time_t t) const; + int Interval(int dir, time_t t) const; +}; +//----------------------------------------------------------------------------- + +#endif diff --git a/projects/stargazer/tariffs.cpp b/projects/stargazer/tariffs.cpp new file mode 100644 index 00000000..1f8a4230 --- /dev/null +++ b/projects/stargazer/tariffs.cpp @@ -0,0 +1,242 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 07.11.2007 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.9 $ + $Date: 2010/10/07 18:43:21 $ + $Author: faust $ + */ + +#include +#include +#include + +#include "tariffs.h" +#include "stg_locker.h" +#include "stg_logger.h" +#include "base_store.h" +#include "admin.h" + +using namespace std; + +//----------------------------------------------------------------------------- +TARIFFS::TARIFFS(BASE_STORE * st) + : tariffs(), + store(st), + WriteServLog(GetStgLogger()), + strError(), + noTariff(NO_TARIFF_NAME) +{ +pthread_mutex_init(&mutex, NULL); +ReadTariffs(); +} +//----------------------------------------------------------------------------- +TARIFFS::~TARIFFS() +{ +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +int TARIFFS::ReadTariffs() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +vector tariffsList; +if (store->GetTariffsList(&tariffsList)) + { + WriteServLog("Cannot get tariffs list."); + WriteServLog("%s", store->GetStrError().c_str()); + } + +int tariffsNum = tariffsList.size(); + +for (int i = 0; i < tariffsNum; i++) + { + TARIFF_DATA td; + if (store->RestoreTariff(&td, tariffsList[i])) + { + WriteServLog("Cannot read tariff %s.", tariffsList[i].c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + return -1; + } + tariffs.push_back(TARIFF(td)); + } + +return 0; +} +//----------------------------------------------------------------------------- +int TARIFFS::GetTariffsNum() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return tariffs.size(); +} +//----------------------------------------------------------------------------- +const TARIFF * TARIFFS::FindByName(const string & name) const +{ +if (name == NO_TARIFF_NAME) + return &noTariff; + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +list::const_iterator ti; +ti = find(tariffs.begin(), tariffs.end(), TARIFF(name)); + +if (ti != tariffs.end()) + return &(*ti); + +return NULL; +} +//----------------------------------------------------------------------------- +int TARIFFS::Chg(const TARIFF_DATA & td, const ADMIN & admin) +{ +const PRIV * priv = admin.GetPriv(); + +if (!priv->tariffChg) + { + string s = admin.GetLogStr() + " Change tariff \'" + + td.tariffConf.name + "\'. Access denied."; + strError = "Access denied."; + WriteServLog(s.c_str()); + return -1; + } + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +list::iterator ti; +ti = find(tariffs.begin(), tariffs.end(), TARIFF(td.tariffConf.name)); + +if (ti == tariffs.end()) + { + strError = "Tariff \'" + td.tariffConf.name + "\' cannot be changed. Tariff does not exist."; + WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + return -1; + } + +*ti = td; + +if (store->SaveTariff(td, td.tariffConf.name)) + { + string error = "Tariff " + td.tariffConf.name + " writing error. " + store->GetStrError(); + WriteServLog(error.c_str()); + return -1; + } + +WriteServLog("%s Tariff \'%s\' changed.", + admin.GetLogStr().c_str(), td.tariffConf.name.c_str()); + +return 0; +} +//----------------------------------------------------------------------------- +int TARIFFS::Del(const string & name, const ADMIN & admin) +{ +const PRIV * priv = admin.GetPriv(); + +if (!priv->tariffChg) + { + string s = admin.GetLogStr() + " Delete tariff \'" + + name + "\'. Access denied."; + strError = "Access denied."; + WriteServLog(s.c_str()); + return -1; + } + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +list::iterator ti; +ti = find(tariffs.begin(), tariffs.end(), TARIFF(name)); + +if (ti == tariffs.end()) + { + strError = "Tariff \'" + name + "\' cannot be deleted. Tariff does not exist."; + WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + return -1; + } + +if (store->DelTariff(name)) + { + WriteServLog("Cannot delete tariff %s.", name.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + return -1; + } + +tariffs.erase(ti); + +WriteServLog("%s Tariff \'%s\' deleted.", + admin.GetLogStr().c_str(), + name.c_str()); +return 0; +} +//----------------------------------------------------------------------------- +int TARIFFS::Add(const string & name, const ADMIN & admin) +{ +const PRIV * priv = admin.GetPriv(); + +if (!priv->tariffChg) + { + string s = admin.GetLogStr() + " Add tariff \'" + + name + "\'. Access denied."; + strError = "Access denied."; + WriteServLog(s.c_str()); + return -1; + } + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +list::iterator ti; +ti = find(tariffs.begin(), tariffs.end(), TARIFF(name)); + +if (ti != tariffs.end()) + { + strError = "Tariff \'" + name + "\' cannot be added. Tariff alredy exist."; + WriteServLog("%s %s", admin.GetLogStr().c_str(), strError.c_str()); + return -1; + } + +tariffs.push_back(TARIFF(name)); + +if (store->AddTariff(name) < 0) + { + strError = "Tariff " + name + " adding error. " + store->GetStrError(); + WriteServLog(strError.c_str()); + return -1; + } + +WriteServLog("%s Tariff \'%s\' added.", + admin.GetLogStr().c_str(), name.c_str()); + +return 0; +} +//----------------------------------------------------------------------------- +void TARIFFS::GetTariffsData(std::list * tdl) +{ +assert(tdl != NULL && "Tariffs data list is not null"); +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +std::list::const_iterator it = tariffs.begin(); +TARIFF_DATA td; +for (; it != tariffs.end(); ++it) + { + it->GetTariffData(&td); + tdl->push_back(td); + } +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/tariffs.h b/projects/stargazer/tariffs.h new file mode 100644 index 00000000..f2d76fb1 --- /dev/null +++ b/projects/stargazer/tariffs.h @@ -0,0 +1,79 @@ + /* + $Revision: 1.7 $ + $Date: 2010/10/07 18:43:21 $ + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 07.11.2007 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.7 $ + $Date: 2010/10/07 18:43:21 $ + $Author: faust $ + */ + +#ifndef TARIFFS_H +#define TARIFFS_H + +#include +#include + +#include "tariff.h" +#include "tariff_conf.h" + +#define TARIFF_DAY 0 +#define TARIFF_NIGHT 1 + +class BASE_STORE; +class STG_LOGGER; +class ADMIN; + +//----------------------------------------------------------------------------- +class TARIFFS +{ +public: + TARIFFS(BASE_STORE * store); + ~TARIFFS(); + int ReadTariffs (); + const TARIFF * FindByName(const std::string & name) const; + const TARIFF * GetNoTariff() const { return &noTariff; }; + int GetTariffsNum() const; + int Del(const std::string & name, const ADMIN & admin); + int Add(const std::string & name, const ADMIN & admin); + int Chg(const TARIFF_DATA & td, const ADMIN & admin); + + void GetTariffsData(std::list * tdl); + + const std::string & GetStrError() const { return strError; }; +private: + std::list tariffs; + BASE_STORE * store; + STG_LOGGER & WriteServLog; + mutable pthread_mutex_t mutex; + std::string strError; + TARIFF noTariff; +}; +//----------------------------------------------------------------------------- + +#endif diff --git a/projects/stargazer/traffcounter.cpp b/projects/stargazer/traffcounter.cpp new file mode 100644 index 00000000..54014167 --- /dev/null +++ b/projects/stargazer/traffcounter.cpp @@ -0,0 +1,935 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.58 $ + $Date: 2010/11/03 11:28:07 $ + $Author: faust $ + */ + +#include +#include + +#include "traffcounter.h" +#include "common.h" +#include "stg_locker.h" + +#define FLUSH_TIME (10) +#define REMOVE_TIME (31) + +const char protoName[PROTOMAX][8] = +{"TCP", "UDP", "ICMP", "TCP_UDP", "ALL"}; + +enum protoNum + { + tcp = 0, udp, icmp, tcp_udp, all + }; + +//----------------------------------------------------------------------------- +TRAFFCOUNTER::TRAFFCOUNTER(USERS * u, const TARIFFS *, const string & fn) + : WriteServLog(GetStgLogger()), + rulesFileName(fn), + monitoring(false), + users(u), + running(false), + stopped(true), + addUserNotifier(*this), + delUserNotifier(*this) +{ +for (int i = 0; i < DIR_NUM; i++) + strprintf(&dirName[i], "DIR%d", i); + +dirName[DIR_NUM] = "NULL"; + +users->AddNotifierUserAdd(&addUserNotifier); +users->AddNotifierUserDel(&delUserNotifier); + +pthread_mutex_init(&mutex, NULL); +} +//----------------------------------------------------------------------------- +TRAFFCOUNTER::~TRAFFCOUNTER() +{ +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +int TRAFFCOUNTER::Start() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (!stopped) + return 0; + +if (ReadRules()) + { + WriteServLog("TRAFFCOUNTER: Cannot read rules."); + return -1; + } + +printfd(__FILE__, "TRAFFCOUNTER::Start()\n"); +int h = users->OpenSearch(); +user_iter u; +if (!h) + { + WriteServLog("TRAFFCOUNTER: Cannot get users."); + return -1; + } + +while (users->SearchNext(h, &u) == 0) + { + SetUserNotifiers(u); + } +users->CloseSearch(h); + +running = true; +if (pthread_create(&thread, NULL, Run, this)) + { + WriteServLog("TRAFFCOUNTER: Error: Cannot start thread!"); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int TRAFFCOUNTER::Stop() +{ +if (stopped) + return 0; + +running = false; + +int h = users->OpenSearch(); +if (!h) + { + WriteServLog("TRAFFCOUNTER: Fatal error: Cannot get users."); + return -1; + } + +user_iter u; +while (users->SearchNext(h, &u) == 0) + { + UnSetUserNotifiers(u); + } +users->CloseSearch(h); + +//5 seconds to thread stops itself +for (int i = 0; i < 25 && !stopped; i++) + { + usleep(200000); + } + +//after 5 seconds waiting thread still running. now kill it +if (!stopped) + { + printfd(__FILE__, "kill TRAFFCOUNTER thread.\n"); + if (pthread_kill(thread, SIGINT)) + { + return -1; + } + printfd(__FILE__, "TRAFFCOUNTER killed\n"); + } +printfd(__FILE__, "TRAFFCOUNTER::Stop()\n"); + +return 0; +} +//----------------------------------------------------------------------------- +void * TRAFFCOUNTER::Run(void * data) +{ +TRAFFCOUNTER * tc = static_cast(data); +tc->stopped = false; +int c = 0; + +time_t touchTime = stgTime - MONITOR_TIME_DELAY_SEC; + +while (tc->running) + { + usleep(500000); + if (!tc->running) + { + tc->FlushAndRemove(); + break; + } + + if (tc->monitoring && (touchTime + MONITOR_TIME_DELAY_SEC <= stgTime)) + { + string monFile(tc->monitorDir + "/traffcounter_r"); + printfd(__FILE__, "Monitor=%d file TRAFFCOUNTER %s\n", tc->monitoring, monFile.c_str()); + touchTime = stgTime; + TouchFile(monFile.c_str()); + } + + if (++c % FLUSH_TIME == 0) + tc->FlushAndRemove(); + } + +tc->stopped = true; +return NULL; +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::Process(const RAW_PACKET & rawPacket) +{ +if (!running) + return; + +static time_t touchTime = stgTime - MONITOR_TIME_DELAY_SEC; + +if (monitoring && (touchTime + MONITOR_TIME_DELAY_SEC <= stgTime)) + { + static string monFile = monitorDir + "/traffcounter_p"; + printfd(__FILE__, "Monitor=%d file TRAFFCOUNTER %s\n", monitoring, monFile.c_str()); + touchTime = stgTime; + TouchFile(monFile.c_str()); + } + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +//printfd(__FILE__, "TRAFFCOUNTER::Process()\n"); +//TODO replace find with lower_bound. + +// Searching a new packet in a tree. +pp_iter pi = packets.find(rawPacket); + +// Packet found - update length and time +if (pi != packets.end()) + { + pi->second.lenU += rawPacket.GetLen(); + pi->second.lenD += rawPacket.GetLen(); + pi->second.updateTime = stgTime; + /*printfd(__FILE__, "=============================\n"); + printfd(__FILE__, "Packet found!\n"); + printfd(__FILE__, "Version=%d\n", rawPacket.GetIPVersion()); + printfd(__FILE__, "HeaderLen=%d\n", rawPacket.GetHeaderLen()); + printfd(__FILE__, "PacketLen=%d\n", rawPacket.GetLen()); + printfd(__FILE__, "SIP=%s\n", inet_ntostring(rawPacket.GetSrcIP()).c_str()); + printfd(__FILE__, "DIP=%s\n", inet_ntostring(rawPacket.GetDstIP()).c_str()); + printfd(__FILE__, "src port=%d\n", rawPacket.GetSrcPort()); + printfd(__FILE__, "pst port=%d\n", rawPacket.GetDstPort()); + printfd(__FILE__, "len=%d\n", rawPacket.GetLen()); + printfd(__FILE__, "proto=%d\n", rawPacket.GetProto()); + printfd(__FILE__, "PacketDirU=%d\n", pi->second.dirU); + printfd(__FILE__, "PacketDirD=%d\n", pi->second.dirD); + printfd(__FILE__, "=============================\n");*/ + return; + } + +PACKET_EXTRA_DATA ed; + +// Packet not found - add new packet + +ed.updateTime = stgTime; +ed.flushTime = stgTime; + +/* + userU is that whose user_ip == packet_ip_src + userD is that whose user_ip == packet_ip_dst + */ + +uint32_t ipU = rawPacket.GetSrcIP(); +uint32_t ipD = rawPacket.GetDstIP(); + +// Searching users with such IP +if (users->FindByIPIdx(ipU, &ed.userU) == 0) + { + ed.userUPresent = true; + } + +if (users->FindByIPIdx(ipD, &ed.userD) == 0) + { + ed.userDPresent = true; + } + +if (ed.userUPresent || + ed.userDPresent) + { + DeterminateDir(rawPacket, &ed.dirU, &ed.dirD); + + ed.lenD = ed.lenU = rawPacket.GetLen(); + + //TODO use result of lower_bound to inserting new record + + // Adding packet to a tree. + pair insertResult = packets.insert(pair(rawPacket, ed)); + pp_iter newPacket = insertResult.first; + + // Adding packet reference to an IP index. + ip2packets.insert(pair(ipU, newPacket)); + ip2packets.insert(pair(ipD, newPacket)); + } +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::FlushAndRemove() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +int oldPacketsSize = packets.size(); +int oldIp2packetsSize = ip2packets.size(); + +pp_iter pi; +pi = packets.begin(); +std::map newPackets; +std::multimap newIP2Packets; +while (pi != packets.end()) + { + //Flushing + if (stgTime - pi->second.flushTime > FLUSH_TIME) + { + if (pi->second.userUPresent) + { + //printfd(__FILE__, "+++ Flushing U user %s (%s:%d) of length %d\n", pi->second.userU->GetLogin().c_str(), inet_ntostring(pi->first.GetSrcIP()).c_str(), pi->first.GetSrcPort(), pi->second.lenU); + + // Add stat + if (pi->second.dirU < DIR_NUM) + { + #ifdef TRAFF_STAT_WITH_PORTS + pi->second.userU->AddTraffStatU(pi->second.dirU, + pi->first.GetDstIP(), + pi->first.GetDstPort(), + pi->second.lenU); + #else + pi->second.userU->AddTraffStatU(pi->second.dirU, + pi->first.GetDstIP(), + pi->second.lenU); + #endif + } + + pi->second.lenU = 0; + pi->second.flushTime = stgTime; + } + + if (pi->second.userDPresent) + { + //printfd(__FILE__, "+++ Flushing D user %s (%s:%d) of length %d\n", pi->second.userD->GetLogin().c_str(), inet_ntostring(pi->first.GetDstIP()).c_str(), pi->first.GetDstPort(), pi->second.lenD); + + // Add stat + if (pi->second.dirD < DIR_NUM) + { + #ifdef TRAFF_STAT_WITH_PORTS + pi->second.userD->AddTraffStatD(pi->second.dirD, + pi->first.GetSrcIP(), + pi->first.GetSrcPort(), + pi->second.lenD); + #else + pi->second.userD->AddTraffStatD(pi->second.dirD, + pi->first.GetSrcIP(), + pi->second.lenD); + #endif + } + + pi->second.lenD = 0; + pi->second.flushTime = stgTime; + } + } + + /*//Removing + if (stgTime - pi->second.updateTime > REMOVE_TIME) + { + // Remove packet and references from ip2packets index + //printfd(__FILE__, "+++ Removing +++\n"); + pair be( + ip2packets.equal_range(pi->first.GetSrcIP())); + while (be.first != be.second) + { + // Have a reference to a packet? + if (be.first->second == pi) + { + ip2packets.erase(be.first++); + //printfd(__FILE__, "Remove U from ip2packets %s\n", inet_ntostring(pi->first.GetSrcIP()).c_str()); + } + else + { + ++be.first; + } + } + + //printfd(__FILE__, "-------------------\n"); + be = ip2packets.equal_range(pi->first.GetDstIP()); + while (be.first != be.second) + { + // Have a reference to a packet? + if (be.first->second == pi) + { + ip2packets.erase(be.first++); + //printfd(__FILE__, "Remove D from ip2packets %s\n", inet_ntostring(pi->first.GetDstIP()).c_str()); + } + else + { + ++be.first; + } + } + //printfd(__FILE__, "Remove packet\n"); + packets.erase(pi++); + } + else + { + ++pi; + }*/ + if (stgTime - pi->second.updateTime < REMOVE_TIME) + { + pair res = newPackets.insert(*pi); + if (res.second) + { + newIP2Packets.insert(make_pair(pi->first.GetSrcIP(), res.first)); + newIP2Packets.insert(make_pair(pi->first.GetDstIP(), res.first)); + } + } + ++pi; + } +swap(packets, newPackets); +swap(ip2packets, newIP2Packets); +printfd(__FILE__, "FlushAndRemove() packets: %d(rem %d) ip2packets: %d(rem %d)\n", + packets.size(), + oldPacketsSize - packets.size(), + ip2packets.size(), + oldIp2packetsSize - ip2packets.size()); + +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::AddUser(user_iter user) +{ +printfd(__FILE__, "AddUser: %s\n", user->GetLogin().c_str()); +uint32_t uip = user->GetCurrIP(); +pair pi; + +STG_LOCKER(&mutex, __FILE__, __LINE__); +// Find all packets with IP belongs to this user +pi = ip2packets.equal_range(uip); + +while (pi.first != pi.second) + { + if (pi.first->second->first.GetSrcIP() == uip) + { + assert((!pi.first->second->second.userUPresent || + pi.first->second->second.userU == user) && + "U user present but it's not current user"); + + pi.first->second->second.lenU = 0; + pi.first->second->second.userU = user; + pi.first->second->second.userUPresent = true; + } + + if (pi.first->second->first.GetDstIP() == uip) + { + assert((!pi.first->second->second.userDPresent || + pi.first->second->second.userD == user) && + "D user present but it's not current user"); + + pi.first->second->second.lenD = 0; + pi.first->second->second.userD = user; + pi.first->second->second.userDPresent = true; + } + + ++pi.first; + } +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::DelUser(uint32_t uip) +{ +printfd(__FILE__, "DelUser: %s \n", inet_ntostring(uip).c_str()); +pair pi; + +STG_LOCKER(&mutex, __FILE__, __LINE__); +pi = ip2packets.equal_range(uip); + +while (pi.first != pi.second) + { + if (pi.first->second->first.GetSrcIP() == uip) + { + if (pi.first->second->second.dirU < DIR_NUM && pi.first->second->second.userUPresent) + { + #ifdef TRAFF_STAT_WITH_PORTS + pi.first->second->second.userU->AddTraffStatU(pi.first->second->second.dirU, + pi.first->second->first.GetDstIP(), + pi.first->second->first.GetDstPort(), + pi.first->second->second.lenU); + #else + pi.first->second->second.userU->AddTraffStatU(pi.first->second->second.dirU, + pi.first->second->first.GetDstIP(), + pi.first->second->second.lenU); + #endif + } + pi.first->second->second.userUPresent = false; + } + + if (pi.first->second->first.GetDstIP() == uip) + { + if (pi.first->second->second.dirD < DIR_NUM && pi.first->second->second.userDPresent) + { + #ifdef TRAFF_STAT_WITH_PORTS + pi.first->second->second.userD->AddTraffStatD(pi.first->second->second.dirD, + pi.first->second->first.GetSrcIP(), + pi.first->second->first.GetSrcPort(), + pi.first->second->second.lenD); + #else + pi.first->second->second.userD->AddTraffStatD(pi.first->second->second.dirD, + pi.first->second->first.GetSrcIP(), + pi.first->second->second.lenD); + #endif + } + + pi.first->second->second.userDPresent = false; + } + + ++pi.first; + } + +ip2packets.erase(pi.first, pi.second); +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::SetUserNotifiers(user_iter user) +{ +// Adding user. Adding notifiers to user. +TRF_IP_BEFORE ipBNotifier(*this, user); +ipBeforeNotifiers.push_front(ipBNotifier); +user->AddCurrIPBeforeNotifier(&(*ipBeforeNotifiers.begin())); + +TRF_IP_AFTER ipANotifier(*this, user); +ipAfterNotifiers.push_front(ipANotifier); +user->AddCurrIPAfterNotifier(&(*ipAfterNotifiers.begin())); +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::UnSetUserNotifiers(user_iter user) +{ +// Removing user. Removing notifiers from user. +list::iterator bi; +list::iterator ai; + +bi = ipBeforeNotifiers.begin(); +while (bi != ipBeforeNotifiers.end()) + { + if (user->GetLogin() == bi->GetUser()->GetLogin()) + { + user->DelCurrIPBeforeNotifier(&(*bi)); + ipBeforeNotifiers.erase(bi); + break; + } + ++bi; + } + +ai = ipAfterNotifiers.begin(); +while (ai != ipAfterNotifiers.end()) + { + if (user->GetLogin() == ai->GetUser()->GetLogin()) + { + user->DelCurrIPAfterNotifier(&(*ai)); + ipAfterNotifiers.erase(ai); + break; + } + ++ai; + } +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::DeterminateDir(const RAW_PACKET & packet, + int * dirU, // Direction for incoming packet + int * dirD) const // Direction for outgoing packet +{ +bool addrMatchU; +bool portMatchU; +bool addrMatchD; +bool portMatchD; +bool foundU = false; // Was rule for U found ? +bool foundD = false; // Was rule for D found ? +//printfd(__FILE__, "foundU=%d, foundD=%d\n", foundU, foundD); + +enum { ICMP_RPOTO = 1, TCP_PROTO = 6, UDP_PROTO = 17 }; + +list::const_iterator ln; +ln = rules.begin(); + +while (ln != rules.end()) + { + if (!foundU) + { + addrMatchU = false; + portMatchU = false; + + switch (ln->proto) + { + case all: + portMatchU = true; + break; + + case icmp: + portMatchU = (packet.GetProto() == ICMP_RPOTO); + break; + + case tcp_udp: + if (packet.GetProto() == TCP_PROTO || packet.GetProto() == UDP_PROTO) + portMatchU = (packet.GetDstPort() >= ln->port1) && (packet.GetDstPort() <= ln->port2); + break; + + case tcp: + if (packet.GetProto() == TCP_PROTO) + portMatchU = (packet.GetDstPort() >= ln->port1) && (packet.GetDstPort() <= ln->port2); + break; + + case udp: + if (packet.GetProto() == UDP_PROTO) + portMatchU = (packet.GetDstPort() >= ln->port1) && (packet.GetDstPort() <= ln->port2); + break; + + default: + printfd(__FILE__, "Error! Incorrect rule!\n"); + break; + } + + addrMatchU = (packet.GetDstIP() & ln->mask) == ln->ip; + + if (!foundU && addrMatchU && portMatchU) + { + foundU = true; + *dirU = ln->dir; + //printfd(__FILE__, "Up rule ok! %d\n", ln->dir); + //PrintRule(ln->rule); + } + + } //if (!foundU) + + if (!foundD) + { + addrMatchD = false; + portMatchD = false; + + switch (ln->proto) + { + case all: + portMatchD = true; + break; + + case icmp: + portMatchD = (packet.GetProto() == ICMP_RPOTO); + break; + + case tcp_udp: + if (packet.GetProto() == TCP_PROTO || packet.GetProto() == UDP_PROTO) + portMatchD = (packet.GetSrcPort() >= ln->port1) && (packet.GetSrcPort() <= ln->port2); + break; + + case tcp: + if (packet.GetProto() == TCP_PROTO) + portMatchD = (packet.GetSrcPort() >= ln->port1) && (packet.GetSrcPort() <= ln->port2); + break; + + case udp: + if (packet.GetProto() == UDP_PROTO) + portMatchD = (packet.GetSrcPort() >= ln->port1) && (packet.GetSrcPort() <= ln->port2); + break; + + default: + printfd(__FILE__, "Error! Incorrect rule!\n"); + break; + } + + addrMatchD = (packet.GetSrcIP() & ln->mask) == ln->ip; + + if (!foundD && addrMatchD && portMatchD) + { + foundD = true; + *dirD = ln->dir; + //printfd(__FILE__, "Down rule ok! %d\n", ln->dir); + //PrintRule(ln->rule); + } + } //if (!foundD) + + ++ln; + } //while (ln != rules.end()) + +if (!foundU) + *dirU = DIR_NUM; + +if (!foundD) + *dirD = DIR_NUM; + +return; +}; +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::SetRulesFile(const string & fn) +{ +rulesFileName = fn; +} +//----------------------------------------------------------------------------- +bool TRAFFCOUNTER::ReadRules(bool test) +{ +//printfd(__FILE__, "TRAFFCOUNTER::ReadRules()\n"); + +RULE rul; +FILE * f; +char str[1024]; +char tp[100]; // protocol +char ta[100]; // address +char td[100]; // target direction +int r; +int lineNumber = 0; +f = fopen(rulesFileName.c_str(), "rt"); + +if (!f) + { + WriteServLog("File %s cannot be oppened.", rulesFileName.c_str()); + return true; + } + +while (fgets(str, 1023, f)) + { + lineNumber++; + if (str[strspn(str," \t")] == '#' || str[strspn(str," \t")] == '\n') + { + continue; + } + + r = sscanf(str,"%s %s %s", tp, ta, td); + if (r != 3) + { + WriteServLog("Error in file %s. There must be 3 parameters. Line %d.", rulesFileName.c_str(), lineNumber); + return true; + } + + rul.proto = 0xff; + rul.dir = 0xff; + + for (int i = 0; i < PROTOMAX; i++) + { + if (strcasecmp(tp, protoName[i]) == 0) + rul.proto = i; + } + + for (int i = 0; i < DIR_NUM + 1; i++) + { + if (td == dirName[i]) + rul.dir = i; + } + + if (rul.dir == 0xff || rul.proto == 0xff) + { + WriteServLog("Error in file %s. Line %d.", + rulesFileName.c_str(), lineNumber); + return true; + } + + if (ParseAddress(ta, &rul) != 0) + { + WriteServLog("Error in file %s. Error in adress. Line %d.", + rulesFileName.c_str(), lineNumber); + return true; + } + if (!test) + rules.push_back(rul); + //PrintRule(rul); + } + +fclose(f); + +// Adding lastest rule: ALL 0.0.0.0/0 NULL +rul.dir = DIR_NUM; //NULL +rul.ip = 0; //0.0.0.0 +rul.mask = 0; +rul.port1 = 0; +rul.port2 = 65535; +rul.proto = all; + +if (!test) + rules.push_back(rul); + +//PrintRule(rul); + +return false; +} +//----------------------------------------------------------------------------- +int TRAFFCOUNTER::Reload() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (ReadRules(true)) + { + WriteServLog("TRAFFCOUNTER: Cannot reload rules. Errors found."); + return -1; + } + +FreeRules(); +ReadRules(); +WriteServLog("TRAFFCOUNTER: Reload rules successfull."); +return 0; +} +//----------------------------------------------------------------------------- +bool TRAFFCOUNTER::ParseAddress(const char * ta, RULE * rule) const +{ +char addr[50], mask[20], port1[20], port2[20], ports[40]; + +int len = strlen(ta); +char n = 0; +int i, p; +memset(addr, 0, sizeof(addr)); +for (i = 0; i < len; i++) + { + if (ta[i] == '/' || ta[i] == ':') + { + addr[i] = 0; + n = ta[i]; + break; + } + addr[i] = ta[i]; + n = 0; + } +addr[i + 1] = 0; +p = i + 1; + +if (n == '/') + { + // mask + for (; i < len; i++) + { + if (ta[i] == ':') + { + mask[i - p] = 0; + n = ':'; + break; + } + mask[i - p] = ta[i]; + } + mask[i - p] = 0; + } +else + { + strcpy(mask, "32"); + } + +p = i + 1; +i++; + +if (n == ':') + { + // port + if (!(rule->proto == tcp || rule->proto == udp || rule->proto == tcp_udp)) + { + WriteServLog("No ports specified for this protocol."); + return true; + } + + for (; i < len; i++) + ports[i - p] = ta[i]; + + ports[i - p] = 0; + } +else + { + strcpy(ports, "0-65535"); + } + +char *sss; +char pts[100]; +strcpy(pts, ports); + +if ((sss = strchr(ports, '-')) != NULL) + { + strncpy(port1, ports, int(sss-ports)); + port1[int(sss - ports)] = 0; + strcpy(port2, sss + 1); + } +else + { + strcpy(port1, ports); + strcpy(port2, ports); + } + +// Convert strings to mask, ports and IP +int prt1, prt2, msk; +unsigned ip; +char *res; + +msk = strtol(mask, &res, 10); +if (*res != 0) + return true; + +prt1 = strtol(port1, &res, 10); +if (*res != 0) + return true; + +prt2 = strtol(port2, &res, 10); +if (*res != 0) + return true; + +int r = inet_aton(addr, (struct in_addr*)&ip); +if (r == 0) + return true; + +rule->ip = ip; +rule->mask = CalcMask(msk); +//msk = 1; +//printfd(__FILE__, "msk=%d mask=%08X mask=%08X\n", msk, rule->mask, (0xFFffFFff << (32 - msk))); + +if ((ip & rule->mask) != ip) + { + WriteServLog("Address does'n match mask."); + return true; + } + +rule->port1 = prt1; +rule->port2 = prt2; + +return false; +} +//----------------------------------------------------------------------------- +uint32_t TRAFFCOUNTER::CalcMask(uint32_t msk) const +{ +if (msk >= 32) return 0xFFffFFff; +if (msk == 0) return 0; +return htonl(0xFFffFFff << (32 - msk)); +} +//--------------------------------------------------------------------------- +void TRAFFCOUNTER::FreeRules() +{ +rules.clear(); +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::PrintRule(RULE rule) const +{ +printf("%15s ", inet_ntostring(rule.ip).c_str()); +printf("mask=%08X ", rule.mask); +printf("port1=%5d ", rule.port1); +printf("port2=%5d ", rule.port2); +switch (rule.proto) + { + case 0: + printf("TCP "); + break; + case 1: + printf("UDP "); + break; + case 2: + printf("ICMP "); + break; + case 3: + printf("TCP_UDP "); + break; + case 4: + printf("ALL "); + break; + } +printf("dir=%d \n", rule.dir); +return; +} +//----------------------------------------------------------------------------- +void TRAFFCOUNTER::SetMonitorDir(const string & monitorDir) +{ +TRAFFCOUNTER::monitorDir = monitorDir; +monitoring = (monitorDir != ""); +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/traffcounter.h b/projects/stargazer/traffcounter.h new file mode 100644 index 00000000..7ea4dc64 --- /dev/null +++ b/projects/stargazer/traffcounter.h @@ -0,0 +1,262 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.23 $ + $Date: 2010/04/22 12:57:46 $ + $Author: faust $ + */ + + +#ifndef TRAFFCOUNTER_H +#define TRAFFCOUNTER_H + +#include +#include +#include +#include +#include + +#include "os_int.h" +#include "stg_logger.h" +#include "raw_ip_packet.h" +#include "users.h" +#include "actions.h" +#include "noncopyable.h" +#include "eventloop.h" + +#define PROTOMAX (5) + +//----------------------------------------------------------------------------- +struct RULE +{ +uint32_t ip; // IP +uint32_t mask; // Network mask +uint16_t port1; // Min port +uint16_t port2; // Max port +uint8_t proto; // Protocol +uint32_t dir; // Direction +}; +//----------------------------------------------------------------------------- +struct PACKET_EXTRA_DATA +{ +PACKET_EXTRA_DATA() + : flushTime(0), + updateTime(0), + userU(), + userUPresent(false), + userD(), + userDPresent(false), + dirU(DIR_NUM), + dirD(DIR_NUM), + lenU(0), + lenD(0) +{}; + +PACKET_EXTRA_DATA(const PACKET_EXTRA_DATA & pp) + : flushTime(pp.flushTime), + updateTime(pp.updateTime), + userU(pp.userU), + userUPresent(pp.userUPresent), + userD(pp.userD), + userDPresent(pp.userDPresent), + dirU(pp.dirU), + dirD(pp.dirD), + lenU(pp.lenU), + lenD(pp.lenD) +{}; + +time_t flushTime; // Last flush time +time_t updateTime; // Last update time +user_iter userU; // Uploader +bool userUPresent; // Uploader is registered user +user_iter userD; // Downloader +bool userDPresent; // Downloader is registered user +int dirU; // Upload direction +int dirD; // Download direction +uint32_t lenU; // Upload length +uint32_t lenD; // Download length +}; +//----------------------------------------------------------------------------- +class TRAFFCOUNTER; +//----------------------------------------------------------------------------- +class TRF_IP_BEFORE: public PROPERTY_NOTIFIER_BASE +{ +public: + TRF_IP_BEFORE(TRAFFCOUNTER & t, user_iter u) + : PROPERTY_NOTIFIER_BASE(), + traffCnt(t), + user(u) + {}; + void Notify(const uint32_t & oldValue, const uint32_t & newValue); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() { return user; } + +private: + TRAFFCOUNTER & traffCnt; + user_iter user; +}; +//----------------------------------------------------------------------------- +class TRF_IP_AFTER: public PROPERTY_NOTIFIER_BASE +{ +public: + TRF_IP_AFTER(TRAFFCOUNTER & t, user_iter u) + : PROPERTY_NOTIFIER_BASE(), + traffCnt(t), + user(u) + {}; + void Notify(const uint32_t & oldValue, const uint32_t & newValue); + void SetUser(user_iter u) { user = u; } + user_iter GetUser() { return user; } +private: + TRAFFCOUNTER & traffCnt; + user_iter user; +}; +//----------------------------------------------------------------------------- +class ADD_USER_NONIFIER: public NOTIFIER_BASE +{ +public: + ADD_USER_NONIFIER(TRAFFCOUNTER & t) : + NOTIFIER_BASE(), + traffCnt(t) {}; + virtual ~ADD_USER_NONIFIER(){}; + void Notify(const user_iter & user); +private: + TRAFFCOUNTER & traffCnt; +}; +//----------------------------------------------------------------------------- +class DEL_USER_NONIFIER: public NOTIFIER_BASE +{ +public: + DEL_USER_NONIFIER(TRAFFCOUNTER & t) : + NOTIFIER_BASE(), + traffCnt(t) {}; + virtual ~DEL_USER_NONIFIER(){}; + void Notify(const user_iter & user); +private: + TRAFFCOUNTER & traffCnt; +}; +//----------------------------------------------------------------------------- +class TRAFFCOUNTER : private NONCOPYABLE +{ +friend class ADD_USER_NONIFIER; +friend class DEL_USER_NONIFIER; +friend class TRF_IP_BEFORE; +friend class TRF_IP_AFTER; +public: + TRAFFCOUNTER(USERS * users, const TARIFFS * tariffs, const std::string & rulesFileName); + ~TRAFFCOUNTER(); + + void SetRulesFile(const std::string & rulesFileName); + + int Reload(); + int Start(); + int Stop(); + + void Process(const RAW_PACKET & rawPacket); + void SetMonitorDir(const std::string & monitorDir); + +private: + bool ParseAddress(const char * ta, RULE * rule) const; + uint32_t CalcMask(uint32_t msk) const; + void FreeRules(); + void PrintRule(RULE rule) const; + bool ReadRules(bool test = false); + + static void * Run(void * data); + + void DeterminateDir(const RAW_PACKET & packet, + int * dirU, // Direction for upload + int * dirD) const; // Direction for download + + void FlushAndRemove(); + + void AddUser(user_iter user); + void DelUser(uint32_t uip); + void SetUserNotifiers(user_iter user); + void UnSetUserNotifiers(user_iter user); + + std::list rules; + typedef std::list::iterator rule_iter; + + std::map packets; // Packets tree + typedef std::map::iterator pp_iter; + + std::multimap ip2packets; // IP-to-Packet index + + typedef std::multimap::iterator ip2p_iter; + typedef std::multimap::const_iterator ip2p_citer; + + std::string dirName[DIR_NUM + 1]; + + STG_LOGGER & WriteServLog; + std::string rulesFileName; + + std::string monitorDir; + bool monitoring; + + USERS * users; + + bool running; + bool stopped; + pthread_mutex_t mutex; + pthread_t thread; + + std::list ipBeforeNotifiers; + std::list ipAfterNotifiers; + + ADD_USER_NONIFIER addUserNotifier; + DEL_USER_NONIFIER delUserNotifier; +}; +//----------------------------------------------------------------------------- +inline +void TRF_IP_BEFORE::Notify(const uint32_t & oldValue, const uint32_t &) +{ +// User changes his address. Remove old IP +if (!oldValue) + return; + +EVENT_LOOP_SINGLETON::GetInstance().Enqueue(traffCnt, &TRAFFCOUNTER::DelUser, oldValue); +} +//----------------------------------------------------------------------------- +inline +void TRF_IP_AFTER::Notify(const uint32_t &, const uint32_t & newValue) +{ +// User changes his address. Add new IP +if (!newValue) + return; + +EVENT_LOOP_SINGLETON::GetInstance().Enqueue(traffCnt, &TRAFFCOUNTER::AddUser, user); +} +//----------------------------------------------------------------------------- +inline +void ADD_USER_NONIFIER::Notify(const user_iter & user) +{ +EVENT_LOOP_SINGLETON::GetInstance().Enqueue(traffCnt, &TRAFFCOUNTER::SetUserNotifiers, user); +} +//----------------------------------------------------------------------------- +inline +void DEL_USER_NONIFIER::Notify(const user_iter & user) +{ +EVENT_LOOP_SINGLETON::GetInstance().Enqueue(traffCnt, &TRAFFCOUNTER::UnSetUserNotifiers, user); +EVENT_LOOP_SINGLETON::GetInstance().Enqueue(traffCnt, &TRAFFCOUNTER::DelUser, user->GetCurrIP()); +} +//----------------------------------------------------------------------------- +#endif //TRAFFCOUNTER_H diff --git a/projects/stargazer/user.cpp b/projects/stargazer/user.cpp new file mode 100644 index 00000000..6c328602 --- /dev/null +++ b/projects/stargazer/user.cpp @@ -0,0 +1,1376 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.101 $ + $Date: 2010/11/03 10:50:03 $ + $Author: faust $ + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include // access + +#include + +#include "user.h" +#include "common.h" +#include "settings.h" +#include "script_executer.h" +#include "tariff.h" +#include "tariffs.h" +#include "admin.h" + +USER::USER(const SETTINGS * s, + const BASE_STORE * st, + const TARIFFS * t, + const ADMIN & a, + const map * ipIdx) + : property(s), + WriteServLog(GetStgLogger()), + login(), + id(0), + __connected(0), + connected(__connected), + userIDGenerator(), + __currIP(0), + currIP(__currIP), + lastIPForDisconnect(0), + pingTime(0), + sysAdmin(a), + store(st), + tariffs(t), + tariff(tariffs->GetNoTariff()), + cash(property.cash), + up(property.up), + down(property.down), + lastCashAdd(property.lastCashAdd), + passiveTime(property.passiveTime), + lastCashAddTime(property.lastCashAddTime), + freeMb(property.freeMb), + lastActivityTime(property.lastActivityTime), + password(property.password), + passive(property.passive), + disabled(property.disabled), + disabledDetailStat(property.disabledDetailStat), + alwaysOnline(property.alwaysOnline), + tariffName(property.tariffName), + nextTariff(property.nextTariff), + address(property.address), + note(property.note), + group(property.group), + email(property.email), + phone(property.phone), + realName(property.realName), + credit(property.credit), + creditExpire(property.creditExpire), + ips(property.ips), + userdata0(property.userdata0), + userdata1(property.userdata1), + userdata2(property.userdata2), + userdata3(property.userdata3), + userdata4(property.userdata4), + userdata5(property.userdata5), + userdata6(property.userdata6), + userdata7(property.userdata7), + userdata8(property.userdata8), + userdata9(property.userdata9), + passiveNotifier(this), + tariffNotifier(this), + cashNotifier(this), + ipNotifier(this) +{ +settings = s; +ipIndex = ipIdx; + +password = "*_EMPTY_PASSWORD_*"; +tariffName = NO_TARIFF_NAME; +connected = 0; +traffStatInUse = 0; +traffStat = &traffStatInternal[0]; +tariff = tariffs->GetNoTariff(); +ips = StrToIPS("*"); +deleted = false; +lastWriteStat = stgTime + random() % settings->GetStatWritePeriod(); +lastWriteDeatiledStat = stgTime; +lastSwapDeatiledStat = stgTime; + +property.tariffName.AddBeforeNotifier(&tariffNotifier); +property.passive.AddBeforeNotifier(&passiveNotifier); +property.cash.AddBeforeNotifier(&cashNotifier); +currIP.AddAfterNotifier(&ipNotifier); + +lastScanMessages = 0; + +writeFreeMbTraffCost = settings->GetWriteFreeMbTraffCost(); + +pthread_mutexattr_t attr; +pthread_mutexattr_init(&attr); +pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +pthread_mutex_init(&mutex, &attr); +} +//----------------------------------------------------------------------------- +USER::USER(const USER & u) + : property(u.settings), + WriteServLog(GetStgLogger()), + login(u.login), + id(u.id), + __connected(u.__connected), + connected(__connected), + __currIP(u.__currIP), + currIP(__currIP), + lastIPForDisconnect(0), + pingTime(u.pingTime), + sysAdmin(u.sysAdmin), + store(u.store), + tariffs(u.tariffs), + tariff(u.tariff), + cash(property.cash), + up(property.up), + down(property.down), + lastCashAdd(property.lastCashAdd), + passiveTime(property.passiveTime), + lastCashAddTime(property.lastCashAddTime), + freeMb(property.freeMb), + lastActivityTime(property.lastActivityTime), + password(property.password), + passive(property.passive), + disabled(property.disabled), + disabledDetailStat(property.disabledDetailStat), + alwaysOnline(property.alwaysOnline), + tariffName(property.tariffName), + nextTariff(property.nextTariff), + address(property.address), + note(property.note), + group(property.group), + email(property.email), + phone(property.phone), + realName(property.realName), + credit(property.credit), + creditExpire(property.creditExpire), + ips(property.ips), + userdata0(property.userdata0), + userdata1(property.userdata1), + userdata2(property.userdata2), + userdata3(property.userdata3), + userdata4(property.userdata4), + userdata5(property.userdata5), + userdata6(property.userdata6), + userdata7(property.userdata7), + userdata8(property.userdata8), + userdata9(property.userdata9), + passiveNotifier(this), + tariffNotifier(this), + cashNotifier(this), + ipNotifier(this) +{ +if (&u == this) + return; + +connected = 0; +traffStatInUse = 0; + +ipIndex = u.ipIndex; + +deleted = u.deleted; +traffStat = &traffStatInternal[traffStatInUse % 2]; +traffStatToWrite = &traffStatInternal[(traffStatInUse +1) % 2]; + +lastWriteStat = u.lastWriteStat; +lastWriteDeatiledStat = u.lastWriteDeatiledStat; +lastSwapDeatiledStat = u.lastSwapDeatiledStat; + +settings = u.settings; + +property.tariffName.AddBeforeNotifier(&tariffNotifier); +property.passive.AddBeforeNotifier(&passiveNotifier); +property.cash.AddBeforeNotifier(&cashNotifier); +currIP.AddAfterNotifier(&ipNotifier); + +lastScanMessages = 0; + +writeFreeMbTraffCost = settings->GetWriteFreeMbTraffCost(); + +pthread_mutexattr_t attr; +pthread_mutexattr_init(&attr); +pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +pthread_mutex_init(&mutex, &attr); +} +//----------------------------------------------------------------------------- +USER::~USER() +{ +property.passive.DelBeforeNotifier(&passiveNotifier); +property.tariffName.DelBeforeNotifier(&tariffNotifier); +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +void USER::SetLogin(string const & l) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +assert(login.empty() && "Login is already set"); +login = l; +id = userIDGenerator.GetNextID(); +} +//----------------------------------------------------------------------------- +int USER::ReadConf() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +USER_CONF uc; + +if (store->RestoreUserConf(&uc, login)) + { + WriteServLog("Cannot read conf for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot read conf for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +password = uc.password; +passive = uc.passive; +disabled = uc.disabled; +disabledDetailStat = uc.disabledDetailStat; +alwaysOnline = uc.alwaysOnline; +tariffName = uc.tariffName; +address = uc.address; +phone = uc.phone; +email = uc.email; +note = uc.note; +realName = uc.realName; +group = uc.group; +credit = uc.credit; +nextTariff = uc.nextTariff; +userdata0 = uc.userdata[0]; +userdata1 = uc.userdata[1]; +userdata2 = uc.userdata[2]; +userdata3 = uc.userdata[3]; +userdata4 = uc.userdata[4]; +userdata5 = uc.userdata[5]; +userdata6 = uc.userdata[6]; +userdata7 = uc.userdata[7]; +userdata8 = uc.userdata[8]; +userdata9 = uc.userdata[9]; + +creditExpire = uc.creditExpire; +ips = uc.ips; + +tariff = tariffs->FindByName(tariffName); +if (tariff == NULL) + { + WriteServLog("Cannot read user %s. Tariff %s not exist.", + login.c_str(), property.tariffName.Get().c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int USER::ReadStat() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +USER_STAT us; + +if (store->RestoreUserStat(&us, login)) + { + WriteServLog("Cannot read stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot read stat for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +up = us.up; +down = us.down; +cash = us.cash; +freeMb = us.freeMb; +lastCashAdd = us.lastCashAdd; +lastCashAddTime = us.lastCashAddTime; +passiveTime = us.passiveTime; +lastActivityTime = us.lastActivityTime; + +return 0; +} +//----------------------------------------------------------------------------- +int USER::WriteConf() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +USER_CONF uc; + +uc.password = password; +uc.passive = passive; +uc.disabled = disabled; +uc.disabledDetailStat = disabledDetailStat; +uc.alwaysOnline = alwaysOnline; +uc.tariffName = tariffName; +uc.address = address; +uc.phone = phone; +uc.email = email; +uc.note = note; +uc.realName = realName; +uc.group = group; +uc.credit = credit; +uc.nextTariff = nextTariff; +uc.userdata[0] = userdata0; +uc.userdata[1] = userdata1; +uc.userdata[2] = userdata2; +uc.userdata[3] = userdata3; +uc.userdata[4] = userdata4; +uc.userdata[5] = userdata5; +uc.userdata[6] = userdata6; +uc.userdata[7] = userdata7; +uc.userdata[8] = userdata8; +uc.userdata[9] = userdata9; + +uc.creditExpire = creditExpire; +uc.ips = ips; + +printfd(__FILE__, "USER::WriteConf()\n"); + +if (store->SaveUserConf(uc, login)) + { + WriteServLog("Cannot write conf for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot write conf for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int USER::WriteStat() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +USER_STAT us; + +us.up = up; +us.down = down; +us.cash = cash; +us.freeMb = freeMb; +us.lastCashAdd = lastCashAdd; +us.lastCashAddTime = lastCashAddTime; +us.passiveTime = passiveTime; +us.lastActivityTime = lastActivityTime; + +printfd(__FILE__, "USER::WriteStat()\n"); + +if (store->SaveUserStat(us, login)) + { + WriteServLog("Cannot write stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot write stat for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +lastWriteStat = stgTime; + +return 0; +} +//----------------------------------------------------------------------------- +int USER::WriteMonthStat() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +USER_STAT us; +struct tm * t1; +time_t tt = stgTime - 3600; +t1 = localtime(&tt); + +us.up = up; +us.down = down; +us.cash = cash; +us.freeMb = freeMb; +us.lastCashAdd = lastCashAdd; +us.lastCashAddTime = lastCashAddTime; +us.passiveTime = passiveTime; +us.lastActivityTime = lastActivityTime; + +if (store->SaveMonthStat(us, t1->tm_mon, t1->tm_year, login)) + { + WriteServLog("Cannot write month stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot write month stat for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int USER::Authorize(uint32_t ip, const string &, uint32_t dirs, const BASE_AUTH * auth) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +/* + * Authorize user. It only means that user will be authorized. Nothing more. + * User can be connected or disconnected while authorized. + * Example: user is authorized but disconnected due to 0 money or blocking + */ + +/* + * Prevent double authorization by identical authorizers + */ +if (authorizedBy.find(auth) != authorizedBy.end()) + return 0; + +if (!ip) + return -1; + +for (int i = 0; i < DIR_NUM; i++) + { + enabledDirs[i] = dirs & (1 << i); + } + +if (authorizedBy.size()) + { + if (currIP != ip) + { + // We are already authorized, but with different IP address + errorStr = "User " + login + " alredy authorized with IP address " + inet_ntostring(ip); + return -1; + } + + map::const_iterator ci = ipIndex->find(ip); + if (ci != ipIndex->end()) + { + // Address is already present in IP-index + // If it's not our IP - throw an error + if (&(*ci->second) != this) + { + errorStr = "IP address " + inet_ntostring(ip) + " alredy in use"; + return -1; + } + } + } +else + { + if (ipIndex->find(ip) != ipIndex->end()) + { + // Address is already present in IP-index + errorStr = "IP address " + inet_ntostring(ip) + " alredy in use"; + return -1; + } + + if (ips.ConstData().IsIPInIPS(ip)) + { + currIP = ip; + lastIPForDisconnect = currIP; + } + else + { + printfd(__FILE__, " user %s: ips = %s\n", login.c_str(), ips.ConstData().GetIpStr().c_str()); + errorStr = "IP address " + inet_ntostring(ip) + " not belong user " + login; + return -1; + } + } + +authorizedBy.insert(auth); + +ScanMessage(); + +return 0; +} +//----------------------------------------------------------------------------- +void USER::Unauthorize(const BASE_AUTH * auth) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +/* + * Authorizer tries to unauthorize user, that was not authorized by it + */ +if (!authorizedBy.erase(auth)) + return; + +if (authorizedBy.empty()) + { + lastIPForDisconnect = currIP; + currIP = 0; // DelUser in traffcounter + return; + } +} +//----------------------------------------------------------------------------- +bool USER::IsAuthorizedBy(const BASE_AUTH * auth) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +// Is this user authorized by specified authorizer? +return authorizedBy.find(auth) != authorizedBy.end(); +} +//----------------------------------------------------------------------------- +void USER::Connect(bool fakeConnect) +{ +/* + * Connect user to Internet. This function is differ from Authorize() !!! + */ + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (!fakeConnect) + { + string scriptOnConnect = settings->GetScriptDir() + "/OnConnect"; + + if (access(scriptOnConnect.c_str(), X_OK) == 0) + { + char dirsStr[DIR_NUM + 1]; + dirsStr[DIR_NUM] = 0; + for (int i = 0; i < DIR_NUM; i++) + { + dirsStr[i] = enabledDirs[i] ? '1' : '0'; + } + + string scriptOnConnectParams; + strprintf(&scriptOnConnectParams, + "%s \"%s\" \"%s\" \"%f\" \"%d\" \"%s\"", + scriptOnConnect.c_str(), + login.c_str(), + inet_ntostring(currIP).c_str(), + (double)cash, + id, + dirsStr); + + ScriptExec(scriptOnConnectParams); + } + else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnConnect.c_str()); + } + + connected = true; + } + +if (store->WriteUserConnect(login, currIP)) + { + WriteServLog("Cannot write connect for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + } + +if (!fakeConnect) + lastIPForDisconnect = currIP; + +//printfd(__FILE__, "Connect. user name \'%s\' ip=%s\n", login.c_str(), inet_ntostring(currIP).c_str()); +/*if (settings->GetLogUserConnectDisconnect()) + WriteServLog("User \'%s\', %s: Connect.", login.c_str(), inet_ntostring(currIP).c_str());*/ +} +//----------------------------------------------------------------------------- +void USER::Disconnect(bool fakeDisconnect, const std::string & reason) +{ +/* + * Disconnect user from Internet. This function is differ from UnAuthorize() !!! + */ + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (!lastIPForDisconnect) + { + printfd(__FILE__, "lastIPForDisconnect\n"); + return; + } + +if (!fakeDisconnect) + { + string scriptOnDisonnect = settings->GetScriptDir() + "/OnDisconnect"; + + if (access(scriptOnDisonnect.c_str(), X_OK) == 0) + { + char dirsStr[DIR_NUM + 1]; + dirsStr[DIR_NUM] = 0; + for (int i = 0; i < DIR_NUM; i++) + { + dirsStr[i] = enabledDirs[i] ? '1' : '0'; + } + + string scriptOnDisonnectParams; + strprintf(&scriptOnDisonnectParams, + "%s \"%s\" \"%s\" \"%f\" \"%d\" \"%s\"", + scriptOnDisonnect.c_str(), + login.c_str(), + inet_ntostring(lastIPForDisconnect).c_str(), + (double)cash, + id, + dirsStr); + + ScriptExec(scriptOnDisonnectParams); + } + else + { + WriteServLog("Script OnDisconnect cannot be executed. File not found."); + } + + connected = false; + } + +if (store->WriteUserDisconnect(login, up, down, sessionUpload, sessionDownload, cash, freeMb, reason)) + { + WriteServLog("Cannot write disconnect for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + } + +//printfd(__FILE__, "Disconnect. User name \'%s\' ip=%s reason: '%s'\n", login.c_str(), inet_ntostring(lastIPForDisconnect).c_str(), reason.c_str()); +/*if (settings->GetLogUserConnectDisconnect()) + WriteServLog("User \'%s\', %s: Disconnect.", login.c_str(), inet_ntostring(lastIPForDisconnect).c_str());*/ + +if (!fakeDisconnect) + lastIPForDisconnect = 0; + +DIR_TRAFF zeroSesssion; + +sessionUpload = zeroSesssion; +sessionDownload = zeroSesssion; +} +//----------------------------------------------------------------------------- +void USER::PrintUser() const +{ +//return; +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +cout << "============================================================" << endl; +cout << "id=" << id << endl; +cout << "login=" << login << endl; +cout << "password=" << password << endl; +cout << "passive=" << passive << endl; +cout << "disabled=" << disabled << endl; +cout << "disabledDetailStat=" << disabledDetailStat << endl; +cout << "alwaysOnline=" << alwaysOnline << endl; +cout << "tariffName=" << tariffName << endl; +cout << "address=" << address << endl; +cout << "phone=" << phone << endl; +cout << "email=" << email << endl; +cout << "note=" << note << endl; +cout << "realName=" < settings->GetStatWritePeriod()) + { + printfd(__FILE__, "USER::WriteStat user=%s\n", GetLogin().c_str()); + WriteStat(); + } +if (creditExpire.ConstData() && creditExpire.ConstData() < stgTime) + { + WriteServLog("User: %s. Credit expired.", login.c_str()); + credit = 0; + creditExpire = 0; + WriteConf(); + } + +if (passive.ConstData() + && (stgTime % 30 == 0) + && (passiveTime.ModificationTime() != stgTime)) + { + passiveTime = passiveTime + (stgTime - passiveTime.ModificationTime()); + printfd(__FILE__, "===== %s: passiveTime=%d =====\n", login.c_str(), passiveTime.ConstData()); + } + +if (!authorizedBy.empty()) + { + if (connected) + { + lastActivityTime = *const_cast(&stgTime); + } + if (!connected && IsInetable()) + { + Connect(); + } + if (connected && !IsInetable()) + { + if (disabled) + Disconnect(false, "disabled"); + else if (passive) + Disconnect(false, "passive"); + else + Disconnect(false, "no cash"); + } + + if (stgTime - lastScanMessages > 10) + { + ScanMessage(); + lastScanMessages = stgTime; + } + } +else + { + if (connected) + { + Disconnect(false, "not authorized"); + } + } + +} +//----------------------------------------------------------------------------- +void USER::UpdatePingTime(time_t t) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +//printfd(__FILE__, "UpdatePingTime(%d) %s\n", t, login.c_str()); +if (t) + pingTime = t; +else + pingTime = stgTime; +} +//----------------------------------------------------------------------------- +bool USER::IsInetable() +{ +//STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (disabled || passive) + return false; + +if (settings->GetFreeMbAllowInet()) + { + if (freeMb >= 0) + return true; + } + +if (settings->GetShowFeeInCash()) + { + return (cash >= -credit); + } + +return (cash - tariff->GetFee() >= -credit); +} +//----------------------------------------------------------------------------- +string USER::GetEnabledDirs() +{ +//STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +string dirs = ""; +for(int i = 0; i < DIR_NUM; i++) + dirs += enabledDirs[i] ? "1" : "0"; +return dirs; +} +//----------------------------------------------------------------------------- +#ifdef TRAFF_STAT_WITH_PORTS +void USER::AddTraffStatU(int dir, uint32_t ip, uint16_t port, uint32_t len) +#else +void USER::AddTraffStatU(int dir, uint32_t ip, uint32_t len) +#endif +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (!connected) + return; + +double cost = 0; +DIR_TRAFF dt(up); + +int64_t traff = tariff->GetTraffByType(up.ConstData()[dir], down.ConstData()[dir]); +int64_t threshold = tariff->GetThreshold(dir) * 1024 * 1024; + +dt[dir] += len; + +int tt = tariff->GetTraffType(); +if (tt == TRAFF_UP || + tt == TRAFF_UP_DOWN || + // Check NEW traff data + (tt == TRAFF_MAX && dt[dir] > down.ConstData()[dir])) + { + double dc = 0; + if (traff < threshold && + traff + len >= threshold) + { + // cash = partBeforeThreshold * priceBeforeThreshold + + // partAfterThreshold * priceAfterThreshold + int64_t before = threshold - traff; // Chunk part before threshold + int64_t after = len - before; // Chunk part after threshold + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], // Traff before chunk + down.ConstData()[dir], + dir, + stgTime) * before + + tariff->GetPriceWithTraffType(dt[dir], // Traff after chunk + down.ConstData()[dir], + dir, + stgTime) * after; + } + else + { + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], + down.ConstData()[dir], + dir, + stgTime) * len; + } + + if (freeMb.ConstData() <= 0) // FreeMb is exhausted + cost = dc; + else if (freeMb.ConstData() < dc) // FreeMb is partially exhausted + cost = dc - freeMb.ConstData(); + + // Direct access to internal data structures via friend-specifier + property.stat.freeMb -= dc; + property.stat.cash -= cost; + cash.ModifyTime(); + freeMb.ModifyTime(); + } + +up = dt; +sessionUpload[dir] += len; + +//Add detailed stat + +if (!writeFreeMbTraffCost && freeMb.ConstData() >= 0) + cost = 0; + +#ifdef TRAFF_STAT_WITH_PORTS +IP_DIR_PAIR idp(ip, dir, port); +#else +IP_DIR_PAIR idp(ip, dir); +#endif + +map::iterator lb; +lb = traffStat->lower_bound(idp); +if (lb == traffStat->end()) + { + traffStat->insert(lb, + pair(idp, + STAT_NODE(len, 0, cost))); + } +else + if (lb->first.dir == dir && lb->first.ip == ip) + { + lb->second.cash += cost; + lb->second.up += len; + } + else + { + traffStat->insert(lb, + pair(idp, + STAT_NODE(len, 0, cost))); + } +} +//----------------------------------------------------------------------------- +#ifdef TRAFF_STAT_WITH_PORTS +void USER::AddTraffStatD(int dir, uint32_t ip, uint16_t port, uint32_t len) +#else +void USER::AddTraffStatD(int dir, uint32_t ip, uint32_t len) +#endif +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (!connected) + return; + +double cost = 0; +DIR_TRAFF dt(down); + +int64_t traff = tariff->GetTraffByType(up.ConstData()[dir], down.ConstData()[dir]); +int64_t threshold = tariff->GetThreshold(dir) * 1024 * 1024; + +dt[dir] += len; + +int tt = tariff->GetTraffType(); +if (tt == TRAFF_DOWN || + tt == TRAFF_UP_DOWN || + // Check NEW traff data + (tt == TRAFF_MAX && up.ConstData()[dir] <= dt[dir])) + { + double dc = 0; + if (traff < threshold && + traff + len >= threshold) + { + // cash = partBeforeThreshold * priceBeforeThreshold + + // partAfterThreshold * priceAfterThreshold + int64_t before = threshold - traff; // Chunk part before threshold + int64_t after = len - before; // Chunk part after threshold + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], + down.ConstData()[dir], // Traff before chunk + dir, + stgTime) * before + + tariff->GetPriceWithTraffType(up.ConstData()[dir], + dt[dir], // Traff after chunk + dir, + stgTime) * after; + } + else + { + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], + down.ConstData()[dir], + dir, + stgTime) * len; + } + + if (freeMb.ConstData() <= 0) // FreeMb is exhausted + cost = dc; + else if (freeMb.ConstData() < dc) // FreeMb is partially exhausted + cost = dc - freeMb.ConstData(); + + property.stat.freeMb -= dc; + property.stat.cash -= cost; + cash.ModifyTime(); + freeMb.ModifyTime(); + } + +down = dt; +sessionDownload[dir] += len; + +//Add detailed stat + +if (!writeFreeMbTraffCost && freeMb.ConstData() >= 0) + cost = 0; + +#ifdef TRAFF_STAT_WITH_PORTS +IP_DIR_PAIR idp(ip, dir, port); +#else +IP_DIR_PAIR idp(ip, dir); +#endif + +map::iterator lb; +lb = traffStat->lower_bound(idp); +if (lb == traffStat->end()) + { + traffStat->insert(lb, + pair(idp, + STAT_NODE(0, len, cost))); + } +else + if (lb->first.dir == dir && lb->first.ip == ip) + { + lb->second.cash += cost; + lb->second.down += len; + } + else + { + traffStat->insert(lb, + pair(idp, + STAT_NODE(0, len, cost))); + } +} +//----------------------------------------------------------------------------- +void USER::AddCurrIPBeforeNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +currIP.AddBeforeNotifier(n); +} +//----------------------------------------------------------------------------- +void USER::DelCurrIPBeforeNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +currIP.DelBeforeNotifier(n); +} +//----------------------------------------------------------------------------- +void USER::AddCurrIPAfterNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +currIP.AddAfterNotifier(n); +} +//----------------------------------------------------------------------------- +void USER::DelCurrIPAfterNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +currIP.DelAfterNotifier(n); +} +//----------------------------------------------------------------------------- +void USER::OnAdd() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +string scriptOnAdd = settings->GetScriptDir() + "/OnUserAdd"; + +if (access(scriptOnAdd.c_str(), X_OK) == 0) + { + string scriptOnAddParams; + strprintf(&scriptOnAddParams, + "%s \"%s\"", + scriptOnAdd.c_str(), + login.c_str()); + + ScriptExec(scriptOnAddParams); + } +else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnAdd.c_str()); + } +} +//----------------------------------------------------------------------------- +void USER::OnDelete() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +string scriptOnDel = settings->GetScriptDir() + "/OnUserDel"; + +if (access(scriptOnDel.c_str(), X_OK) == 0) + { + string scriptOnDelParams; + strprintf(&scriptOnDelParams, + "%s \"%s\"", + scriptOnDel.c_str(), + login.c_str()); + + ScriptExec(scriptOnDelParams); + } +else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnDel.c_str()); + } + +Run(); +} +//----------------------------------------------------------------------------- +void USER::ResetDetailStat() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +traffStatToWrite->erase(traffStatToWrite->begin(), traffStatToWrite->end()); +} +//----------------------------------------------------------------------------- +int USER::WriteDetailStat() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +printfd(__FILE__, "USER::WriteDetailedStat(): size = %d\n", traffStatToWrite->size()); + +if (traffStatToWrite->size() && !disabledDetailStat) + { + if (store->WriteDetailedStat(traffStatToWrite, lastWriteDeatiledStat, login)) + { + WriteServLog("Cannot write detail stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + } + } +lastWriteDeatiledStat = lastSwapDeatiledStat; +return 0; +} +//----------------------------------------------------------------------------- +int USER::SwapDetailStat() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +lastSwapDeatiledStat = stgTime; +traffStatToWrite = &traffStatInternal[traffStatInUse % 2]; +traffStat = &traffStatInternal[++traffStatInUse % 2]; + +return 0; +} +//----------------------------------------------------------------------------- +double USER::GetPassiveTimePart() const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +static int daysInMonth[12] = +{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +struct tm * tms; +time_t t = stgTime; +tms = localtime(&t); + +time_t secMonth = daysInMonth[(tms->tm_mon + 11) % 12] * 24 * 3600; // Previous month + +if (tms->tm_year % 4 == 0 && tms->tm_mon == 1) + { + // Leap year + secMonth += 24 * 3600; + } + +int dt = secMonth - passiveTime; + +if (dt < 0) + dt = 0; + +return double(dt) / (secMonth); +} +//----------------------------------------------------------------------------- +void USER::SetPassiveTimeAsNewUser() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +time_t t; +struct tm * tm; +t = stgTime; +tm = localtime(&t); +int daysCurrMon = DaysInCurrentMonth(); +double pt = (tm->tm_mday - 1) / (double)daysCurrMon; + +passiveTime = (time_t)(pt * 24 * 3600 * daysCurrMon); +} +//----------------------------------------------------------------------------- +void USER::MidnightResetSessionStat() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (connected) + { + Disconnect(true, "fake"); + Connect(true); + } +} +//----------------------------------------------------------------------------- +void USER::ProcessNewMonth() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +// Reset traff +if (connected) + { + Disconnect(true, "fake"); + } +DIR_TRAFF zeroTarff; + +WriteMonthStat(); + +up = zeroTarff; +down = zeroTarff; + +if (connected) + { + Connect(true); + } + +// Set new tariff +if (nextTariff.ConstData() != "") + { + const TARIFF * nt; + nt = tariffs->FindByName(nextTariff); + if (nt == NULL) + { + WriteServLog("Cannot change tariff for user %s. Tariff %s not exist.", + login.c_str(), property.tariffName.Get().c_str()); + } + else + { + property.tariffName.Set(nextTariff, sysAdmin, login, store); + tariff = nt; + } + ResetNextTariff(); + WriteConf(); + } +} +//----------------------------------------------------------------------------- +void USER::ProcessDayFeeSpread() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (passive.ConstData()) + return; + +double f = tariff->GetFee() / DaysInCurrentMonth(); + +if (f == 0.0) + return; + +double c = cash; +property.cash.Set(c - f, sysAdmin, login, store, "Subscriber fee charge"); +ResetPassiveTime(); +} +//----------------------------------------------------------------------------- +void USER::ProcessDayFee() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +double passiveTimePart = 1.0; +if (!settings->GetFullFee()) + { + passiveTimePart = GetPassiveTimePart(); + } +else + { + if (passive.ConstData()) + { + printfd(__FILE__, "Don't charge fee `cause we are passive\n"); + return; + } + } +double f = tariff->GetFee() * passiveTimePart; + +ResetPassiveTime(); + +if (f == 0.0) + return; + +double c = cash; +printfd(__FILE__, "login: %8s Fee=%f PassiveTimePart=%f fee=%f\n", + login.c_str(), + tariff->GetFee(), + passiveTimePart, + f); +property.cash.Set(c - f, sysAdmin, login, store, "Subscriber fee charge"); +} +//----------------------------------------------------------------------------- +void USER::SetPrepaidTraff() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +property.freeMb.Set(tariff->GetFree(), sysAdmin, login, store, "Prepaid traffic"); +} +//----------------------------------------------------------------------------- +int USER::AddMessage(STG_MSG * msg) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (SendMessage(*msg) == 0) + { + if (msg->header.repeat > 0) + { + msg->header.repeat--; + #ifndef DEBUG + //TODO: gcc v. 4.x generate ICE on x86_64 + msg->header.lastSendTime = time(NULL); + #else + msg->header.lastSendTime = stgTime; + #endif + if (store->AddMessage(msg, login)) + { + errorStr = store->GetStrError(); + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog("Error adding message %s", errorStr.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + return -1; + } + } + } +else + { + if (store->AddMessage(msg, login)) + { + errorStr = store->GetStrError(); + STG_LOGGER & WriteServLog = GetStgLogger(); + WriteServLog("Error adding message %s", errorStr.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + return -1; + } + } +return 0; +} +//----------------------------------------------------------------------------- +int USER::SendMessage(const STG_MSG & msg) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (authorizedBy.empty()) + { + return -1; + } + +int ret = -1; +set::iterator it; + +it = authorizedBy.begin(); +while (it != authorizedBy.end()) + { + if ((*it)->SendMessage(msg, currIP) == 0) + ret = 0; + ++it; + } +return ret; +} +//----------------------------------------------------------------------------- +int USER::ScanMessage() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +vector hdrsList; + +if (store->GetMessageHdrs(&hdrsList, login)) + { + printfd(__FILE__, "Error GetMessageHdrs %s\n", store->GetStrError().c_str()); + return -1; + } + +for (unsigned i = 0; i < hdrsList.size(); i++) + { + + if (hdrsList[i].lastSendTime + hdrsList[i].repeatPeriod * 60 < (unsigned)stgTime) + { + STG_MSG msg; + if (store->GetMessage(hdrsList[i].id, &msg, login) == 0) + { + if (SendMessage(msg) == 0) + { + msg.header.repeat--; + if (msg.header.repeat < 0) + { + printfd(__FILE__, "DelMessage\n"); + store->DelMessage(hdrsList[i].id, login); + } + else + { + #ifndef DEBUG + //TODO: gcc v. 4.x generate ICE on x86_64 + msg.header.lastSendTime = time(NULL); + #else + msg.header.lastSendTime = stgTime; + #endif + if (store->EditMessage(msg, login)) + { + printfd(__FILE__, "EditMessage Error %s\n", store->GetStrError().c_str()); + } + } + } + } + else + { + WriteServLog("Cannot get message for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + } + } + } +return 0; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHG_PASSIVE_NOTIFIER::Notify(const int & oldPassive, const int & newPassive) +{ +if (newPassive && !oldPassive) + user->property.cash.Set(user->cash - user->tariff->GetPassiveCost(), + user->sysAdmin, + user->login, + user->store, + "Freeze"); +} +//----------------------------------------------------------------------------- +void CHG_TARIFF_NOTIFIER::Notify(const string &, const string & newTariff) +{ +user->tariff = user->tariffs->FindByName(newTariff); +} +//----------------------------------------------------------------------------- +void CHG_CASH_NOTIFIER::Notify(const double & oldCash, const double & newCash) +{ +user->lastCashAddTime = *const_cast(&stgTime); +user->lastCashAdd = newCash - oldCash; +} +//----------------------------------------------------------------------------- +void CHG_IP_NOTIFIER::Notify(const uint32_t & from, const uint32_t & to) +{ + printfd(__FILE__, "Change IP from %s to %s\n", inet_ntostring(from).c_str(), inet_ntostring(to).c_str()); + if (from != 0) + user->Disconnect(false, "Change IP"); + if (to != 0) + user->Connect(false); +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/user.h b/projects/stargazer/user.h new file mode 100644 index 00000000..7a9896bf --- /dev/null +++ b/projects/stargazer/user.h @@ -0,0 +1,309 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.48 $ + $Date: 2010/11/03 10:50:03 $ + $Author: faust $ + */ + +#ifndef USER_H +#define USER_H + +#include +#include +#include +#include +#include + +#include "os_int.h" +#include "stg_const.h" +#include "user_stat.h" +#include "user_conf.h" +#include "user_ips.h" +#include "user_property.h" +#include "base_auth.h" +#include "stg_message.h" +#include "noncopyable.h" + +using namespace std; + +//----------------------------------------------------------------------------- +class USER; +class TARIFF; +class TARIFFS; +class ADMIN; +typedef list::iterator user_iter; +typedef list::const_iterator const_user_iter; +//----------------------------------------------------------------------------- +class USER_ID_GENERATOR +{ +friend class USER; +private: + USER_ID_GENERATOR() {} + int GetNextID() { static int id = 0; return id++; } +}; +//----------------------------------------------------------------------------- +class CHG_PASSIVE_NOTIFIER : public PROPERTY_NOTIFIER_BASE, + private NONCOPYABLE +{ +public: + CHG_PASSIVE_NOTIFIER(USER * u) : user(u) {} + void Notify(const int & oldPassive, const int & newPassive); + +private: + USER * user; +}; +//----------------------------------------------------------------------------- +class CHG_TARIFF_NOTIFIER : public PROPERTY_NOTIFIER_BASE, + private NONCOPYABLE +{ +public: + CHG_TARIFF_NOTIFIER(USER * u) : user(u) {} + void Notify(const string & oldTariff, const string & newTariff); + +private: + USER * user; +}; +//----------------------------------------------------------------------------- +class CHG_CASH_NOTIFIER : public PROPERTY_NOTIFIER_BASE, + private NONCOPYABLE +{ +public: + CHG_CASH_NOTIFIER(USER * u) : user(u) {} + void Notify(const double & oldCash, const double & newCash); + +private: + USER * user; +}; +//----------------------------------------------------------------------------- +class CHG_IP_NOTIFIER : public PROPERTY_NOTIFIER_BASE, + private NONCOPYABLE +{ +public: + CHG_IP_NOTIFIER(USER * u) : user(u) {} + void Notify(const uint32_t & oldCash, const uint32_t & newCash); + +private: + USER * user; +}; +//----------------------------------------------------------------------------- +class USER +{ +friend class CHG_PASSIVE_NOTIFIER; +friend class CHG_TARIFF_NOTIFIER; +friend class CHG_CASH_NOTIFIER; +friend class CHG_IP_NOTIFIER; +public: + USER(const SETTINGS * settings, + const BASE_STORE * store, + const TARIFFS * tariffs, + const ADMIN & sysAdmin, + const map * ipIndex); + USER(const USER & u); + ~USER(); + + int ReadConf(); + int ReadStat(); + int WriteConf(); + int WriteStat(); + int WriteMonthStat(); + + string const & GetLogin() const { return login; } + void SetLogin(string const & l); + + uint32_t GetCurrIP() const { return currIP; } + time_t GetCurrIPModificationTime() const { return currIP.ModificationTime(); } + + void AddCurrIPBeforeNotifier(PROPERTY_NOTIFIER_BASE *); + void DelCurrIPBeforeNotifier(PROPERTY_NOTIFIER_BASE *); + + void AddCurrIPAfterNotifier(PROPERTY_NOTIFIER_BASE *); + void DelCurrIPAfterNotifier(PROPERTY_NOTIFIER_BASE *); + + int GetID() const { return id; } + + double GetPassiveTimePart() const; + void ResetPassiveTime() { passiveTime = 0; } + void SetPassiveTimeAsNewUser(); + + void ResetDetailStat(); + int SwapDetailStat(); + int WriteDetailStat(); + + const TARIFF * GetTariff() const { return tariff; } + void ResetNextTariff() { nextTariff = ""; } + + #ifdef TRAFF_STAT_WITH_PORTS + void AddTraffStatU(int dir, uint32_t ip, uint16_t port, uint32_t len); + void AddTraffStatD(int dir, uint32_t ip, uint16_t port, uint32_t len); + #else + void AddTraffStatU(int dir, uint32_t ip, uint32_t len); + void AddTraffStatD(int dir, uint32_t ip, uint32_t len); + #endif + + const DIR_TRAFF & GetSessionUpload() const { return sessionUpload; } + const DIR_TRAFF & GetSessionDownload() const { return sessionDownload; } + + bool GetConnected() const { return connected; } + time_t GetConnectedModificationTime() const { return connected.ModificationTime(); } + int GetAuthorized() const { return authorizedBy.size(); } + int Authorize(uint32_t ip, const string & iface, uint32_t enabledDirs, const BASE_AUTH * auth); + void Unauthorize(const BASE_AUTH * auth); + bool IsAuthorizedBy(const BASE_AUTH * auth) const; + void OnAdd(); + void OnDelete(); + + int AddMessage(STG_MSG * msg); + + void UpdatePingTime(time_t t = 0); + time_t GetPingTime() const { return pingTime; } + + void PrintUser() const; + void Run(); + + const string & GetStrError() const { return errorStr; } + + USER_PROPERTIES property; + + void SetDeleted() { deleted = true; } + bool GetDeleted() const { return deleted; } + + time_t GetLastWriteStatTime() const { return lastWriteStat; } + + void MidnightResetSessionStat(); + void ProcessDayFee(); + void SetPrepaidTraff(); + void ProcessDayFeeSpread(); + void ProcessNewMonth(); + + bool IsInetable(); + string GetEnabledDirs(); + +private: + STG_LOGGER & WriteServLog; + + void Connect(bool fakeConnect = false); + void Disconnect(bool fakeDisconnect, const std::string & reason); + int SaveMonthStat(int month, int year); + + int SendMessage(const STG_MSG & msg); + int RemoveMessage(uint64_t) { return 0; } + int ScanMessage(); + time_t lastScanMessages; + + string login; + int id; + bool __connected; + USER_PROPERTY connected; + + bool enabledDirs[DIR_NUM]; + + USER_ID_GENERATOR userIDGenerator; + + uint32_t __currIP; // ôÅËÕÝÉÊ ÁÄÒÅÓ ÐÏÌØÚÏ×ÁÔÅÌÑ + USER_PROPERTY currIP; + + /* + ë ÔÏÍÕ ÍÏÍÅÎÔÕ ËÁË ÍÙ ÕÖÅ ÎÅ Á×ÔÏÒÉÚÏ×ÁÎÉÙ, ÎÏ ÅÝÅ ÎÅ ×ÙÐÏÌÎÅÎ Disconnect, + currIP ÕÖÅ ÓÂÒÏÛÅÎ. ðÏÓÌÅÄÎÅÅ ÚÎÁÞÅÎÉÅ currIP ÓÏÈÒÁÎÑÅÍ × lastIPForDisconnect + */ + uint32_t lastIPForDisconnect; + + time_t pingTime; + + const ADMIN sysAdmin; + const BASE_STORE * store; + + const TARIFFS * tariffs; + const TARIFF * tariff; + + map traffStatInternal[2]; + map * traffStat; + map * traffStatToWrite; + int traffStatInUse; + + const SETTINGS * settings; + + set authorizedBy; + + const map * ipIndex; + + bool deleted; + + time_t lastWriteStat; // ÷ÒÅÍÑ ÐÏÓÌÅÄÎÅÊ ÚÁÐÉÓÉ ÓÔÁÔÉÓÔÉËÉ + time_t lastWriteDeatiledStat; // ÷ÒÅÍÑ ÐÏÓÌÅÄÎÅÊ ÚÁÐÉÓÉ ÄÅÔÁÌØÎÏÊ ÓÔÁÔÉÓÔÉËÉ + time_t lastSwapDeatiledStat; // ÷ÒÅÍÑ ÐÏÓÌÅÄÎÅÊ ÚÁÐÉÓÉ ÄÅÔÁÌØÎÏÊ ÓÔÁÔÉÓÔÉËÉ + + bool writeFreeMbTraffCost; + + // Properties + USER_PROPERTY & cash; + USER_PROPERTY & up; + USER_PROPERTY & down; + USER_PROPERTY & lastCashAdd; + USER_PROPERTY & passiveTime; + USER_PROPERTY & lastCashAddTime; + USER_PROPERTY & freeMb; + USER_PROPERTY & lastActivityTime; + USER_PROPERTY & password; + USER_PROPERTY & passive; + USER_PROPERTY & disabled; + USER_PROPERTY & disabledDetailStat; + USER_PROPERTY & alwaysOnline; + USER_PROPERTY & tariffName; + USER_PROPERTY & nextTariff; + USER_PROPERTY & address; + USER_PROPERTY & note; + USER_PROPERTY & group; + USER_PROPERTY & email; + USER_PROPERTY & phone; + USER_PROPERTY & realName; + USER_PROPERTY & credit; + USER_PROPERTY & creditExpire; + USER_PROPERTY & ips; + USER_PROPERTY & userdata0; + USER_PROPERTY & userdata1; + USER_PROPERTY & userdata2; + USER_PROPERTY & userdata3; + USER_PROPERTY & userdata4; + USER_PROPERTY & userdata5; + USER_PROPERTY & userdata6; + USER_PROPERTY & userdata7; + USER_PROPERTY & userdata8; + USER_PROPERTY & userdata9; + + // End properties + + DIR_TRAFF sessionUpload; + DIR_TRAFF sessionDownload; + + CHG_PASSIVE_NOTIFIER passiveNotifier; + CHG_TARIFF_NOTIFIER tariffNotifier; + CHG_CASH_NOTIFIER cashNotifier; + CHG_IP_NOTIFIER ipNotifier; + + mutable pthread_mutex_t mutex; + + string errorStr; +}; +//----------------------------------------------------------------------------- + +#endif //USER_H diff --git a/projects/stargazer/user_property.cpp b/projects/stargazer/user_property.cpp new file mode 100644 index 00000000..c1e202d7 --- /dev/null +++ b/projects/stargazer/user_property.cpp @@ -0,0 +1,46 @@ +#include "user_property.h" + +//----------------------------------------------------------------------------- +USER_PROPERTIES::USER_PROPERTIES(const SETTINGS * s) +: +cash (stat.cash, "cash", false, true, GetStgLogger(), s), +up (stat.up, "upload", false, true, GetStgLogger(), s), +down (stat.down, "download", false, true, GetStgLogger(), s), +lastCashAdd (stat.lastCashAdd, "lastCashAdd", false, true, GetStgLogger(), s), +passiveTime (stat.passiveTime, "passiveTime", false, true, GetStgLogger(), s), +lastCashAddTime (stat.lastCashAddTime, "lastCashAddTime", false, true, GetStgLogger(), s), +freeMb (stat.freeMb, "freeMb", false, true, GetStgLogger(), s), +lastActivityTime(stat.lastActivityTime, "lastActivityTime", false, true, GetStgLogger(), s), + + +password (conf.password, "password", true, false, GetStgLogger(), s), +passive (conf.passive, "passive", false, false, GetStgLogger(), s), +disabled (conf.disabled, "disabled", false, false, GetStgLogger(), s), +disabledDetailStat(conf.disabledDetailStat,"DisabledDetailStat",false,false,GetStgLogger(), s), +alwaysOnline(conf.alwaysOnline, "alwaysOnline", false, false, GetStgLogger(), s), +tariffName (conf.tariffName, "tariff", false, false, GetStgLogger(), s), +nextTariff (conf.nextTariff, "new tariff", false, false, GetStgLogger(), s), +address (conf.address, "address", false, false, GetStgLogger(), s), +note (conf.note, "note", false, false, GetStgLogger(), s), +group (conf.group, "group", false, false, GetStgLogger(), s), +email (conf.email, "email", false, false, GetStgLogger(), s), +phone (conf.phone, "phone", false, false, GetStgLogger(), s), +realName (conf.realName, "realName", false, false, GetStgLogger(), s), +credit (conf.credit, "credit", false, false, GetStgLogger(), s), +creditExpire(conf.creditExpire, "creditExpire", false, false, GetStgLogger(), s), +ips (conf.ips, "IP", false, false, GetStgLogger(), s), +userdata0 (conf.userdata[0], "userdata0", false, false, GetStgLogger(), s), +userdata1 (conf.userdata[1], "userdata1", false, false, GetStgLogger(), s), +userdata2 (conf.userdata[2], "userdata2", false, false, GetStgLogger(), s), +userdata3 (conf.userdata[3], "userdata3", false, false, GetStgLogger(), s), +userdata4 (conf.userdata[4], "userdata4", false, false, GetStgLogger(), s), +userdata5 (conf.userdata[5], "userdata5", false, false, GetStgLogger(), s), +userdata6 (conf.userdata[6], "userdata6", false, false, GetStgLogger(), s), +userdata7 (conf.userdata[7], "userdata7", false, false, GetStgLogger(), s), +userdata8 (conf.userdata[8], "userdata8", false, false, GetStgLogger(), s), +userdata9 (conf.userdata[9], "userdata9", false, false, GetStgLogger(), s) +{ + +} +//----------------------------------------------------------------------------- + diff --git a/projects/stargazer/user_property.h b/projects/stargazer/user_property.h new file mode 100644 index 00000000..5a8856d0 --- /dev/null +++ b/projects/stargazer/user_property.h @@ -0,0 +1,479 @@ +/* +$Revision: 1.44 $ +$Date: 2010/09/13 05:54:43 $ +$Author: faust $ +*/ + + +#ifndef USER_PROPERTY_H +#define USER_PROPERTY_H + +#include +#include +#include +#include +#include +#include + +#include "base_store.h" +#include "stg_logger.h" +#include "admin.h" +#include "settings.h" +#include "notifer.h" +#include "stg_logger.h" +#include "stg_locker.h" +#include "script_executer.h" + +using namespace std; + +extern const volatile time_t stgTime; + +//----------------------------------------------------------------------------- +template +class USER_PROPERTY + { +public: + USER_PROPERTY(varT& val); + USER_PROPERTY& operator= (const varT&); + USER_PROPERTY& operator-= (const varT&); + virtual ~USER_PROPERTY(); + + const varT * operator&() const throw(); + const varT& ConstData() const throw(); + + operator const varT&() const throw() + { + return value; + } + + //bool IsEmpty() const throw(); + + void AddBeforeNotifier(PROPERTY_NOTIFIER_BASE * n); + void DelBeforeNotifier(PROPERTY_NOTIFIER_BASE * n); + + void AddAfterNotifier(PROPERTY_NOTIFIER_BASE * n); + void DelAfterNotifier(PROPERTY_NOTIFIER_BASE * n); + + time_t ModificationTime() const throw(); + void ModifyTime() throw(); + +protected: + varT & value; + time_t modificationTime; + //typedef set *>::iterator notifier_iter_t; + mutable set *> beforeNotifiers; + mutable set *> afterNotifiers; + mutable pthread_mutex_t mutex; + }; +//----------------------------------------------------------------------------- +template +class USER_PROPERTY_LOGGED: public USER_PROPERTY + { +public: + USER_PROPERTY_LOGGED(varT& val, + const string n, + bool isPassword, + bool isStat, + STG_LOGGER & logger, + const SETTINGS * s); + virtual ~USER_PROPERTY_LOGGED(); + + //operator const varT&() const throw();; + USER_PROPERTY_LOGGED * GetPointer() throw(); + const varT & Get() const; + const string & GetName() const; + bool Set(const varT & val, + const ADMIN & admin, + const string & login, + const BASE_STORE * store, + const string & msg = ""); +protected: + void WriteAccessDenied(const string & login, + const ADMIN & admin, + const string & parameter); + + void WriteSuccessChange(const string & login, + const ADMIN & admin, + const string & parameter, + const string & oldValue, + const string & newValue, + const string & msg, + const BASE_STORE * store); + + void OnChange(const string & login, + const string & paramName, + const string & oldValue, + const string & newValue, + const ADMIN & admin); + + string name; // parameter name. needed for logging + bool isPassword; // is parameter password. when true, it will be logged as ******* + bool isStat; // is parameter a stat data or conf data? + mutable pthread_mutex_t mutex; + STG_LOGGER & stgLogger; // server's logger + const SETTINGS * settings; + }; +//----------------------------------------------------------------------------- +class USER_PROPERTIES + { + friend class USER; +/* + В этом месте важен порядок следования приватной и открытой частей. + Это связано с тем, что часть которая находится в публичной секции + по сути является завуалированной ссылкой на закрытую часть. Т.о. нам нужно + чтобы конструкторы из закрытой части вызвались раньше открытой. Поэтомому в + начале идет закрытая секция + * */ + +private: + USER_STAT stat; + USER_CONF conf; + +public: + USER_PROPERTIES(const SETTINGS * settings); + USER_PROPERTY_LOGGED cash; + USER_PROPERTY_LOGGED up; + USER_PROPERTY_LOGGED down; + USER_PROPERTY_LOGGED lastCashAdd; + USER_PROPERTY_LOGGED passiveTime; + USER_PROPERTY_LOGGED lastCashAddTime; + USER_PROPERTY_LOGGED freeMb; + USER_PROPERTY_LOGGED lastActivityTime; + + USER_PROPERTY_LOGGED password; + USER_PROPERTY_LOGGED passive; + USER_PROPERTY_LOGGED disabled; + USER_PROPERTY_LOGGED disabledDetailStat; + USER_PROPERTY_LOGGED alwaysOnline; + USER_PROPERTY_LOGGED tariffName; + USER_PROPERTY_LOGGED nextTariff; + USER_PROPERTY_LOGGED address; + USER_PROPERTY_LOGGED note; + USER_PROPERTY_LOGGED group; + USER_PROPERTY_LOGGED email; + USER_PROPERTY_LOGGED phone; + USER_PROPERTY_LOGGED realName; + USER_PROPERTY_LOGGED credit; + USER_PROPERTY_LOGGED creditExpire; + USER_PROPERTY_LOGGED ips; + USER_PROPERTY_LOGGED userdata0; + USER_PROPERTY_LOGGED userdata1; + USER_PROPERTY_LOGGED userdata2; + USER_PROPERTY_LOGGED userdata3; + USER_PROPERTY_LOGGED userdata4; + USER_PROPERTY_LOGGED userdata5; + USER_PROPERTY_LOGGED userdata6; + USER_PROPERTY_LOGGED userdata7; + USER_PROPERTY_LOGGED userdata8; + USER_PROPERTY_LOGGED userdata9; + }; + +//============================================================================= + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +USER_PROPERTY::USER_PROPERTY(varT& val) +: +value(val) +{ +pthread_mutex_init(&mutex, NULL); +modificationTime = stgTime; +} +//----------------------------------------------------------------------------- +template +USER_PROPERTY::~USER_PROPERTY() +{ +} +//----------------------------------------------------------------------------- +template +void USER_PROPERTY::ModifyTime() throw() +{ + modificationTime = stgTime; +} +//----------------------------------------------------------------------------- +template +USER_PROPERTY& USER_PROPERTY::operator= (const varT& newValue) +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); + +/* +TODO +if (value == newValue) + return *this;*/ + +typename set *>::iterator ni; + +//printf("USER_PROPERTY::operator= (const varT& rhs)\n"); + +varT oldVal = value; + +ni = beforeNotifiers.begin(); +while (ni != beforeNotifiers.end()) + (*ni++)->Notify(oldVal, newValue); + +value = newValue; +modificationTime = stgTime; + +ni = afterNotifiers.begin(); +while (ni != afterNotifiers.end()) + (*ni++)->Notify(oldVal, newValue); + +return *this; +} +//----------------------------------------------------------------------------- +template +USER_PROPERTY& USER_PROPERTY::operator-= (const varT& delta) +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); + +typename set *>::iterator ni; + +varT oldVal = value; + +ni = beforeNotifiers.begin(); +while (ni != beforeNotifiers.end()) + (*ni++)->Notify(oldVal, oldVal - delta); + +value -= delta; +modificationTime = stgTime; + +ni = afterNotifiers.begin(); +while (ni != afterNotifiers.end()) + (*ni++)->Notify(oldVal, value); + +return *this; +} +//----------------------------------------------------------------------------- +template +const varT * USER_PROPERTY::operator&() const throw() +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +return &value; +} +//----------------------------------------------------------------------------- +template +const varT& USER_PROPERTY::ConstData() const throw() +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +return value; +} +//----------------------------------------------------------------------------- +/*template +bool USER_PROPERTY::IsEmpty() const throw() +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +return !is_set; +}*/ +//----------------------------------------------------------------------------- +template +void USER_PROPERTY::AddBeforeNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +beforeNotifiers.insert(n); +} +//----------------------------------------------------------------------------- +template +void USER_PROPERTY::DelBeforeNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +beforeNotifiers.erase(n); +} +//----------------------------------------------------------------------------- +template +void USER_PROPERTY::AddAfterNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +afterNotifiers.insert(n); +} +//----------------------------------------------------------------------------- +template +void USER_PROPERTY::DelAfterNotifier(PROPERTY_NOTIFIER_BASE * n) +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +afterNotifiers.erase(n); +} +//----------------------------------------------------------------------------- +template +time_t USER_PROPERTY::ModificationTime() const throw() +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +return modificationTime; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +USER_PROPERTY_LOGGED::USER_PROPERTY_LOGGED( + varT& val, + string n, + bool isPass, + bool isSt, + STG_LOGGER & logger, + const SETTINGS * s) + +:USER_PROPERTY(val), +stgLogger(logger) +{ +pthread_mutex_init(&mutex, NULL); +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +USER_PROPERTY::value = val; +isPassword = isPass; +isStat = isSt; +name = n; +settings = s; +} +//----------------------------------------------------------------------------- +template +USER_PROPERTY_LOGGED::~USER_PROPERTY_LOGGED() +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +} +//----------------------------------------------------------------------------- +template +USER_PROPERTY_LOGGED * USER_PROPERTY_LOGGED::GetPointer() throw() +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +return this; +} +//----------------------------------------------------------------------------- +template +const varT & USER_PROPERTY_LOGGED::Get() const +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +return USER_PROPERTY::value; +}; +//------------------------------------------------------------------------- +template +const string & USER_PROPERTY_LOGGED::GetName() const +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); +return name; +}; +//------------------------------------------------------------------------- +template +bool USER_PROPERTY_LOGGED::Set(const varT & val, + const ADMIN & admin, + const string & login, + const BASE_STORE * store, + const string & msg) +{ +STG_LOCKER locker(&mutex, __FILE__, __LINE__); + +//cout << "USER_PROPERTY_LOGGED " << val << endl; +//value = val; +//modificationTime = stgTime; + +const PRIV * priv = admin.GetPriv(); +string adm_login = admin.GetLogin(); +string adm_ip = admin.GetAdminIPStr(); + +if ((priv->userConf && !isStat) || (priv->userStat && isStat) || (priv->userPasswd && isPassword) || (priv->userCash && name == "cash")) + { + stringstream oldVal; + stringstream newVal; + + oldVal.flags(oldVal.flags() | ios::fixed); + newVal.flags(newVal.flags() | ios::fixed); + + oldVal << USER_PROPERTY::value; + newVal << val; + + OnChange(login, name, oldVal.str(), newVal.str(), admin); + + if (isPassword) + { + WriteSuccessChange(login, admin, name, "******", "******", msg, store); + } + else + { + WriteSuccessChange(login, admin, name, oldVal.str(), newVal.str(), msg, store); + } + USER_PROPERTY::operator =(val); + return true; + } +else + { + WriteAccessDenied(login, admin, name); + return false; + } +return true; +} +//------------------------------------------------------------------------- +template +void USER_PROPERTY_LOGGED::WriteAccessDenied(const string & login, + const ADMIN & admin, + const string & parameter) +{ +stgLogger("%s Change user \'%s.\' Parameter \'%s\'. Access denied.", + admin.GetLogStr().c_str(), login.c_str(), parameter.c_str()); +} +//------------------------------------------------------------------------- +template +void USER_PROPERTY_LOGGED::WriteSuccessChange(const string & login, + const ADMIN & admin, + const string & parameter, + const string & oldValue, + const string & newValue, + const string & msg, + const BASE_STORE * store) +{ +stgLogger("%s User \'%s\': \'%s\' parameter changed from \'%s\' to \'%s\'. %s", + admin.GetLogStr().c_str(), + login.c_str(), + parameter.c_str(), + oldValue.c_str(), + newValue.c_str(), + msg.c_str()); + + +/*char userLogMsg[2048]; +sprintf(userLogMsg, "\'%s\' parameter changed from \'%s\' to \'%s\'. %s", + parameter.c_str(), oldValue.c_str(), + newValue.c_str(), msg.c_str());*/ +store->WriteUserChgLog(login, admin.GetLogin(), admin.GetAdminIP(), parameter, oldValue, newValue, msg); +//store->WriteLogString(userLogMsg, login); +} +//------------------------------------------------------------------------- +template +void USER_PROPERTY_LOGGED::OnChange(const string & login, + const string & paramName, + const string & oldValue, + const string & newValue, + const ADMIN &) +{ +string str1; + +str1 = settings->GetConfDir() + "/OnChange"; + +if (access(str1.c_str(), X_OK) == 0) + { + string str2("\"" + str1 + "\" \"" + login + "\" \"" + paramName + "\" \"" + oldValue + "\" \"" + newValue + "\""); + ScriptExec(str2); + } +else + { + stgLogger("Script OnChange cannot be executed. File %s not found.", str1.c_str()); + } +} +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- +template +stringstream & operator<< (stringstream & s, const USER_PROPERTY & v) +{ +s << v.ConstData(); +return s; +} +//----------------------------------------------------------------------------- +template +ostream & operator<< (ostream & o, const USER_PROPERTY & v) +{ +return o << v.ConstData(); +} +//----------------------------------------------------------------------------- + + +#endif // USER_PROPERTY_H + diff --git a/projects/stargazer/users.cpp b/projects/stargazer/users.cpp new file mode 100644 index 00000000..1b710baa --- /dev/null +++ b/projects/stargazer/users.cpp @@ -0,0 +1,768 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.61 $ + $Date: 2010/09/13 05:56:42 $ + $Author: faust $ + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "settings.h" +#include "users.h" +#include "user.h" +#include "common.h" +#include "stg_timer.h" + +using namespace std; + +extern const volatile time_t stgTime; + +//#define USERS_DEBUG 1 + +//----------------------------------------------------------------------------- +USERS::USERS(SETTINGS * s, BASE_STORE * st, TARIFFS * t, const ADMIN & sa) + : users(), + usersToDelete(), + userIPNotifiersBefore(), + userIPNotifiersAfter(), + ipIndex(), + loginIndex(), + settings(s), + tariffs(t), + store(st), + sysAdmin(sa), + WriteServLog(GetStgLogger()), + nonstop(false), + isRunning(false), + mutex(), + thread(), + handle(0), + searchDescriptors(), + onAddNotifiers(), + onDelNotifiers() +{ +pthread_mutexattr_t attr; +pthread_mutexattr_init(&attr); +pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +pthread_mutex_init(&mutex, &attr); +} +//----------------------------------------------------------------------------- +USERS::~USERS() +{ +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +int USERS::FindByNameNonLock(const string & login, user_iter * user) const +{ +map::const_iterator iter; +iter = loginIndex.find(login); +if (iter != loginIndex.end()) + { + if (user) + *user = iter->second; + return 0; + } +return -1; +} +//----------------------------------------------------------------------------- +int USERS::FindByName(const string & login, user_iter * user) const +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return FindByNameNonLock(login, user); +} +//----------------------------------------------------------------------------- +bool USERS::TariffInUse(const string & tariffName) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +list::iterator iter; +iter = users.begin(); +while (iter != users.end()) + { + if (iter->property.tariffName.Get() == tariffName) + return true; + ++iter; + } +return false; +} +//----------------------------------------------------------------------------- +int USERS::Add(const string & login, const ADMIN & admin) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +const PRIV * priv = admin.GetPriv(); + +if (!priv->userAddDel) + { + WriteServLog("%s tried to add user \'%s\'. Access denied.", + admin.GetLogStr().c_str(), login.c_str()); + /*errorStr = "Admin \'" + admin.GetLogin() + + "\': tried to add user \'" + ud->login + "\'. Access denied.";*/ + return -1; + } + +////// +if (store->AddUser(login)) + { + //TODO + //WriteServLog("Admin \'%s\': tried to add user \'%s\'. Access denied.", + // admin.GetLogin().c_str(), ud->login.c_str()); + return -1; + } +////// + +USER u(settings, store, tariffs, sysAdmin, &ipIndex); + +struct tm * tms; +time_t t = stgTime; + +tms = localtime(&t); + +tms->tm_hour = 0; +tms->tm_min = 0; +tms->tm_sec = 0; + +if (settings->GetDayResetTraff() > tms->tm_mday) + tms->tm_mon -= 1; + +tms->tm_mday = settings->GetDayResetTraff(); + +u.SetLogin(login); + +u.SetPassiveTimeAsNewUser(); + +u.WriteConf(); +u.WriteStat(); + +WriteServLog("%s User \'%s\' added.", + admin.GetLogStr().c_str(), login.c_str()); + +u.OnAdd(); + +users.push_front(u); + +AddUserIntoIndexes(users.begin()); +SetUserNotifiers(users.begin()); + +// õ×ÅÄÏÍÌÑÅÍ ×ÓÅÈ ÖÅÌÁÀÝÉÈ, ÞÔÏ ÄÏÂÁ×ÌÅÎ ÎÏ×ÙÊ ÐÏÌØÚÏ×ÁÔÅÌØ +set *>::iterator ni = onAddNotifiers.begin(); +while (ni != onAddNotifiers.end()) + { + (*ni)->Notify(users.begin()); + ++ni; + } + +return 0; +} +//----------------------------------------------------------------------------- +void USERS::Del(const string & login, const ADMIN & admin) +{ +const PRIV * priv = admin.GetPriv(); +user_iter u; + +if (!priv->userAddDel) + { + WriteServLog("%s tried to remove user \'%s\'. Access denied.", + admin.GetLogStr().c_str(), login.c_str()); + return; + } + + + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + + if (FindByNameNonLock(login, &u)) + { + WriteServLog("%s tried to delete user \'%s\': not found.", + admin.GetLogStr().c_str(), + login.c_str()); + return; + } + } + +set *>::iterator ni = onDelNotifiers.begin(); +while (ni != onDelNotifiers.end()) + { + (*ni)->Notify(u); + ++ni; + } + + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + + u->OnDelete(); + u->SetDeleted(); + + USER_TO_DEL utd; + utd.iter = u; + utd.delTime = stgTime; + usersToDelete.push_back(utd); + + UnSetUserNotifiers(u); + DelUserFromIndexes(u); + + WriteServLog("%s User \'%s\' deleted.", + admin.GetLogStr().c_str(), login.c_str()); + + } +} +//----------------------------------------------------------------------------- +int USERS::ReadUsers() +{ +vector usersList; +usersList.clear(); +if (store->GetUsersList(&usersList) < 0) + { + WriteServLog(store->GetStrError().c_str()); + exit(1); + } + +user_iter ui; + +for (unsigned int i = 0; i < usersList.size(); i++) + { + USER u(settings, store, tariffs, sysAdmin, &ipIndex); + + u.SetLogin(usersList[i]); + users.push_front(u); + ui = users.begin(); + + AddUserIntoIndexes(ui); + SetUserNotifiers(ui); + + if (ui->ReadConf() < 0) + return -1; + + if (ui->ReadStat() < 0) + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +void * USERS::Run(void * d) +{ +printfd(__FILE__, "=====================| pid: %d |===================== \n", getpid()); +USERS * us = (USERS*) d; + +struct tm * t; +time_t tt = stgTime; +t = localtime(&tt); + +int min = t->tm_min; +int day = t->tm_mday; + +printfd(__FILE__,"Day = %d Min = %d\n", day, min); + +time_t touchTime = stgTime - MONITOR_TIME_DELAY_SEC; +string monFile = us->settings->GetMonitorDir() + "/users_r"; +printfd(__FILE__, "Monitor=%d file USERS %s\n", us->settings->GetMonitoring(), monFile.c_str()); + +us->isRunning = true; +while (us->nonstop) + { + //printfd(__FILE__,"New Minute. old = %02d current = %02d\n", min, t->tm_min); + //printfd(__FILE__,"New Day. old = %2d current = %2d\n", day, t->tm_mday); + + for_each(us->users.begin(), us->users.end(), mem_fun_ref(&USER::Run)); + + tt = stgTime; + t = localtime(&tt); + + if (min != t->tm_min) + { + printfd(__FILE__,"Sec = %d\n", stgTime); + printfd(__FILE__,"New Minute. old = %d current = %d\n", min, t->tm_min); + min = t->tm_min; + + us->NewMinute(t); + } + + t = localtime(&tt); + if (day != t->tm_mday) + { + printfd(__FILE__,"Sec = %d\n", stgTime); + printfd(__FILE__,"New Day. old = %d current = %d\n", day, t->tm_mday); + day = t->tm_mday; + us->NewDay(t); + } + + if (us->settings->GetMonitoring() && (touchTime + MONITOR_TIME_DELAY_SEC <= stgTime)) + { + //printfd(__FILE__, "Monitor=%d file TRAFFCOUNTER %s\n", tc->monitoring, monFile.c_str()); + touchTime = stgTime; + TouchFile(monFile.c_str()); + } + + stgUsleep(100000); + } //while (us->nonstop) + +user_iter ui = us->users.begin(); +while (ui != us->users.end()) + { + us->UnSetUserNotifiers(ui); + us->DelUserFromIndexes(ui); + ui++; + } + +list::iterator iter; +iter = us->usersToDelete.begin(); +while (iter != us->usersToDelete.end()) + { + iter->delTime -= 2 * userDeleteDelayTime; + ++iter; + } +us->RealDelUser(); + +us->isRunning = false; + +return NULL; +} +//----------------------------------------------------------------------------- +void USERS::NewMinute(const struct tm * t) +{ +int usersCnt = 0; +list::iterator usr; + +//Write traff, reset session traff. Fake disconnect-connect +if (t->tm_hour == 23 && t->tm_min == 59) + { + printfd(__FILE__,"MidnightResetSessionStat\n"); + for_each(users.begin(), users.end(), mem_fun_ref(&USER::MidnightResetSessionStat)); + } + +if (TimeToWriteDetailStat(t)) + { + //printfd(__FILE__, "USER::WriteInetStat\n"); + for_each(users.begin(), users.end(), mem_fun_ref(&USER::SwapDetailStat)); + usersCnt = 0; + + // ðÉÛÅÍ ÀÚÅÒÏ× ÞÁÓÔÑÍÉ. ÷ ÐÅÒÅÒÙ×ÁÈ ×ÙÚÙ×ÁÅÍ USER::Run + usr = users.begin(); + while (usr != users.end()) + { + usersCnt++; + usr->WriteDetailStat(); + usr++; + if (usersCnt % 10 == 0) + for_each(users.begin(), users.end(), mem_fun_ref(&USER::Run)); + } + + for_each(users.begin(), users.end(), mem_fun_ref(&USER::ResetDetailStat)); + } + +RealDelUser(); +} +//----------------------------------------------------------------------------- +void USERS::NewDay(const struct tm * t) +{ +struct tm * t1; +time_t tt = stgTime; +t1 = localtime(&tt); +int dayFee = settings->GetDayFee(); + +if (dayFee == 0) + dayFee = DaysInCurrentMonth(); + +printfd(__FILE__, "DayFee = %d\n", dayFee); +printfd(__FILE__, "Today = %d DayResetTraff = %d\n", t1->tm_mday, settings->GetDayResetTraff()); +printfd(__FILE__, "DayFeeIsLastDay = %d\n", settings->GetDayFeeIsLastDay()); + +if (!settings->GetDayFeeIsLastDay()) + { + printfd(__FILE__, "DayResetTraff - 1 -\n"); + DayResetTraff(t1); + //printfd(__FILE__, "DayResetTraff - 1 - 1 -\n"); + } + +if (settings->GetSpreadFee()) + { + printfd(__FILE__, "Spread DayFee\n"); + for_each(users.begin(), users.end(), mem_fun_ref(&USER::ProcessDayFeeSpread)); + } +else + { + if (t->tm_mday == dayFee) + { + printfd(__FILE__, "DayFee\n"); + for_each(users.begin(), users.end(), mem_fun_ref(&USER::ProcessDayFee)); + } + } + +if (settings->GetDayFeeIsLastDay()) + { + printfd(__FILE__, "DayResetTraff - 2 -\n"); + DayResetTraff(t1); + } +} +//----------------------------------------------------------------------------- +void USERS::DayResetTraff(const struct tm * t1) +{ +int dayResetTraff = settings->GetDayResetTraff(); +if (dayResetTraff == 0) + dayResetTraff = DaysInCurrentMonth(); +if (t1->tm_mday == dayResetTraff) + { + printfd(__FILE__, "ResetTraff\n"); + for_each(users.begin(), users.end(), mem_fun_ref(&USER::ProcessNewMonth)); + for_each(users.begin(), users.end(), mem_fun_ref(&USER::SetPrepaidTraff)); + } +} +//----------------------------------------------------------------------------- +int USERS::Start() +{ +if (ReadUsers()) + { + WriteServLog("USERS: Error: Cannot read users!"); + return -1; + } + +nonstop = true; +if (pthread_create(&thread, NULL, Run, this)) + { + WriteServLog("USERS: Error: Cannot start thread!"); + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int USERS::Stop() +{ +printfd(__FILE__, "USERS::Stop()\n"); + +if (!isRunning) + { + //printfd(__FILE__, "Alredy stopped\n"); + return 0; + } + +nonstop = false; + +//5 seconds to thread stops itself +unsigned i; +for (i = 0; i < 25 * (users.size() / 50 + 1); i++) + { + if (!isRunning) + break; + + usleep(200000); + } + +//after 5 seconds waiting thread still running. now kill it +if (isRunning) + { + printfd(__FILE__, "kill USERS thread.\n"); + //TODO pthread_cancel() + if (pthread_kill(thread, SIGINT)) + { + //errorStr = "Cannot kill USERS thread."; + //printfd(__FILE__, "Cannot kill USERS thread.\n"); + //return 0; + } + printfd(__FILE__, "USERS killed\n"); + } + +printfd(__FILE__, "Before USERS::Run()\n"); +for_each(users.begin(), users.end(), mem_fun_ref(&USER::Run)); +for_each(users.begin(), users.end(), mem_fun_ref(&USER::SwapDetailStat)); +for_each(users.begin(), users.end(), mem_fun_ref(&USER::WriteDetailStat)); +for_each(users.begin(), users.end(), mem_fun_ref(&USER::WriteStat)); +for_each(users.begin(), users.end(), mem_fun_ref(&USER::WriteConf)); + +printfd(__FILE__, "USERS::Stop()\n"); +return 0; +} +//----------------------------------------------------------------------------- +void USERS::RealDelUser() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +printfd(__FILE__, "RealDelUser() users to del: %d\n", usersToDelete.size()); + +list::iterator iter; +iter = usersToDelete.begin(); +while (iter != usersToDelete.end()) + { + printfd(__FILE__, "RealDelUser() user=%s\n", iter->iter->GetLogin().c_str()); + if (iter->delTime + userDeleteDelayTime < stgTime) + { + printfd(__FILE__, "RealDelUser() user=%s removed from DB\n", iter->iter->GetLogin().c_str()); + if (store->DelUser(iter->iter->GetLogin())) + { + WriteServLog("Error removing user \'%s\' from database.", iter->iter->GetLogin().c_str()); + } + users.erase(iter->iter); + usersToDelete.erase(iter++); + } + else + { + ++iter; + } + } +return; +} +//----------------------------------------------------------------------------- +int USERS::GetUserNum() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +return users.size(); +} +//----------------------------------------------------------------------------- +void USERS::AddToIPIdx(user_iter user) +{ +printfd(__FILE__, "USERS: Add IP Idx\n"); +uint32_t ip = user->GetCurrIP(); +//assert(ip && "User has non-null ip"); +if (!ip) + return; // User has disconnected + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +const map::iterator it( + ipIndex.lower_bound(ip) +); + +assert((it == ipIndex.end() || it->first != ip) && "User is not in index"); + +ipIndex.insert(it, std::make_pair(ip, user)); +} +//----------------------------------------------------------------------------- +void USERS::DelFromIPIdx(uint32_t ip) +{ +printfd(__FILE__, "USERS: Del IP Idx\n"); +assert(ip && "User has non-null ip"); + +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +const map::iterator it( + ipIndex.find(ip) +); + +//assert(it != ipIndex.end() && "User is in index"); +if (it == ipIndex.end()) + return; // User has not been added + +ipIndex.erase(it); +} +//----------------------------------------------------------------------------- +int USERS::FindByIPIdx(uint32_t ip, user_iter * usr) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +map::iterator it; +it = ipIndex.find(ip); + +if (it == ipIndex.end()) + { + //printfd(__FILE__, "User NOT found in IP_Index!!!\n"); + return -1; + } +*usr = it->second; +//printfd(__FILE__, "User found in IP_Index\n"); +return 0; +} +//----------------------------------------------------------------------------- +void USERS::AddNotifierUserAdd(NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +onAddNotifiers.insert(n); +} +//----------------------------------------------------------------------------- +void USERS::DelNotifierUserAdd(NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +//printfd(__FILE__, "DelNotifierUserAdd\n"); +onAddNotifiers.erase(n); +} +//----------------------------------------------------------------------------- +void USERS::AddNotifierUserDel(NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +onDelNotifiers.insert(n); +} +//----------------------------------------------------------------------------- +void USERS::DelNotifierUserDel(NOTIFIER_BASE * n) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +onDelNotifiers.erase(n); +} +//----------------------------------------------------------------------------- +int USERS::OpenSearch() +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +handle++; +searchDescriptors[handle] = users.begin(); +return handle; +} +//----------------------------------------------------------------------------- +int USERS::SearchNext(int h, user_iter * u) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +if (searchDescriptors.find(h) == searchDescriptors.end()) + { + WriteServLog("USERS. Incorrect search handle."); + return -1; + } + +if (searchDescriptors[h] == users.end()) + return -1; + +while (searchDescriptors[h]->GetDeleted()) + { + ++searchDescriptors[h]; + if (searchDescriptors[h] == users.end()) + { + return -1; + } + } + +*u = searchDescriptors[h]; + +++searchDescriptors[h]; + +return 0; +} +//----------------------------------------------------------------------------- +int USERS::CloseSearch(int h) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +if (searchDescriptors.find(h) != searchDescriptors.end()) + { + searchDescriptors.erase(searchDescriptors.find(h)); + return 0; + } + +WriteServLog("USERS. Incorrect search handle."); +return -1; +} +//----------------------------------------------------------------------------- +void USERS::SetUserNotifiers(user_iter user) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +PROPERTY_NOTIFER_IP_BEFORE nb(*this, user); +PROPERTY_NOTIFER_IP_AFTER na(*this, user); + +userIPNotifiersBefore.push_front(nb); +userIPNotifiersAfter.push_front(na); + +user->AddCurrIPBeforeNotifier(&(*userIPNotifiersBefore.begin())); +user->AddCurrIPAfterNotifier(&(*userIPNotifiersAfter.begin())); +} +//----------------------------------------------------------------------------- +void USERS::UnSetUserNotifiers(user_iter user) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); + +list::iterator bi; +list::iterator ai; + +bi = userIPNotifiersBefore.begin(); +while (bi != userIPNotifiersBefore.end()) + { + if (bi->GetUser() == user) + { + bi->GetUser()->DelCurrIPBeforeNotifier(&(*bi)); + userIPNotifiersBefore.erase(bi); + //printfd(__FILE__, "Notifier Before removed. User %s\n", bi->GetUser()->GetLogin().c_str()); + break; + } + bi++; + } + +ai = userIPNotifiersAfter.begin(); +while (ai != userIPNotifiersAfter.end()) + { + if (ai->GetUser() == user) + { + ai->GetUser()->DelCurrIPAfterNotifier(&(*ai)); + userIPNotifiersAfter.erase(ai); + //printfd(__FILE__, "Notifier After removed. User %s\n", ai->GetUser()->GetLogin().c_str()); + break; + } + ai++; + } +} +//----------------------------------------------------------------------------- +void USERS::AddUserIntoIndexes(user_iter user) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +loginIndex.insert(pair(user->GetLogin(), user)); +} +//----------------------------------------------------------------------------- +void USERS::DelUserFromIndexes(user_iter user) +{ +STG_LOCKER lock(&mutex, __FILE__, __LINE__); +loginIndex.erase(user->GetLogin()); +} +//----------------------------------------------------------------------------- +bool USERS::TimeToWriteDetailStat(const struct tm * t) +{ +int statTime = settings->GetDetailStatWritePeriod(); + +switch (statTime) + { + case dsPeriod_1: + if (t->tm_min == 0) + return true; + break; + case dsPeriod_1_2: + if (t->tm_min % 30 == 0) + return true; + break; + case dsPeriod_1_4: + if (t->tm_min % 15 == 0) + return true; + break; + case dsPeriod_1_6: + if (t->tm_min % 10 == 0) + return true; + break; + } +return false; +} +//----------------------------------------------------------------------------- +/*int USERS::SendMessage(const string & login, + time_t sndTtime, + time_t showTime, + char type, + const string & text) const +{ +return 0; +}*/ +//----------------------------------------------------------------------------- + + + diff --git a/projects/stargazer/users.h b/projects/stargazer/users.h new file mode 100644 index 00000000..e882dcc1 --- /dev/null +++ b/projects/stargazer/users.h @@ -0,0 +1,199 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* +$Revision: 1.31 $ +$Date: 2010/10/07 20:04:48 $ +$Author: faust $ +*/ + + +#ifndef USERS_H +#define USERS_H + +#include +#include +#include +#include +#include +#include + +#include "os_int.h" + +#include "settings.h" +#include "user.h" +#include "tariffs.h" +#include "stg_logger.h" +#include "notifer.h" +#include "actions.h" +#include "noncopyable.h" +#include "eventloop.h" + +const int userDeleteDelayTime = 120; + +using namespace std; +class USERS; +//----------------------------------------------------------------------------- +class PROPERTY_NOTIFER_IP_BEFORE: public PROPERTY_NOTIFIER_BASE +{ +public: + PROPERTY_NOTIFER_IP_BEFORE(USERS & us, user_iter u) : users(us), user(u) {}; + void Notify(const uint32_t & oldValue, const uint32_t & newValue); + user_iter GetUser() { return user; }; +private: + USERS & users; + user_iter user; +}; +//----------------------------------------------------------------------------- +class PROPERTY_NOTIFER_IP_AFTER: public PROPERTY_NOTIFIER_BASE +{ +public: + PROPERTY_NOTIFER_IP_AFTER(USERS & us, user_iter u) : users(us), user(u) {}; + void Notify(const uint32_t & oldValue, const uint32_t & newValue); + user_iter GetUser() { return user; }; +private: + USERS & users; + user_iter user; +}; +//----------------------------------------------------------------------------- +struct USER_TO_DEL +{ +USER_TO_DEL() + : iter(), + delTime(0) +{}; + +list::iterator iter; +time_t delTime; +}; +//----------------------------------------------------------------------------- +class USERS : private NONCOPYABLE + { + friend class PROPERTY_NOTIFER_IP_BEFORE; + friend class PROPERTY_NOTIFER_IP_AFTER; + +public: + USERS(SETTINGS * s, BASE_STORE * store, TARIFFS * tariffs, const ADMIN & sysAdmin); + ~USERS(); + + int FindByName(const string & login, user_iter * user) const; + int FindByID(int id, user_iter * user); + + bool TariffInUse(const string & tariffName); + + void AddNotifierUserAdd(NOTIFIER_BASE *); + void DelNotifierUserAdd(NOTIFIER_BASE *); + + void AddNotifierUserDel(NOTIFIER_BASE *); + void DelNotifierUserDel(NOTIFIER_BASE *); + + int Add(const string & login, const ADMIN & admin); + void Del(const string & login, const ADMIN & admin); + + int ReadUsers(); + int GetUserNum(); + + int FindByIPIdx(uint32_t ip, user_iter * user); + + int OpenSearch(); + int SearchNext(int handler, user_iter * u); + int CloseSearch(int handler); + + int Start(); + int Stop(); + + int SendMessage(const string & login, time_t sndTtime, time_t showTime, char type, const string & text) const; + +private: + void AddToIPIdx(user_iter); + void DelFromIPIdx(uint32_t ip); + + int FindByNameNonLock(const string & login, user_iter * user) const; + int FindByIDNonLock(int id, user_iter * user); + + void RealDelUser(); + void ProcessActions(); + + void SetUserNotifiers(user_iter user); + void UnSetUserNotifiers(user_iter user); + + void AddUserIntoIndexes(user_iter user); + void DelUserFromIndexes(user_iter user); + + static void * Run(void *); + void NewMinute(const struct tm * t); + void NewDay(const struct tm * t); + void DayResetTraff(const struct tm * t); + + bool TimeToWriteDetailStat(const struct tm * t); + + list users; + list usersToDelete; + list userIPNotifiersBefore; + list userIPNotifiersAfter; + + map ipIndex; + map loginIndex; + + SETTINGS * settings; + TARIFFS * tariffs; + BASE_STORE * store; + const ADMIN sysAdmin; + STG_LOGGER & WriteServLog; + + bool nonstop; + bool isRunning; + + mutable pthread_mutex_t mutex; + pthread_t thread; + mutable unsigned int handle; + + mutable map searchDescriptors; + + set *> onAddNotifiers; + set *> onDelNotifiers; + }; +//----------------------------------------------------------------------------- +inline +void PROPERTY_NOTIFER_IP_BEFORE::Notify(const uint32_t & oldValue, + const uint32_t &) +{ +if (!oldValue) + return; + +//EVENT_LOOP_SINGLETON::GetInstance().Enqueue(users, &USERS::DelFromIPIdx, oldValue); +// Using explicit call to assure that index is valid, because fast reconnect with delayed call can result in authorization error +users.DelFromIPIdx(oldValue); +} +//----------------------------------------------------------------------------- +inline +void PROPERTY_NOTIFER_IP_AFTER::Notify(const uint32_t &, + const uint32_t & newValue) +{ +if (!newValue) + return; + +//EVENT_LOOP_SINGLETON::GetInstance().Enqueue(users, &USERS::AddToIPIdx, user); +// Using explicit call to assure that index is valid, because fast reconnect with delayed call can result in authorization error +users.AddToIPIdx(user); +} +//----------------------------------------------------------------------------- +#endif + diff --git a/projects/traffcounter/Makefile b/projects/traffcounter/Makefile new file mode 100644 index 00000000..09abeb74 --- /dev/null +++ b/projects/traffcounter/Makefile @@ -0,0 +1,48 @@ +include make.conf + +CFLAGS += -g3 -W -Wall -pedantic +CFLAGS += $(DEFINES) -D_BSD_SOURCE + +CXXFLAGS += $(CFLAGS) + +SOURCES=logger.cpp lock.cpp traffcounter.cpp rules.cpp utils.cpp +RULES_TESTER_SOURCES=logger.cpp rules.cpp utils.cpp rules_tester.cpp +RULES_FINDER_TESTER_SOURCES=logger.cpp lock.cpp rules.cpp rules_finder.cpp utils.cpp rf_tester.cpp +TC_TESTER_SOURCES=logger.cpp rules.cpp rules_finder.cpp utils.cpp traffcounter.cpp lock.cpp tc_tester.cpp +LIBS=-lpthread +PROG=st_core + +.PHONY: all tests clean + +#all: $(PROG) +all: tests + +$(PROG): $(subst .cpp,.o,$(SOURCES)) + $(CXX) $^ $(LDFLAGS) $(LIBS) -o $@ + +tests: rules_tester rf_tester tc_tester + +rules_tester: $(subst .cpp,.o,$(RULES_TESTER_SOURCES)) + $(CXX) $^ $(LDFLAGS) -o $@ + +rf_tester: $(subst .cpp,.o,$(RULES_FINDER_TESTER_SOURCES)) + $(CXX) $^ $(LDFLAGS) -o $@ + +tc_tester: $(subst .cpp,.o,$(TC_TESTER_SOURCES)) + $(CXX) $^ $(LDFLAGS) $(LIBS) -o $@ + +clean: + rm -f $(PROG) *.o *d rules_tester rf_tester tc_tester gmon.out + +ifneq ($(MAKECMDGOALS),distclean) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include $(subst .cpp,.d,$(SOURCES)) +endif +endif +endif + +%.d: %.cpp + @$(CC) -MM $(CFLAGS) $< > $@.$$$$; \ + sed 's,\($*\).o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$ diff --git a/projects/traffcounter/capturer_tc_iface.h b/projects/traffcounter/capturer_tc_iface.h new file mode 100644 index 00000000..0b51dfc2 --- /dev/null +++ b/projects/traffcounter/capturer_tc_iface.h @@ -0,0 +1,26 @@ +#ifndef __CAPTURER_TC_IFACE_H__ +#define __CAPTURER_TC_IFACE_H__ + +#ifdef HAVE_STDINT + #include +#else + #ifdef HAVE_INTTYPES + #include + #else + #error "You need either stdint.h or inttypes.h to compile this!" + #endif +#endif + +namespace STG +{ + + class ICAPTURER_TC + { + public: + virtual ~ICAPTURER_TC() {}; + virtual void AddPacket(const iphdr &, uint16_t, uint16_t) = 0; + }; + +} + +#endif diff --git a/projects/traffcounter/configure b/projects/traffcounter/configure new file mode 100755 index 00000000..ab7d3f2e --- /dev/null +++ b/projects/traffcounter/configure @@ -0,0 +1,78 @@ +#!/bin/sh + +echo -n "checking os type... " +OS=`uname` +echo $OS + +echo -n "checking stdint.h... " +if [ -f /usr/include/stdint.h ] +then + DEFINES="$DEFINES -DHAVE_STDINT" + echo "ok" +else + echo "fail" + + echo -n "checking inttypes.h... " + if [ -f /usr/include/inttypes.h ] + then + DEFINES="$DEFINES -DHAVE_INTTYPES" + echo "ok" + else + echo "fail" + echo "You need either stdint.h or inttypes.h to compile this" + exit 1 + fi +fi + +if [ "$OS"=="Linux" ] +then + DEFINES="$DEFINES -DLINUX" + echo -n "checking gmake... " + gmake --version > /dev/null 2> /dev/null + if [ $? -eq 0 ] + then + MAKE="gmake" + echo "ok" + else + echo "fail" + echo -n "checking make... " + make --version > /dev/null 2> /dev/null + if [ $? -eq 0 ] + then + echo "ok" + MAKE="make" + else + echo "fail" + echo "You need a GNU Make to compile this" + exit 1 + fi + fi +else + if [ "$OS"=="FreeBSD" ] + then + DEFINES="$DEFINES -DFREEBSD" + echo -n "checking gmake... " + gmake --version > /dev/null 2> /dev/null + if [ $? -eq 0 ] + then + echo "ok" + MAKE="gmake" + else + echo "fail" + echo "You need a GNU Make to use this" + exit 1 + fi + else + echo "This version of software is only compatible with Linux and FreeBSD" + exit 1 + fi +fi + +echo "Configuration successfull. Details:" +echo -e "\tOS: $OS" +echo -e "\tGNU Make utility: $MAKE" +echo -e "\nType $MAKE and $MAKE install now" + +rm -f make.conf +echo "OS = $OS" >> make.conf +echo "DEFINES = $DEFINES" >> make.conf diff --git a/projects/traffcounter/lock.cpp b/projects/traffcounter/lock.cpp new file mode 100644 index 00000000..5a166712 --- /dev/null +++ b/projects/traffcounter/lock.cpp @@ -0,0 +1,17 @@ +#include + +#include + +#include "lock.h" + + +SCOPED_LOCK::SCOPED_LOCK(pthread_mutex_t & mtx) + : mutex(mtx) +{ +pthread_mutex_lock(&mutex); +} + +SCOPED_LOCK::~SCOPED_LOCK() +{ +pthread_mutex_unlock(&mutex); +} diff --git a/projects/traffcounter/lock.h b/projects/traffcounter/lock.h new file mode 100644 index 00000000..4a89c297 --- /dev/null +++ b/projects/traffcounter/lock.h @@ -0,0 +1,17 @@ +#ifndef __SCOPED_LOCK_H__ +#define __SCOPED_LOCK_H__ + +#include + +class SCOPED_LOCK +{ +public: + SCOPED_LOCK(pthread_mutex_t & mtx); + ~SCOPED_LOCK(); +private: + pthread_mutex_t & mutex; + + SCOPED_LOCK(const SCOPED_LOCK & lock) : mutex(lock.mutex) {}; +}; + +#endif diff --git a/projects/traffcounter/logger.cpp b/projects/traffcounter/logger.cpp new file mode 100644 index 00000000..0352d05b --- /dev/null +++ b/projects/traffcounter/logger.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include + +#include "logger.h" + +using namespace std; + +STGLogger::~STGLogger() +{ +} + +ostream & STGLogger::operator <<(const string & val) +{ + LogDate(); + out << " " << val; + return out; +} + +void STGLogger::LogDate() +{ + time_t t(time(NULL)); + struct tm * tt = localtime(&t); + out << "[" << tt->tm_year + 1900 << "-"; + out << (tt->tm_mon + 1 < 10 ? "0" : "") << tt->tm_mon + 1 << "-"; + out << (tt->tm_mday < 10 ? "0" : "") << tt->tm_mday << " "; + out << (tt->tm_hour < 10 ? "0" : "") << tt->tm_hour << ":"; + out << (tt->tm_min < 10 ? "0" : "") << tt->tm_min << ":"; + out << (tt->tm_sec < 10 ? "0" : "") << tt->tm_sec << "]"; +} diff --git a/projects/traffcounter/logger.h b/projects/traffcounter/logger.h new file mode 100644 index 00000000..85c17bef --- /dev/null +++ b/projects/traffcounter/logger.h @@ -0,0 +1,23 @@ +#ifndef __LOGGER_H__ +#define __LOGGER_H__ + +#include +#include + +#define LOG_IT (log << __FILE__ << ":" << __LINE__ << " ") + +class STGLogger { +public: + STGLogger() : out(std::cout) {}; + STGLogger(std::ostream & stream) : out(stream) {}; + ~STGLogger(); + + std::ostream &operator <<(const std::string & val); +private: + void LogDate(); + std::ostream & out; +}; + +extern STGLogger log; + +#endif diff --git a/projects/traffcounter/rf_tester.cpp b/projects/traffcounter/rf_tester.cpp new file mode 100644 index 00000000..6e20a24c --- /dev/null +++ b/projects/traffcounter/rf_tester.cpp @@ -0,0 +1,241 @@ +/* + * Network: + * - server: 192.168.0.1 + * - user A: 192.168.0.2 + * - user B: 192.168.0.3 + * + * External resources: + * - host 1: 216.239.59.104 + * - host 2: 72.14.221.104 + * - host 3: 66.249.93.104 + * - host 4: 195.5.61.68 + * + * Directions: + * - Local: ALL 192.168.0.0/24 + * - DNS: TCP_UDP 195.5.61.68/32:53 + * - FTP: TCP 129.22.8.159/32:20-21 + * - World: ALL 0.0.0.0/0 + * + */ + +#include +#include +#include +#include +#include + +#include + +#include "rules.h" +#include "rules_finder.h" +#include "logger.h" + +using namespace std; +using namespace STG; + +STGLogger log; + +RULE MakeRule(const std::string & ip, + const std::string & mask, + uint16_t port1, + uint16_t port2, + int proto, + int dir) +{ + RULE rule; + + rule.ip = inet_addr(ip.c_str()); + rule.mask = inet_addr(mask.c_str()); + rule.port1 = port1; + rule.port2 = port2; + rule.proto = proto; + rule.dir = dir; + + return rule; +} + +RULES PrepareRules() +{ + RULES rules; + RULE local(MakeRule("192.168.0.0", + "255.255.255.0", + 0, + 65535, + -1, + 0)); + RULE dns(MakeRule("195.5.61.68", + "255.255.255.255", + 53, + 53, + -1, + 1)); + RULE ftp(MakeRule("129.22.8.159", + "255.255.255.255", + 20, + 21, + -1, + 2)); + RULE world(MakeRule("0.0.0.0", + "0.0.0.0", + 0, + 65535, + -1, + 3)); + + rules.push_back(local); + + rules.push_back(dns); + + rules.push_back(ftp); + + rules.push_back(world); + + return rules; +} + +PENDING_PACKET MakePacket(const std::string & from, + const std::string & to, + uint16_t sport, + uint16_t dport, + int proto, + PENDING_PACKET::DIRECTION direction, + int length) +{ + iphdr hdr; + + hdr.ihl = 5; + hdr.version = 4; + hdr.tos = 0; + hdr.tot_len = length; + hdr.id = 0; + hdr.frag_off = 50; + hdr.ttl = 64; + hdr.protocol = proto; + hdr.check = 0; + hdr.saddr = inet_addr(from.c_str()); + hdr.daddr = inet_addr(to.c_str()); + + PENDING_PACKET packet(hdr, sport, dport); + + packet.direction = direction; + + return packet; +} + +struct TEST_INFO { + int wantedDir; + int actualDir; // Parser error status + bool stdException; // Parser throws an std execption + bool otherException; // Parser throws another exception + bool result; +}; + +struct RF_TESTER : public std::unary_function, void> +{ +public: + RF_TESTER(RULES_FINDER & r) + : rf(r), + testLog(), + result(true) + { + }; + ~RF_TESTER() + { + PrintLog(); + if (result) + exit(EXIT_SUCCESS); + exit(EXIT_FAILURE); + } + void operator()(const std::pair & entry) + { + TEST_INFO info; + info.wantedDir = entry.second; + info.actualDir = -1; + info.stdException = false; + info.otherException = false; + info.result = true; + try + { + info.actualDir = rf.GetDir(entry.first); + } + catch (std::exception & ex) + { + info.stdException = true; + info.result = false; + } + catch (...) + { + info.otherException = true; + info.result = false; + } + info.result &= (info.actualDir == info.wantedDir); + result &= info.result; + testLog.push_back(info); + }; + + void PrintLog() + { + int testNumber = 1; + std::cout << "RF_TESTER results:\n"; + std::cout << "-----------------------------------------------------------------\n"; + std::vector::const_iterator it; + for (it = testLog.begin(); it != testLog.end(); ++it) + { + std::cout << "Test no.: " << testNumber++ << "\t" + << "Correct dir: " << it->wantedDir << "\t" + << "Actual dir:" << it->actualDir << "\t" + << "STD exceptions: " << it->stdException << "\t" + << "Other exceptions: " << it->otherException << "\t" + << "Result: " << it->result << "\n"; + } + std::cout << "-----------------------------------------------------------------\n"; + std::cout << "Final result: " << (result ? "passed" : "failed") << std::endl; + } + + bool Result() const { return result; }; +private: + RULES_FINDER & rf; + std::vector testLog; + bool result; +}; + +int main() +{ + RULES rules(PrepareRules()); + RULES_FINDER rf; + + rf.SetRules(rules); + + std::list > tests; + + // Local, SSH + tests.push_back(make_pair(MakePacket("192.168.0.2", "192.168.0.1", 3214, 22, 6, PENDING_PACKET::OUTGOING, 0), 0)); + tests.push_back(make_pair(MakePacket("192.168.0.1", "192.168.0.2", 22, 3214, 6, PENDING_PACKET::OUTGOING, 0), 0)); + // Local, SSH, incorrect direction detection + tests.push_back(make_pair(MakePacket("192.168.0.2", "192.168.0.1", 3214, 22, 6, PENDING_PACKET::INCOMING, 0), 0)); + tests.push_back(make_pair(MakePacket("192.168.0.1", "192.168.0.2", 22, 3214, 6, PENDING_PACKET::INCOMING, 0), 0)); + // Local, FTP + tests.push_back(make_pair(MakePacket("192.168.0.2", "192.168.0.1", 3214, 20, 6, PENDING_PACKET::OUTGOING, 0), 0)); + tests.push_back(make_pair(MakePacket("192.168.0.1", "192.168.0.2", 21, 3214, 6, PENDING_PACKET::OUTGOING, 0), 0)); + // Local, DNS + tests.push_back(make_pair(MakePacket("192.168.0.2", "192.168.0.1", 3214, 53, 6, PENDING_PACKET::OUTGOING, 0), 0)); + tests.push_back(make_pair(MakePacket("192.168.0.1", "192.168.0.2", 53, 3214, 6, PENDING_PACKET::OUTGOING, 0), 0)); + // Known DNS, DNS + tests.push_back(make_pair(MakePacket("192.168.0.2", "195.5.61.68", 3210, 53, 6, PENDING_PACKET::OUTGOING, 0), 1)); + tests.push_back(make_pair(MakePacket("195.5.61.68", "192.168.0.2", 53, 3210, 6, PENDING_PACKET::INCOMING, 0), 1)); + // Known DNS, invalid ports + tests.push_back(make_pair(MakePacket("192.168.0.2", "195.5.61.68", 3210, 54, 6, PENDING_PACKET::OUTGOING, 0), 3)); + tests.push_back(make_pair(MakePacket("195.5.61.68", "192.168.0.2", 20, 3210, 6, PENDING_PACKET::INCOMING, 0), 3)); + // Known FTP, FTP + tests.push_back(make_pair(MakePacket("192.168.0.2", "129.22.8.159", 3241, 20, 6, PENDING_PACKET::OUTGOING, 0), 2)); + tests.push_back(make_pair(MakePacket("129.22.8.159", "192.168.0.2", 21, 3241, 6, PENDING_PACKET::INCOMING, 0), 2)); + // Known FTP, invalid ports + tests.push_back(make_pair(MakePacket("192.168.0.2", "129.22.8.159", 3241, 53, 6, PENDING_PACKET::OUTGOING, 0), 3)); + tests.push_back(make_pair(MakePacket("129.22.8.159", "192.168.0.2", 22, 3241, 6, PENDING_PACKET::INCOMING, 0), 3)); + + std::for_each(tests.begin(), + tests.end(), + RF_TESTER(rf)); + + return EXIT_SUCCESS; +} diff --git a/projects/traffcounter/rules b/projects/traffcounter/rules new file mode 100644 index 00000000..ad3cb886 --- /dev/null +++ b/projects/traffcounter/rules @@ -0,0 +1,1427 @@ +ALL 62.16.0.0/19 DIR0 +ALL 62.64.64.0/18 DIR0 +ALL 62.72.160.0/19 DIR0 +ALL 62.80.160.0/19 DIR0 +ALL 62.149.0.0/19 DIR0 +ALL 62.176.0.0/21 DIR0 +ALL 62.182.80.0/21 DIR0 +ALL 62.182.120.0/21 DIR0 +ALL 62.182.152.0/21 DIR0 +ALL 62.182.160.0/21 DIR0 +ALL 62.205.128.0/19 DIR0 +ALL 62.216.32.0/21 DIR0 +ALL 62.221.32.0/22 DIR0 +ALL 62.221.37.0/24 DIR0 +ALL 62.221.38.0/23 DIR0 +ALL 62.221.40.0/21 DIR0 +ALL 62.221.48.0/20 DIR0 +ALL 62.221.64.0/19 DIR0 +ALL 62.244.0.0/18 DIR0 +ALL 64.18.0.0/20 DIR0 +ALL 64.233.160.0/19 DIR0 +ALL 66.102.0.0/20 DIR0 +ALL 66.249.64.0/19 DIR0 +ALL 72.14.192.0/18 DIR0 +ALL 74.125.0.0/16 DIR0 +ALL 77.47.128.0/17 DIR0 +ALL 77.52.0.0/16 DIR0 +ALL 77.73.88.0/21 DIR0 +ALL 77.75.144.0/21 DIR0 +ALL 77.75.156.0/24 DIR0 +ALL 77.87.32.0/21 DIR0 +ALL 77.87.144.0/20 DIR0 +ALL 77.87.192.0/21 DIR0 +ALL 77.88.0.0/18 DIR0 +ALL 77.88.192.0/18 DIR0 +ALL 77.90.192.0/18 DIR0 +ALL 77.91.128.0/18 DIR0 +ALL 77.93.32.0/20 DIR0 +ALL 77.93.48.0/22 DIR0 +ALL 77.94.160.0/19 DIR0 +ALL 77.95.16.0/21 DIR0 +ALL 77.109.0.0/18 DIR0 +ALL 77.120.32.0/20 DIR0 +ALL 77.120.48.0/22 DIR0 +ALL 77.120.56.0/21 DIR0 +ALL 77.120.64.0/18 DIR0 +ALL 77.120.128.0/18 DIR0 +ALL 77.120.192.0/19 DIR0 +ALL 77.120.224.0/20 DIR0 +ALL 77.120.240.0/22 DIR0 +ALL 77.121.0.0/16 DIR0 +ALL 77.122.0.0/15 DIR0 +ALL 77.222.128.0/19 DIR0 +ALL 77.235.96.0/19 DIR0 +ALL 77.239.160.0/21 DIR0 +ALL 77.239.168.0/24 DIR0 +ALL 77.239.170.0/23 DIR0 +ALL 77.239.172.0/22 DIR0 +ALL 77.239.176.0/20 DIR0 +ALL 77.242.160.0/20 DIR0 +ALL 77.244.32.0/21 DIR0 +ALL 77.244.40.0/22 DIR0 +ALL 77.244.44.0/23 DIR0 +ALL 77.247.16.0/20 DIR0 +ALL 77.247.216.0/21 DIR0 +ALL 78.24.72.0/21 DIR0 +ALL 78.25.0.0/19 DIR0 +ALL 78.25.32.0/20 DIR0 +ALL 78.25.48.0/21 DIR0 +ALL 78.25.58.0/23 DIR0 +ALL 78.25.60.0/22 DIR0 +ALL 78.26.128.0/17 DIR0 +ALL 78.27.128.0/17 DIR0 +ALL 78.30.192.0/18 DIR0 +ALL 78.31.176.0/21 DIR0 +ALL 78.31.232.0/21 DIR0 +ALL 78.31.248.0/21 DIR0 +ALL 78.109.16.0/20 DIR0 +ALL 78.111.16.0/21 DIR0 +ALL 78.111.176.0/20 DIR0 +ALL 78.111.208.0/20 DIR0 +ALL 78.137.0.0/19 DIR0 +ALL 78.137.32.0/24 DIR0 +ALL 78.137.34.0/24 DIR0 +ALL 78.152.160.0/19 DIR0 +ALL 78.154.160.0/19 DIR0 +ALL 78.159.32.0/19 DIR0 +ALL 79.110.16.0/20 DIR0 +ALL 79.110.32.0/19 DIR0 +ALL 79.110.64.0/20 DIR0 +ALL 79.110.96.0/20 DIR0 +ALL 79.110.128.0/18 DIR0 +ALL 79.110.208.0/20 DIR0 +ALL 79.110.224.0/20 DIR0 +ALL 79.124.96.0/21 DIR0 +ALL 79.124.104.0/22 DIR0 +ALL 79.124.108.0/23 DIR0 +ALL 79.124.110.0/24 DIR0 +ALL 79.124.128.0/17 DIR0 +ALL 79.135.192.0/19 DIR0 +ALL 79.140.0.0/20 DIR0 +ALL 79.142.192.0/20 DIR0 +ALL 79.143.32.0/20 DIR0 +ALL 79.171.120.0/21 DIR0 +ALL 79.174.0.0/19 DIR0 +ALL 80.67.208.0/20 DIR0 +ALL 80.70.64.0/20 DIR0 +ALL 80.70.80.0/24 DIR0 +ALL 80.70.82.0/23 DIR0 +ALL 80.73.0.0/20 DIR0 +ALL 80.77.32.0/20 DIR0 +ALL 80.78.32.0/19 DIR0 +ALL 80.84.176.0/20 DIR0 +ALL 80.87.148.0/22 DIR0 +ALL 80.87.152.0/22 DIR0 +ALL 80.90.230.0/23 DIR0 +ALL 80.90.236.0/24 DIR0 +ALL 80.90.238.0/23 DIR0 +ALL 80.91.160.0/19 DIR0 +ALL 80.92.224.0/20 DIR0 +ALL 80.93.112.0/20 DIR0 +ALL 80.94.240.0/20 DIR0 +ALL 80.243.144.0/20 DIR0 +ALL 80.245.112.0/20 DIR0 +ALL 80.249.224.0/20 DIR0 +ALL 80.252.128.0/19 DIR0 +ALL 80.252.240.0/20 DIR0 +ALL 80.254.0.0/20 DIR0 +ALL 80.255.64.0/20 DIR0 +ALL 81.17.128.0/20 DIR0 +ALL 81.21.0.0/20 DIR0 +ALL 81.22.128.0/20 DIR0 +ALL 81.23.16.0/20 DIR0 +ALL 81.24.208.0/20 DIR0 +ALL 81.25.224.0/20 DIR0 +ALL 81.26.144.0/20 DIR0 +ALL 81.30.160.0/20 DIR0 +ALL 81.90.224.0/20 DIR0 +ALL 81.95.176.0/20 DIR0 +ALL 82.144.192.0/19 DIR0 +ALL 82.193.96.0/19 DIR0 +ALL 83.97.104.0/21 DIR0 +ALL 83.137.88.0/21 DIR0 +ALL 83.138.48.0/24 DIR0 +ALL 83.138.51.0/24 DIR0 +ALL 83.138.52.0/22 DIR0 +ALL 83.142.232.0/21 DIR0 +ALL 83.143.232.0/21 DIR0 +ALL 83.170.192.0/18 DIR0 +ALL 83.218.224.0/19 DIR0 +ALL 84.47.128.0/18 DIR0 +ALL 85.90.192.0/19 DIR0 +ALL 85.91.96.0/19 DIR0 +ALL 85.114.192.0/19 DIR0 +ALL 85.117.129.0/24 DIR0 +ALL 85.159.0.0/21 DIR0 +ALL 85.198.128.0/18 DIR0 +ALL 85.202.160.0/20 DIR0 +ALL 85.202.192.0/20 DIR0 +ALL 85.223.128.0/17 DIR0 +ALL 85.238.96.0/19 DIR0 +ALL 86.110.192.0/23 DIR0 +ALL 86.111.224.0/21 DIR0 +ALL 87.238.152.0/23 DIR0 +ALL 87.238.155.0/24 DIR0 +ALL 87.238.157.0/24 DIR0 +ALL 87.238.158.0/23 DIR0 +ALL 87.250.224.0/19 DIR0 +ALL 88.81.224.0/19 DIR0 +ALL 88.84.192.0/19 DIR0 +ALL 88.154.0.0/15 DIR0 +ALL 88.208.8.0/23 DIR0 +ALL 88.214.64.0/18 DIR0 +ALL 89.19.96.0/19 DIR0 +ALL 89.21.64.0/19 DIR0 +ALL 89.28.200.0/21 DIR0 +ALL 89.105.224.0/21 DIR0 +ALL 89.105.236.0/22 DIR0 +ALL 89.105.240.0/20 DIR0 +ALL 89.107.24.0/21 DIR0 +ALL 89.162.128.0/17 DIR0 +ALL 89.185.0.0/19 DIR0 +ALL 89.187.0.0/23 DIR0 +ALL 89.187.3.0/24 DIR0 +ALL 89.187.4.0/24 DIR0 +ALL 89.200.232.0/21 DIR0 +ALL 89.200.248.0/21 DIR0 +ALL 89.207.184.0/21 DIR0 +ALL 89.209.0.0/16 DIR0 +ALL 89.248.238.0/23 DIR0 +ALL 89.251.16.0/21 DIR0 +ALL 89.252.0.0/19 DIR0 +ALL 89.252.32.0/20 DIR0 +ALL 89.252.48.0/21 DIR0 +ALL 89.252.56.0/22 DIR0 +ALL 89.252.60.0/23 DIR0 +ALL 89.252.62.0/24 DIR0 +ALL 91.90.8.0/21 DIR0 +ALL 91.90.16.0/21 DIR0 +ALL 91.103.120.0/21 DIR0 +ALL 91.123.144.0/20 DIR0 +ALL 91.142.160.0/20 DIR0 +ALL 91.145.192.0/18 DIR0 +ALL 91.189.128.0/21 DIR0 +ALL 91.192.4.0/22 DIR0 +ALL 91.192.44.0/22 DIR0 +ALL 91.192.84.0/22 DIR0 +ALL 91.192.128.0/22 DIR0 +ALL 91.192.136.0/22 DIR0 +ALL 91.192.152.0/21 DIR0 +ALL 91.192.160.0/22 DIR0 +ALL 91.192.180.0/22 DIR0 +ALL 91.192.184.0/22 DIR0 +ALL 91.192.216.0/22 DIR0 +ALL 91.193.8.0/22 DIR0 +ALL 91.193.32.0/22 DIR0 +ALL 91.193.68.0/23 DIR0 +ALL 91.193.76.0/22 DIR0 +ALL 91.193.80.0/22 DIR0 +ALL 91.193.104.0/22 DIR0 +ALL 91.193.124.0/22 DIR0 +ALL 91.193.164.0/22 DIR0 +ALL 91.193.172.0/22 DIR0 +ALL 91.193.204.0/22 DIR0 +ALL 91.193.220.0/22 DIR0 +ALL 91.193.232.0/22 DIR0 +ALL 91.193.252.0/22 DIR0 +ALL 91.194.34.0/23 DIR0 +ALL 91.194.50.0/23 DIR0 +ALL 91.194.56.0/23 DIR0 +ALL 91.194.72.0/23 DIR0 +ALL 91.194.78.0/23 DIR0 +ALL 91.194.80.0/23 DIR0 +ALL 91.194.88.0/23 DIR0 +ALL 91.194.124.0/23 DIR0 +ALL 91.194.134.0/23 DIR0 +ALL 91.194.162.0/23 DIR0 +ALL 91.194.238.0/23 DIR0 +ALL 91.194.250.0/23 DIR0 +ALL 91.195.10.0/23 DIR0 +ALL 91.195.12.0/23 DIR0 +ALL 91.195.20.0/23 DIR0 +ALL 91.195.52.0/23 DIR0 +ALL 91.195.68.0/23 DIR0 +ALL 91.195.74.0/23 DIR0 +ALL 91.195.86.0/23 DIR0 +ALL 91.195.90.0/23 DIR0 +ALL 91.195.96.0/23 DIR0 +ALL 91.195.120.0/23 DIR0 +ALL 91.195.156.0/23 DIR0 +ALL 91.195.172.0/23 DIR0 +ALL 91.195.184.0/23 DIR0 +ALL 91.195.214.0/23 DIR0 +ALL 91.195.230.0/23 DIR0 +ALL 91.195.244.0/23 DIR0 +ALL 91.195.248.0/23 DIR0 +ALL 91.196.0.0/22 DIR0 +ALL 91.196.52.0/22 DIR0 +ALL 91.196.60.0/22 DIR0 +ALL 91.196.80.0/22 DIR0 +ALL 91.196.88.0/21 DIR0 +ALL 91.196.96.0/21 DIR0 +ALL 91.196.120.0/22 DIR0 +ALL 91.196.132.0/22 DIR0 +ALL 91.196.148.0/22 DIR0 +ALL 91.196.156.0/22 DIR0 +ALL 91.196.160.0/24 DIR0 +ALL 91.196.164.0/22 DIR0 +ALL 91.196.178.0/24 DIR0 +ALL 91.196.192.0/22 DIR0 +ALL 91.196.196.0/23 DIR0 +ALL 91.196.228.0/22 DIR0 +ALL 91.197.4.0/22 DIR0 +ALL 91.197.16.0/22 DIR0 +ALL 91.197.44.0/22 DIR0 +ALL 91.197.48.0/22 DIR0 +ALL 91.197.56.0/22 DIR0 +ALL 91.197.80.0/22 DIR0 +ALL 91.197.128.0/21 DIR0 +ALL 91.197.144.0/22 DIR0 +ALL 91.197.168.0/22 DIR0 +ALL 91.197.184.0/22 DIR0 +ALL 91.197.216.0/21 DIR0 +ALL 91.197.236.0/22 DIR0 +ALL 91.197.252.0/22 DIR0 +ALL 91.198.1.0/24 DIR0 +ALL 91.198.10.0/24 DIR0 +ALL 91.198.20.0/24 DIR0 +ALL 91.198.34.0/24 DIR0 +ALL 91.198.36.0/24 DIR0 +ALL 91.198.50.0/24 DIR0 +ALL 91.198.83.0/24 DIR0 +ALL 91.198.86.0/24 DIR0 +ALL 91.198.101.0/24 DIR0 +ALL 91.198.116.0/24 DIR0 +ALL 91.198.133.0/24 DIR0 +ALL 91.198.140.0/24 DIR0 +ALL 91.198.143.0/24 DIR0 +ALL 91.198.153.0/24 DIR0 +ALL 91.198.175.0/24 DIR0 +ALL 91.198.188.0/24 DIR0 +ALL 91.198.233.0/24 DIR0 +ALL 91.198.235.0/24 DIR0 +ALL 91.198.249.0/24 DIR0 +ALL 91.199.13.0/24 DIR0 +ALL 91.199.28.0/24 DIR0 +ALL 91.199.33.0/24 DIR0 +ALL 91.199.35.0/24 DIR0 +ALL 91.199.37.0/24 DIR0 +ALL 91.199.54.0/24 DIR0 +ALL 91.199.75.0/24 DIR0 +ALL 91.199.82.0/24 DIR0 +ALL 91.199.91.0/24 DIR0 +ALL 91.199.92.0/23 DIR0 +ALL 91.199.106.0/24 DIR0 +ALL 91.199.115.0/24 DIR0 +ALL 91.199.138.0/23 DIR0 +ALL 91.199.144.0/24 DIR0 +ALL 91.199.182.0/24 DIR0 +ALL 91.199.188.0/24 DIR0 +ALL 91.199.194.0/24 DIR0 +ALL 91.199.206.0/24 DIR0 +ALL 91.199.222.0/24 DIR0 +ALL 91.199.245.0/24 DIR0 +ALL 91.200.0.0/20 DIR0 +ALL 91.200.40.0/23 DIR0 +ALL 91.200.44.0/22 DIR0 +ALL 91.200.52.0/22 DIR0 +ALL 91.200.56.0/22 DIR0 +ALL 91.200.72.0/22 DIR0 +ALL 91.200.104.0/22 DIR0 +ALL 91.200.112.0/22 DIR0 +ALL 91.200.136.0/22 DIR0 +ALL 91.200.160.0/22 DIR0 +ALL 91.200.180.0/22 DIR0 +ALL 91.200.200.0/22 DIR0 +ALL 91.200.212.0/22 DIR0 +ALL 91.200.220.0/22 DIR0 +ALL 91.200.232.0/22 DIR0 +ALL 91.200.244.0/22 DIR0 +ALL 91.200.248.0/21 DIR0 +ALL 91.201.24.0/22 DIR0 +ALL 91.201.36.0/22 DIR0 +ALL 91.201.40.0/22 DIR0 +ALL 91.201.68.0/22 DIR0 +ALL 91.201.84.0/22 DIR0 +ALL 91.201.96.0/22 DIR0 +ALL 91.201.108.0/22 DIR0 +ALL 91.201.124.0/22 DIR0 +ALL 91.201.144.0/22 DIR0 +ALL 91.201.156.0/22 DIR0 +ALL 91.201.168.0/22 DIR0 +ALL 91.201.180.0/22 DIR0 +ALL 91.201.188.0/22 DIR0 +ALL 91.201.196.0/22 DIR0 +ALL 91.201.212.0/22 DIR0 +ALL 91.201.224.0/22 DIR0 +ALL 91.201.232.0/21 DIR0 +ALL 91.201.240.0/21 DIR0 +ALL 91.201.252.0/22 DIR0 +ALL 91.202.0.0/22 DIR0 +ALL 91.202.8.0/22 DIR0 +ALL 91.202.39.0/24 DIR0 +ALL 91.202.52.0/22 DIR0 +ALL 91.202.56.0/22 DIR0 +ALL 91.202.72.0/22 DIR0 +ALL 91.202.104.0/21 DIR0 +ALL 91.202.128.0/21 DIR0 +ALL 91.202.144.0/22 DIR0 +ALL 91.202.160.0/22 DIR0 +ALL 91.202.208.0/21 DIR0 +ALL 91.202.232.0/22 DIR0 +ALL 91.202.240.0/21 DIR0 +ALL 91.203.4.0/22 DIR0 +ALL 91.203.12.0/22 DIR0 +ALL 91.203.24.0/22 DIR0 +ALL 91.203.48.0/22 DIR0 +ALL 91.203.60.0/22 DIR0 +ALL 91.203.76.0/22 DIR0 +ALL 91.203.88.0/21 DIR0 +ALL 91.203.112.0/22 DIR0 +ALL 91.203.136.0/21 DIR0 +ALL 91.203.144.0/22 DIR0 +ALL 91.203.164.0/22 DIR0 +ALL 91.204.36.0/22 DIR0 +ALL 91.204.40.0/21 DIR0 +ALL 91.204.48.0/22 DIR0 +ALL 91.204.60.0/22 DIR0 +ALL 91.204.76.0/22 DIR0 +ALL 91.204.84.0/22 DIR0 +ALL 91.204.92.0/22 DIR0 +ALL 91.204.120.0/22 DIR0 +ALL 91.204.132.0/22 DIR0 +ALL 91.204.180.0/22 DIR0 +ALL 91.204.196.0/22 DIR0 +ALL 91.204.212.0/22 DIR0 +ALL 91.205.16.0/22 DIR0 +ALL 91.205.64.0/22 DIR0 +ALL 91.205.80.0/22 DIR0 +ALL 91.205.108.0/22 DIR0 +ALL 91.205.164.0/22 DIR0 +ALL 91.206.110.0/23 DIR0 +ALL 91.206.186.0/23 DIR0 +ALL 91.206.200.0/23 DIR0 +ALL 91.206.212.0/23 DIR0 +ALL 91.206.218.0/23 DIR0 +ALL 91.206.226.0/23 DIR0 +ALL 91.207.4.0/22 DIR0 +ALL 91.207.8.0/23 DIR0 +ALL 91.207.44.0/22 DIR0 +ALL 91.207.54.0/23 DIR0 +ALL 91.207.60.0/23 DIR0 +ALL 91.207.98.0/23 DIR0 +ALL 91.207.122.0/23 DIR0 +ALL 91.207.146.0/23 DIR0 +ALL 91.207.210.0/23 DIR0 +ALL 91.207.224.0/23 DIR0 +ALL 91.208.25.0/24 DIR0 +ALL 91.208.52.0/24 DIR0 +ALL 91.208.65.0/24 DIR0 +ALL 91.208.97.0/24 DIR0 +ALL 91.208.116.0/24 DIR0 +ALL 91.208.127.0/24 DIR0 +ALL 91.208.153.0/24 DIR0 +ALL 91.208.154.0/24 DIR0 +ALL 91.208.208.0/24 DIR0 +ALL 91.209.11.0/24 DIR0 +ALL 91.209.24.0/24 DIR0 +ALL 91.209.54.0/24 DIR0 +ALL 91.210.8.0/21 DIR0 +ALL 91.210.20.0/22 DIR0 +ALL 91.210.28.0/22 DIR0 +ALL 91.210.32.0/21 DIR0 +ALL 91.210.92.0/22 DIR0 +ALL 91.210.96.0/22 DIR0 +ALL 91.210.120.0/22 DIR0 +ALL 91.210.148.0/22 DIR0 +ALL 92.49.192.0/21 DIR0 +ALL 92.49.208.0/20 DIR0 +ALL 92.49.224.0/19 DIR0 +ALL 92.240.96.0/21 DIR0 +ALL 92.240.104.0/22 DIR0 +ALL 92.240.112.0/21 DIR0 +ALL 92.240.120.0/22 DIR0 +ALL 92.240.124.0/23 DIR0 +ALL 92.240.126.0/24 DIR0 +ALL 92.242.96.0/19 DIR0 +ALL 92.244.96.0/19 DIR0 +ALL 92.249.64.0/18 DIR0 +ALL 93.72.0.0/13 DIR0 +ALL 93.89.208.0/20 DIR0 +ALL 93.126.64.0/18 DIR0 +ALL 93.127.0.0/24 DIR0 +ALL 93.127.6.0/23 DIR0 +ALL 93.127.8.0/21 DIR0 +ALL 93.127.16.0/20 DIR0 +ALL 93.127.32.0/21 DIR0 +ALL 93.127.48.0/21 DIR0 +ALL 93.157.8.0/21 DIR0 +ALL 93.157.24.0/21 DIR0 +ALL 93.158.128.0/18 DIR0 +ALL 93.175.224.0/20 DIR0 +ALL 93.178.192.0/22 DIR0 +ALL 93.178.204.0/23 DIR0 +ALL 93.178.206.0/24 DIR0 +ALL 93.178.210.0/23 DIR0 +ALL 93.180.192.0/18 DIR0 +ALL 93.183.192.0/18 DIR0 +ALL 93.185.192.0/19 DIR0 +ALL 93.188.32.0/21 DIR0 +ALL 93.190.40.0/21 DIR0 +ALL 94.27.0.0/17 DIR0 +ALL 94.74.64.0/18 DIR0 +ALL 94.76.96.0/21 DIR0 +ALL 94.100.208.0/20 DIR0 +ALL 94.124.160.0/21 DIR0 +ALL 94.125.120.0/21 DIR0 +ALL 94.130.0.0/15 DIR0 +ALL 94.153.0.0/16 DIR0 +ALL 94.154.0.0/17 DIR0 +ALL 94.154.128.0/18 DIR0 +ALL 94.158.16.0/20 DIR0 +ALL 94.158.32.0/20 DIR0 +ALL 94.158.64.0/19 DIR0 +ALL 94.158.144.0/20 DIR0 +ALL 94.240.128.0/18 DIR0 +ALL 94.248.0.0/17 DIR0 +ALL 193.0.227.0/24 DIR0 +ALL 193.0.228.0/24 DIR0 +ALL 193.0.240.0/24 DIR0 +ALL 193.0.247.0/24 DIR0 +ALL 193.16.45.0/24 DIR0 +ALL 193.16.47.0/24 DIR0 +ALL 193.16.101.0/24 DIR0 +ALL 193.16.158.0/24 DIR0 +ALL 193.16.233.0/24 DIR0 +ALL 193.16.247.0/24 DIR0 +ALL 193.17.46.0/24 DIR0 +ALL 193.17.69.0/24 DIR0 +ALL 193.17.75.0/24 DIR0 +ALL 193.17.174.0/24 DIR0 +ALL 193.17.208.0/24 DIR0 +ALL 193.17.213.0/24 DIR0 +ALL 193.17.216.0/23 DIR0 +ALL 193.17.253.0/24 DIR0 +ALL 193.19.74.0/23 DIR0 +ALL 193.19.84.0/22 DIR0 +ALL 193.19.100.0/23 DIR0 +ALL 193.19.108.0/22 DIR0 +ALL 193.19.132.0/22 DIR0 +ALL 193.19.144.0/22 DIR0 +ALL 193.19.152.0/23 DIR0 +ALL 193.19.184.0/22 DIR0 +ALL 193.19.228.0/22 DIR0 +ALL 193.19.240.0/21 DIR0 +ALL 193.19.252.0/22 DIR0 +ALL 193.22.84.0/24 DIR0 +ALL 193.22.140.0/24 DIR0 +ALL 193.23.53.0/24 DIR0 +ALL 193.23.60.0/24 DIR0 +ALL 193.23.122.0/24 DIR0 +ALL 193.23.157.0/24 DIR0 +ALL 193.23.181.0/24 DIR0 +ALL 193.23.183.0/24 DIR0 +ALL 193.23.225.0/24 DIR0 +ALL 193.24.25.0/24 DIR0 +ALL 193.24.30.0/24 DIR0 +ALL 193.25.176.0/23 DIR0 +ALL 193.25.255.0/24 DIR0 +ALL 193.26.3.0/24 DIR0 +ALL 193.26.13.0/24 DIR0 +ALL 193.26.20.0/24 DIR0 +ALL 193.26.27.0/24 DIR0 +ALL 193.26.134.0/24 DIR0 +ALL 193.27.0.0/24 DIR0 +ALL 193.27.47.0/24 DIR0 +ALL 193.27.80.0/23 DIR0 +ALL 193.27.234.0/23 DIR0 +ALL 193.27.242.0/23 DIR0 +ALL 193.28.85.0/24 DIR0 +ALL 193.28.87.0/24 DIR0 +ALL 193.28.92.0/24 DIR0 +ALL 193.28.156.0/24 DIR0 +ALL 193.28.177.0/24 DIR0 +ALL 193.28.184.0/24 DIR0 +ALL 193.28.186.0/24 DIR0 +ALL 193.28.190.0/24 DIR0 +ALL 193.28.200.0/24 DIR0 +ALL 193.29.203.0/24 DIR0 +ALL 193.29.204.0/24 DIR0 +ALL 193.29.220.0/24 DIR0 +ALL 193.30.240.0/22 DIR0 +ALL 193.32.21.0/24 DIR0 +ALL 193.33.48.0/23 DIR0 +ALL 193.33.54.0/23 DIR0 +ALL 193.33.64.0/23 DIR0 +ALL 193.33.104.0/23 DIR0 +ALL 193.33.146.0/23 DIR0 +ALL 193.33.172.0/23 DIR0 +ALL 193.33.194.0/23 DIR0 +ALL 193.33.196.0/23 DIR0 +ALL 193.33.202.0/23 DIR0 +ALL 193.33.206.0/23 DIR0 +ALL 193.33.212.0/23 DIR0 +ALL 193.33.236.0/23 DIR0 +ALL 193.34.20.0/22 DIR0 +ALL 193.34.60.0/22 DIR0 +ALL 193.34.72.0/21 DIR0 +ALL 193.34.92.0/22 DIR0 +ALL 193.34.128.0/23 DIR0 +ALL 193.34.140.0/23 DIR0 +ALL 193.34.154.0/23 DIR0 +ALL 193.34.168.0/23 DIR0 +ALL 193.34.172.0/23 DIR0 +ALL 193.35.25.0/24 DIR0 +ALL 193.37.133.0/24 DIR0 +ALL 193.37.141.0/24 DIR0 +ALL 193.37.156.0/24 DIR0 +ALL 193.39.69.0/24 DIR0 +ALL 193.39.72.0/24 DIR0 +ALL 193.39.75.0/24 DIR0 +ALL 193.39.76.0/23 DIR0 +ALL 193.39.114.0/24 DIR0 +ALL 193.39.118.0/24 DIR0 +ALL 193.41.4.0/23 DIR0 +ALL 193.41.38.0/24 DIR0 +ALL 193.41.48.0/23 DIR0 +ALL 193.41.51.0/24 DIR0 +ALL 193.41.60.0/22 DIR0 +ALL 193.41.80.0/24 DIR0 +ALL 193.41.88.0/24 DIR0 +ALL 193.41.128.0/22 DIR0 +ALL 193.41.160.0/22 DIR0 +ALL 193.41.172.0/22 DIR0 +ALL 193.41.184.0/22 DIR0 +ALL 193.41.218.0/23 DIR0 +ALL 193.41.239.0/24 DIR0 +ALL 193.43.95.0/24 DIR0 +ALL 193.43.222.0/23 DIR0 +ALL 193.43.248.0/21 DIR0 +ALL 193.46.46.0/24 DIR0 +ALL 193.46.66.0/24 DIR0 +ALL 193.46.81.0/24 DIR0 +ALL 193.46.86.0/24 DIR0 +ALL 193.46.89.0/24 DIR0 +ALL 193.46.201.0/24 DIR0 +ALL 193.46.210.0/24 DIR0 +ALL 193.47.85.0/24 DIR0 +ALL 193.47.137.0/24 DIR0 +ALL 193.47.145.0/24 DIR0 +ALL 193.47.166.0/24 DIR0 +ALL 193.58.251.0/24 DIR0 +ALL 193.84.17.0/24 DIR0 +ALL 193.84.23.0/24 DIR0 +ALL 193.84.50.0/24 DIR0 +ALL 193.84.72.0/24 DIR0 +ALL 193.84.76.0/23 DIR0 +ALL 193.84.90.0/24 DIR0 +ALL 193.93.12.0/22 DIR0 +ALL 193.93.16.0/22 DIR0 +ALL 193.93.48.0/22 DIR0 +ALL 193.93.76.0/22 DIR0 +ALL 193.93.100.0/22 DIR0 +ALL 193.93.108.0/22 DIR0 +ALL 193.93.116.0/22 DIR0 +ALL 193.93.160.0/22 DIR0 +ALL 193.93.184.0/21 DIR0 +ALL 193.93.192.0/22 DIR0 +ALL 193.93.228.0/22 DIR0 +ALL 193.108.38.0/23 DIR0 +ALL 193.108.46.0/23 DIR0 +ALL 193.108.48.0/22 DIR0 +ALL 193.108.56.0/22 DIR0 +ALL 193.108.102.0/23 DIR0 +ALL 193.108.104.0/23 DIR0 +ALL 193.108.112.0/21 DIR0 +ALL 193.108.120.0/22 DIR0 +ALL 193.108.128.0/22 DIR0 +ALL 193.108.162.0/23 DIR0 +ALL 193.108.170.0/23 DIR0 +ALL 193.108.209.0/24 DIR0 +ALL 193.108.226.0/23 DIR0 +ALL 193.108.236.0/23 DIR0 +ALL 193.108.240.0/22 DIR0 +ALL 193.108.248.0/22 DIR0 +ALL 193.109.8.0/22 DIR0 +ALL 193.109.80.0/24 DIR0 +ALL 193.109.93.0/24 DIR0 +ALL 193.109.100.0/22 DIR0 +ALL 193.109.118.0/24 DIR0 +ALL 193.109.128.0/23 DIR0 +ALL 193.109.144.0/22 DIR0 +ALL 193.109.160.0/21 DIR0 +ALL 193.109.240.0/23 DIR0 +ALL 193.109.248.0/23 DIR0 +ALL 193.110.16.0/21 DIR0 +ALL 193.110.72.0/21 DIR0 +ALL 193.110.89.0/24 DIR0 +ALL 193.110.106.0/23 DIR0 +ALL 193.110.112.0/22 DIR0 +ALL 193.110.124.0/22 DIR0 +ALL 193.110.160.0/22 DIR0 +ALL 193.110.172.0/22 DIR0 +ALL 193.110.176.0/23 DIR0 +ALL 193.110.184.0/23 DIR0 +ALL 193.110.188.0/23 DIR0 +ALL 193.111.6.0/23 DIR0 +ALL 193.111.8.0/23 DIR0 +ALL 193.111.83.0/24 DIR0 +ALL 193.111.114.0/23 DIR0 +ALL 193.111.126.0/23 DIR0 +ALL 193.111.156.0/22 DIR0 +ALL 193.111.173.0/24 DIR0 +ALL 193.111.188.0/22 DIR0 +ALL 193.111.204.0/23 DIR0 +ALL 193.111.239.0/24 DIR0 +ALL 193.111.240.0/22 DIR0 +ALL 193.111.248.0/22 DIR0 +ALL 193.124.48.0/24 DIR0 +ALL 193.124.54.0/24 DIR0 +ALL 193.124.57.0/24 DIR0 +ALL 193.124.59.0/24 DIR0 +ALL 193.124.60.0/23 DIR0 +ALL 193.124.70.0/24 DIR0 +ALL 193.124.76.0/22 DIR0 +ALL 193.124.229.0/24 DIR0 +ALL 193.138.77.0/24 DIR0 +ALL 193.138.84.0/24 DIR0 +ALL 193.138.87.0/24 DIR0 +ALL 193.138.93.0/24 DIR0 +ALL 193.138.114.0/24 DIR0 +ALL 193.138.122.0/24 DIR0 +ALL 193.138.132.0/22 DIR0 +ALL 193.138.144.0/22 DIR0 +ALL 193.138.184.0/22 DIR0 +ALL 193.138.236.0/22 DIR0 +ALL 193.138.244.0/22 DIR0 +ALL 193.142.124.0/24 DIR0 +ALL 193.142.213.0/24 DIR0 +ALL 193.142.218.0/23 DIR0 +ALL 193.142.221.0/24 DIR0 +ALL 193.151.12.0/22 DIR0 +ALL 193.151.56.0/22 DIR0 +ALL 193.151.104.0/22 DIR0 +ALL 193.151.240.0/21 DIR0 +ALL 193.151.252.0/22 DIR0 +ALL 193.164.92.0/22 DIR0 +ALL 193.164.130.0/24 DIR0 +ALL 193.164.149.0/24 DIR0 +ALL 193.178.34.0/24 DIR0 +ALL 193.178.124.0/22 DIR0 +ALL 193.178.144.0/22 DIR0 +ALL 193.178.162.0/24 DIR0 +ALL 193.178.190.0/23 DIR0 +ALL 193.178.228.0/23 DIR0 +ALL 193.178.236.0/23 DIR0 +ALL 193.178.248.0/22 DIR0 +ALL 193.186.9.0/24 DIR0 +ALL 193.186.15.0/24 DIR0 +ALL 193.188.254.0/24 DIR0 +ALL 193.189.96.0/23 DIR0 +ALL 193.189.126.0/23 DIR0 +ALL 193.192.36.0/23 DIR0 +ALL 193.193.192.0/19 DIR0 +ALL 193.200.22.0/24 DIR0 +ALL 193.200.32.0/23 DIR0 +ALL 193.200.36.0/22 DIR0 +ALL 193.200.64.0/23 DIR0 +ALL 193.200.68.0/23 DIR0 +ALL 193.200.84.0/23 DIR0 +ALL 193.200.151.0/24 DIR0 +ALL 193.200.160.0/23 DIR0 +ALL 193.200.173.0/24 DIR0 +ALL 193.200.175.0/24 DIR0 +ALL 193.200.179.0/24 DIR0 +ALL 193.200.183.0/24 DIR0 +ALL 193.200.190.0/24 DIR0 +ALL 193.200.205.0/24 DIR0 +ALL 193.200.209.0/24 DIR0 +ALL 193.200.212.0/24 DIR0 +ALL 193.200.219.0/24 DIR0 +ALL 193.200.229.0/24 DIR0 +ALL 193.200.248.0/24 DIR0 +ALL 193.200.255.0/24 DIR0 +ALL 193.201.60.0/22 DIR0 +ALL 193.201.80.0/22 DIR0 +ALL 193.201.98.0/23 DIR0 +ALL 193.201.100.0/24 DIR0 +ALL 193.201.116.0/23 DIR0 +ALL 193.201.140.0/22 DIR0 +ALL 193.201.175.0/24 DIR0 +ALL 193.201.198.0/23 DIR0 +ALL 193.201.206.0/23 DIR0 +ALL 193.201.208.0/22 DIR0 +ALL 193.201.216.0/22 DIR0 +ALL 193.201.224.0/22 DIR0 +ALL 193.202.21.0/24 DIR0 +ALL 193.202.110.0/24 DIR0 +ALL 193.202.118.0/24 DIR0 +ALL 193.203.110.0/23 DIR0 +ALL 193.203.218.0/23 DIR0 +ALL 193.203.236.0/23 DIR0 +ALL 193.218.144.0/22 DIR0 +ALL 193.219.99.0/24 DIR0 +ALL 193.219.124.0/24 DIR0 +ALL 193.222.111.0/24 DIR0 +ALL 193.222.140.0/24 DIR0 +ALL 193.223.98.0/24 DIR0 +ALL 193.227.97.0/24 DIR0 +ALL 193.227.115.0/24 DIR0 +ALL 193.227.119.0/24 DIR0 +ALL 193.227.120.0/24 DIR0 +ALL 193.227.206.0/23 DIR0 +ALL 193.227.208.0/22 DIR0 +ALL 193.227.230.0/23 DIR0 +ALL 193.227.250.0/23 DIR0 +ALL 193.228.2.0/24 DIR0 +ALL 193.232.65.0/24 DIR0 +ALL 193.238.20.0/22 DIR0 +ALL 193.238.32.0/22 DIR0 +ALL 193.238.96.0/22 DIR0 +ALL 193.238.108.0/22 DIR0 +ALL 193.238.116.0/22 DIR0 +ALL 193.238.152.0/22 DIR0 +ALL 193.238.192.0/22 DIR0 +ALL 193.239.24.0/22 DIR0 +ALL 193.239.68.0/23 DIR0 +ALL 193.239.72.0/22 DIR0 +ALL 193.239.128.0/23 DIR0 +ALL 193.239.132.0/24 DIR0 +ALL 193.239.142.0/23 DIR0 +ALL 193.239.152.0/23 DIR0 +ALL 193.239.170.0/23 DIR0 +ALL 193.239.178.0/23 DIR0 +ALL 193.239.228.0/23 DIR0 +ALL 193.239.234.0/23 DIR0 +ALL 193.239.238.0/23 DIR0 +ALL 193.239.250.0/23 DIR0 +ALL 193.239.254.0/23 DIR0 +ALL 193.242.114.0/24 DIR0 +ALL 193.243.152.0/23 DIR0 +ALL 193.243.156.0/22 DIR0 +ALL 193.254.196.0/23 DIR0 +ALL 193.254.216.0/22 DIR0 +ALL 193.254.220.0/23 DIR0 +ALL 193.254.224.0/22 DIR0 +ALL 193.254.232.0/22 DIR0 +ALL 194.0.88.0/22 DIR0 +ALL 194.0.104.0/22 DIR0 +ALL 194.0.116.0/23 DIR0 +ALL 194.0.131.0/24 DIR0 +ALL 194.0.138.0/24 DIR0 +ALL 194.0.148.0/24 DIR0 +ALL 194.0.150.0/24 DIR0 +ALL 194.0.187.0/24 DIR0 +ALL 194.0.200.0/24 DIR0 +ALL 194.0.206.0/24 DIR0 +ALL 194.0.218.0/24 DIR0 +ALL 194.0.231.0/24 DIR0 +ALL 194.1.193.0/24 DIR0 +ALL 194.1.195.0/24 DIR0 +ALL 194.6.196.0/22 DIR0 +ALL 194.6.231.0/24 DIR0 +ALL 194.6.232.0/23 DIR0 +ALL 194.8.51.0/24 DIR0 +ALL 194.8.56.0/24 DIR0 +ALL 194.8.64.0/23 DIR0 +ALL 194.9.0.0/23 DIR0 +ALL 194.9.14.0/23 DIR0 +ALL 194.9.26.0/23 DIR0 +ALL 194.9.36.0/23 DIR0 +ALL 194.9.50.0/23 DIR0 +ALL 194.9.68.0/23 DIR0 +ALL 194.15.147.0/24 DIR0 +ALL 194.24.162.0/23 DIR0 +ALL 194.24.182.0/23 DIR0 +ALL 194.24.184.0/22 DIR0 +ALL 194.24.190.0/23 DIR0 +ALL 194.24.236.0/23 DIR0 +ALL 194.24.246.0/23 DIR0 +ALL 194.29.60.0/22 DIR0 +ALL 194.29.184.0/22 DIR0 +ALL 194.29.205.0/24 DIR0 +ALL 194.30.163.0/24 DIR0 +ALL 194.30.168.0/24 DIR0 +ALL 194.30.170.0/24 DIR0 +ALL 194.30.172.0/24 DIR0 +ALL 194.33.15.0/24 DIR0 +ALL 194.33.180.0/23 DIR0 +ALL 194.33.188.0/23 DIR0 +ALL 194.37.248.0/24 DIR0 +ALL 194.42.192.0/20 DIR0 +ALL 194.44.0.0/24 DIR0 +ALL 194.44.2.0/23 DIR0 +ALL 194.44.5.0/24 DIR0 +ALL 194.44.7.0/24 DIR0 +ALL 194.44.8.0/22 DIR0 +ALL 194.44.13.0/24 DIR0 +ALL 194.44.14.0/23 DIR0 +ALL 194.44.16.0/22 DIR0 +ALL 194.44.21.0/24 DIR0 +ALL 194.44.22.0/23 DIR0 +ALL 194.44.24.0/23 DIR0 +ALL 194.44.27.0/24 DIR0 +ALL 194.44.28.0/22 DIR0 +ALL 194.44.32.0/20 DIR0 +ALL 194.44.48.0/24 DIR0 +ALL 194.44.50.0/24 DIR0 +ALL 194.44.53.0/24 DIR0 +ALL 194.44.54.0/23 DIR0 +ALL 194.44.56.0/21 DIR0 +ALL 194.44.64.0/24 DIR0 +ALL 194.44.66.0/23 DIR0 +ALL 194.44.69.0/24 DIR0 +ALL 194.44.70.0/23 DIR0 +ALL 194.44.72.0/21 DIR0 +ALL 194.44.80.0/22 DIR0 +ALL 194.44.88.0/23 DIR0 +ALL 194.44.91.0/24 DIR0 +ALL 194.44.92.0/22 DIR0 +ALL 194.44.96.0/21 DIR0 +ALL 194.44.104.0/22 DIR0 +ALL 194.44.108.0/23 DIR0 +ALL 194.44.111.0/24 DIR0 +ALL 194.44.112.0/23 DIR0 +ALL 194.44.114.0/24 DIR0 +ALL 194.44.116.0/22 DIR0 +ALL 194.44.120.0/22 DIR0 +ALL 194.44.126.0/23 DIR0 +ALL 194.44.128.0/20 DIR0 +ALL 194.44.144.0/21 DIR0 +ALL 194.44.152.0/22 DIR0 +ALL 194.44.156.0/23 DIR0 +ALL 194.44.158.0/24 DIR0 +ALL 194.44.160.0/23 DIR0 +ALL 194.44.163.0/24 DIR0 +ALL 194.44.164.0/22 DIR0 +ALL 194.44.168.0/21 DIR0 +ALL 194.44.176.0/22 DIR0 +ALL 194.44.181.0/24 DIR0 +ALL 194.44.182.0/23 DIR0 +ALL 194.44.184.0/22 DIR0 +ALL 194.44.188.0/24 DIR0 +ALL 194.44.190.0/23 DIR0 +ALL 194.44.192.0/18 DIR0 +ALL 194.48.175.0/24 DIR0 +ALL 194.48.212.0/24 DIR0 +ALL 194.50.0.0/24 DIR0 +ALL 194.50.9.0/24 DIR0 +ALL 194.50.85.0/24 DIR0 +ALL 194.50.98.0/24 DIR0 +ALL 194.50.114.0/24 DIR0 +ALL 194.50.116.0/24 DIR0 +ALL 194.50.119.0/24 DIR0 +ALL 194.50.125.0/24 DIR0 +ALL 194.50.161.0/24 DIR0 +ALL 194.50.167.0/24 DIR0 +ALL 194.50.169.0/24 DIR0 +ALL 194.50.254.0/24 DIR0 +ALL 194.54.80.0/22 DIR0 +ALL 194.54.88.0/22 DIR0 +ALL 194.54.152.0/21 DIR0 +ALL 194.54.184.0/22 DIR0 +ALL 194.58.82.0/24 DIR0 +ALL 194.60.69.0/24 DIR0 +ALL 194.60.77.0/24 DIR0 +ALL 194.63.140.0/22 DIR0 +ALL 194.79.8.0/22 DIR0 +ALL 194.79.20.0/22 DIR0 +ALL 194.79.60.0/22 DIR0 +ALL 194.88.1.0/24 DIR0 +ALL 194.88.138.0/23 DIR0 +ALL 194.88.150.0/23 DIR0 +ALL 194.88.152.0/23 DIR0 +ALL 194.88.206.0/23 DIR0 +ALL 194.88.218.0/23 DIR0 +ALL 194.88.220.0/23 DIR0 +ALL 194.93.160.0/19 DIR0 +ALL 194.99.240.0/22 DIR0 +ALL 194.105.136.0/23 DIR0 +ALL 194.105.144.0/23 DIR0 +ALL 194.106.208.0/23 DIR0 +ALL 194.106.216.0/22 DIR0 +ALL 194.107.21.0/24 DIR0 +ALL 194.110.79.0/24 DIR0 +ALL 194.110.126.0/24 DIR0 +ALL 194.110.129.0/24 DIR0 +ALL 194.110.210.0/24 DIR0 +ALL 194.110.219.0/24 DIR0 +ALL 194.110.248.0/24 DIR0 +ALL 194.110.252.0/24 DIR0 +ALL 194.110.254.0/24 DIR0 +ALL 194.114.132.0/22 DIR0 +ALL 194.114.136.0/22 DIR0 +ALL 194.116.162.0/23 DIR0 +ALL 194.116.170.0/23 DIR0 +ALL 194.116.194.0/23 DIR0 +ALL 194.116.228.0/23 DIR0 +ALL 194.116.232.0/23 DIR0 +ALL 194.116.238.0/23 DIR0 +ALL 194.116.244.0/23 DIR0 +ALL 194.125.224.0/22 DIR0 +ALL 194.125.244.0/23 DIR0 +ALL 194.125.248.0/23 DIR0 +ALL 194.126.180.0/22 DIR0 +ALL 194.126.204.0/24 DIR0 +ALL 194.126.224.0/24 DIR0 +ALL 194.135.249.0/24 DIR0 +ALL 194.140.228.0/24 DIR0 +ALL 194.140.237.0/24 DIR0 +ALL 194.143.136.0/23 DIR0 +ALL 194.143.144.0/22 DIR0 +ALL 194.145.117.0/24 DIR0 +ALL 194.145.198.0/23 DIR0 +ALL 194.145.214.0/23 DIR0 +ALL 194.145.216.0/23 DIR0 +ALL 194.145.220.0/23 DIR0 +ALL 194.146.110.0/24 DIR0 +ALL 194.146.112.0/24 DIR0 +ALL 194.146.132.0/22 DIR0 +ALL 194.146.136.0/21 DIR0 +ALL 194.146.156.0/23 DIR0 +ALL 194.146.188.0/22 DIR0 +ALL 194.146.196.0/22 DIR0 +ALL 194.146.220.0/22 DIR0 +ALL 194.146.228.0/22 DIR0 +ALL 194.150.72.0/21 DIR0 +ALL 194.150.92.0/22 DIR0 +ALL 194.150.104.0/22 DIR0 +ALL 194.150.174.0/23 DIR0 +ALL 194.150.192.0/23 DIR0 +ALL 194.150.204.0/23 DIR0 +ALL 194.150.220.0/23 DIR0 +ALL 194.150.232.0/23 DIR0 +ALL 194.153.128.0/23 DIR0 +ALL 194.153.148.0/23 DIR0 +ALL 194.165.46.0/24 DIR0 +ALL 194.165.62.0/24 DIR0 +ALL 194.169.193.0/24 DIR0 +ALL 194.169.205.0/24 DIR0 +ALL 194.169.206.0/23 DIR0 +ALL 194.169.210.0/24 DIR0 +ALL 194.169.238.0/24 DIR0 +ALL 194.176.97.0/24 DIR0 +ALL 194.183.160.0/19 DIR0 +ALL 194.187.28.0/22 DIR0 +ALL 194.187.48.0/22 DIR0 +ALL 194.187.56.0/22 DIR0 +ALL 194.187.104.0/21 DIR0 +ALL 194.187.128.0/22 DIR0 +ALL 194.187.148.0/22 DIR0 +ALL 194.187.152.0/22 DIR0 +ALL 194.187.208.0/24 DIR0 +ALL 194.187.216.0/22 DIR0 +ALL 194.187.228.0/22 DIR0 +ALL 194.213.6.0/24 DIR0 +ALL 194.213.23.0/24 DIR0 +ALL 194.220.139.0/24 DIR0 +ALL 194.220.172.0/24 DIR0 +ALL 194.242.53.0/24 DIR0 +ALL 194.242.60.0/24 DIR0 +ALL 194.242.96.0/22 DIR0 +ALL 194.242.100.0/23 DIR0 +ALL 194.242.102.0/24 DIR0 +ALL 194.242.116.0/22 DIR0 +ALL 194.246.99.0/24 DIR0 +ALL 194.246.104.0/23 DIR0 +ALL 194.246.116.0/23 DIR0 +ALL 194.246.120.0/23 DIR0 +ALL 195.2.236.0/23 DIR0 +ALL 195.2.242.0/23 DIR0 +ALL 195.3.128.0/21 DIR0 +ALL 195.3.148.0/22 DIR0 +ALL 195.3.156.0/22 DIR0 +ALL 195.3.196.0/22 DIR0 +ALL 195.3.204.0/22 DIR0 +ALL 195.3.236.0/22 DIR0 +ALL 195.3.244.0/22 DIR0 +ALL 195.5.108.0/23 DIR0 +ALL 195.5.124.0/23 DIR0 +ALL 195.5.184.0/24 DIR0 +ALL 195.8.200.0/23 DIR0 +ALL 195.8.218.0/23 DIR0 +ALL 195.9.87.0/24 DIR0 +ALL 195.9.247.0/24 DIR0 +ALL 195.10.210.0/24 DIR0 +ALL 195.10.218.0/24 DIR0 +ALL 195.12.36.0/22 DIR0 +ALL 195.14.17.0/24 DIR0 +ALL 195.20.4.0/22 DIR0 +ALL 195.20.28.0/22 DIR0 +ALL 195.20.96.0/23 DIR0 +ALL 195.20.100.0/22 DIR0 +ALL 195.20.118.0/23 DIR0 +ALL 195.20.124.0/23 DIR0 +ALL 195.20.128.0/19 DIR0 +ALL 195.22.112.0/22 DIR0 +ALL 195.22.130.0/23 DIR0 +ALL 195.22.132.0/23 DIR0 +ALL 195.22.140.0/23 DIR0 +ALL 195.24.128.0/19 DIR0 +ALL 195.24.234.0/24 DIR0 +ALL 195.24.252.0/23 DIR0 +ALL 195.26.16.0/22 DIR0 +ALL 195.26.64.0/22 DIR0 +ALL 195.26.80.0/21 DIR0 +ALL 195.26.92.0/22 DIR0 +ALL 195.28.0.0/23 DIR0 +ALL 195.28.186.0/23 DIR0 +ALL 195.34.74.0/23 DIR0 +ALL 195.34.90.0/23 DIR0 +ALL 195.34.94.0/23 DIR0 +ALL 195.34.196.0/22 DIR0 +ALL 195.34.204.0/22 DIR0 +ALL 195.35.65.0/24 DIR0 +ALL 195.38.16.0/23 DIR0 +ALL 195.38.18.0/24 DIR0 +ALL 195.39.196.0/23 DIR0 +ALL 195.39.210.0/23 DIR0 +ALL 195.39.214.0/23 DIR0 +ALL 195.39.232.0/23 DIR0 +ALL 195.39.240.0/22 DIR0 +ALL 195.39.248.0/23 DIR0 +ALL 195.39.252.0/23 DIR0 +ALL 195.42.126.0/23 DIR0 +ALL 195.42.130.0/23 DIR0 +ALL 195.42.136.0/23 DIR0 +ALL 195.43.40.0/22 DIR0 +ALL 195.43.146.0/24 DIR0 +ALL 195.43.148.0/24 DIR0 +ALL 195.46.56.0/22 DIR0 +ALL 195.47.202.0/24 DIR0 +ALL 195.47.219.0/24 DIR0 +ALL 195.47.248.0/24 DIR0 +ALL 195.47.253.0/24 DIR0 +ALL 195.49.128.0/22 DIR0 +ALL 195.49.148.0/22 DIR0 +ALL 195.49.164.0/22 DIR0 +ALL 195.58.224.0/19 DIR0 +ALL 195.60.66.0/23 DIR0 +ALL 195.60.70.0/23 DIR0 +ALL 195.60.174.0/23 DIR0 +ALL 195.60.184.0/23 DIR0 +ALL 195.60.200.0/23 DIR0 +ALL 195.60.224.0/24 DIR0 +ALL 195.60.226.0/24 DIR0 +ALL 195.62.14.0/23 DIR0 +ALL 195.62.24.0/23 DIR0 +ALL 195.62.36.0/23 DIR0 +ALL 195.64.136.0/23 DIR0 +ALL 195.64.142.0/23 DIR0 +ALL 195.64.148.0/23 DIR0 +ALL 195.64.166.0/23 DIR0 +ALL 195.64.190.0/23 DIR0 +ALL 195.64.224.0/19 DIR0 +ALL 195.66.65.0/24 DIR0 +ALL 195.66.66.0/24 DIR0 +ALL 195.66.79.0/24 DIR0 +ALL 195.66.87.0/24 DIR0 +ALL 195.66.93.0/24 DIR0 +ALL 195.66.105.0/24 DIR0 +ALL 195.66.136.0/23 DIR0 +ALL 195.66.140.0/23 DIR0 +ALL 195.66.152.0/23 DIR0 +ALL 195.66.156.0/23 DIR0 +ALL 195.66.192.0/19 DIR0 +ALL 195.68.196.0/23 DIR0 +ALL 195.68.202.0/23 DIR0 +ALL 195.68.210.0/23 DIR0 +ALL 195.68.216.0/22 DIR0 +ALL 195.68.222.0/23 DIR0 +ALL 195.69.76.0/22 DIR0 +ALL 195.69.84.0/22 DIR0 +ALL 195.69.132.0/22 DIR0 +ALL 195.69.168.0/22 DIR0 +ALL 195.69.176.0/23 DIR0 +ALL 195.69.179.0/24 DIR0 +ALL 195.69.184.0/22 DIR0 +ALL 195.69.196.0/22 DIR0 +ALL 195.69.200.0/22 DIR0 +ALL 195.69.220.0/22 DIR0 +ALL 195.69.244.0/22 DIR0 +ALL 195.69.248.0/22 DIR0 +ALL 195.72.144.0/22 DIR0 +ALL 195.72.156.0/22 DIR0 +ALL 195.74.67.0/24 DIR0 +ALL 195.78.38.0/23 DIR0 +ALL 195.78.58.0/23 DIR0 +ALL 195.78.68.0/23 DIR0 +ALL 195.78.92.0/23 DIR0 +ALL 195.78.232.0/22 DIR0 +ALL 195.78.244.0/22 DIR0 +ALL 195.78.252.0/23 DIR0 +ALL 195.80.231.0/24 DIR0 +ALL 195.80.232.0/24 DIR0 +ALL 195.82.150.0/23 DIR0 +ALL 195.85.198.0/24 DIR0 +ALL 195.85.214.0/24 DIR0 +ALL 195.85.219.0/24 DIR0 +ALL 195.85.250.0/24 DIR0 +ALL 195.90.122.0/23 DIR0 +ALL 195.93.138.0/23 DIR0 +ALL 195.93.154.0/23 DIR0 +ALL 195.93.160.0/23 DIR0 +ALL 195.93.172.0/23 DIR0 +ALL 195.93.184.0/23 DIR0 +ALL 195.93.190.0/23 DIR0 +ALL 195.93.204.0/23 DIR0 +ALL 195.93.212.0/22 DIR0 +ALL 195.95.139.0/24 DIR0 +ALL 195.95.147.0/24 DIR0 +ALL 195.95.151.0/24 DIR0 +ALL 195.95.157.0/24 DIR0 +ALL 195.95.165.0/24 DIR0 +ALL 195.95.171.0/24 DIR0 +ALL 195.95.189.0/24 DIR0 +ALL 195.95.206.0/23 DIR0 +ALL 195.95.210.0/23 DIR0 +ALL 195.95.222.0/23 DIR0 +ALL 195.95.232.0/23 DIR0 +ALL 195.110.6.0/23 DIR0 +ALL 195.114.6.0/23 DIR0 +ALL 195.114.30.0/23 DIR0 +ALL 195.114.96.0/23 DIR0 +ALL 195.114.120.0/23 DIR0 +ALL 195.114.128.0/19 DIR0 +ALL 195.123.0.0/16 DIR0 +ALL 195.128.16.0/22 DIR0 +ALL 195.128.56.0/21 DIR0 +ALL 195.128.178.0/23 DIR0 +ALL 195.128.182.0/23 DIR0 +ALL 195.128.230.0/23 DIR0 +ALL 195.128.248.0/23 DIR0 +ALL 195.128.252.0/23 DIR0 +ALL 195.135.196.0/22 DIR0 +ALL 195.137.167.0/24 DIR0 +ALL 195.137.192.0/23 DIR0 +ALL 195.137.196.0/23 DIR0 +ALL 195.137.202.0/23 DIR0 +ALL 195.137.226.0/23 DIR0 +ALL 195.137.232.0/23 DIR0 +ALL 195.137.240.0/23 DIR0 +ALL 195.137.244.0/23 DIR0 +ALL 195.137.250.0/23 DIR0 +ALL 195.138.64.0/19 DIR0 +ALL 195.138.160.0/19 DIR0 +ALL 195.138.193.0/24 DIR0 +ALL 195.138.198.0/24 DIR0 +ALL 195.138.217.0/24 DIR0 +ALL 195.138.218.0/24 DIR0 +ALL 195.140.160.0/22 DIR0 +ALL 195.140.168.0/22 DIR0 +ALL 195.140.176.0/22 DIR0 +ALL 195.140.224.0/22 DIR0 +ALL 195.140.244.0/22 DIR0 +ALL 195.144.6.0/24 DIR0 +ALL 195.144.21.0/24 DIR0 +ALL 195.144.25.0/24 DIR0 +ALL 195.144.28.0/24 DIR0 +ALL 195.149.70.0/24 DIR0 +ALL 195.149.90.0/24 DIR0 +ALL 195.149.96.0/24 DIR0 +ALL 195.149.108.0/23 DIR0 +ALL 195.149.112.0/24 DIR0 +ALL 195.149.114.0/24 DIR0 +ALL 195.149.125.0/24 DIR0 +ALL 195.160.192.0/22 DIR0 +ALL 195.160.220.0/22 DIR0 +ALL 195.160.232.0/22 DIR0 +ALL 195.177.68.0/22 DIR0 +ALL 195.177.72.0/22 DIR0 +ALL 195.177.92.0/22 DIR0 +ALL 195.177.112.0/21 DIR0 +ALL 195.177.124.0/22 DIR0 +ALL 195.177.208.0/23 DIR0 +ALL 195.177.222.0/23 DIR0 +ALL 195.177.236.0/22 DIR0 +ALL 195.177.240.0/23 DIR0 +ALL 195.178.128.0/19 DIR0 +ALL 195.182.0.0/24 DIR0 +ALL 195.182.7.0/24 DIR0 +ALL 195.182.21.0/24 DIR0 +ALL 195.182.22.0/24 DIR0 +ALL 195.182.192.0/22 DIR0 +ALL 195.184.70.0/24 DIR0 +ALL 195.184.80.0/23 DIR0 +ALL 195.184.192.0/19 DIR0 +ALL 195.189.8.0/22 DIR0 +ALL 195.189.16.0/22 DIR0 +ALL 195.189.44.0/22 DIR0 +ALL 195.189.48.0/22 DIR0 +ALL 195.189.60.0/22 DIR0 +ALL 195.189.96.0/22 DIR0 +ALL 195.189.104.0/22 DIR0 +ALL 195.189.200.0/23 DIR0 +ALL 195.189.214.0/23 DIR0 +ALL 195.189.226.0/23 DIR0 +ALL 195.189.228.0/23 DIR0 +ALL 195.189.234.0/23 DIR0 +ALL 195.189.240.0/23 DIR0 +ALL 195.189.246.0/23 DIR0 +ALL 195.189.248.0/23 DIR0 +ALL 195.190.13.0/24 DIR0 +ALL 195.200.64.0/23 DIR0 +ALL 195.200.90.0/23 DIR0 +ALL 195.200.196.0/24 DIR0 +ALL 195.200.221.0/24 DIR0 +ALL 195.206.224.0/21 DIR0 +ALL 195.214.192.0/21 DIR0 +ALL 195.214.208.0/21 DIR0 +ALL 195.214.220.0/22 DIR0 +ALL 195.214.236.0/22 DIR0 +ALL 195.216.204.0/23 DIR0 +ALL 195.216.206.0/24 DIR0 +ALL 195.216.210.0/23 DIR0 +ALL 195.216.212.0/23 DIR0 +ALL 195.216.226.0/24 DIR0 +ALL 195.216.248.0/24 DIR0 +ALL 195.225.52.0/23 DIR0 +ALL 195.225.96.0/22 DIR0 +ALL 195.225.112.0/22 DIR0 +ALL 195.225.144.0/22 DIR0 +ALL 195.225.156.0/22 DIR0 +ALL 195.225.228.0/22 DIR0 +ALL 195.230.96.0/24 DIR0 +ALL 195.230.103.0/24 DIR0 +ALL 195.230.115.0/24 DIR0 +ALL 195.230.128.0/19 DIR0 +ALL 195.234.61.0/24 DIR0 +ALL 195.234.68.0/22 DIR0 +ALL 195.234.72.0/22 DIR0 +ALL 195.234.112.0/22 DIR0 +ALL 195.234.132.0/24 DIR0 +ALL 195.234.148.0/24 DIR0 +ALL 195.234.174.0/24 DIR0 +ALL 195.234.200.0/22 DIR0 +ALL 195.234.212.0/22 DIR0 +ALL 195.234.220.0/22 DIR0 +ALL 195.238.92.0/23 DIR0 +ALL 195.238.176.0/21 DIR0 +ALL 195.238.188.0/22 DIR0 +ALL 195.242.94.0/23 DIR0 +ALL 195.242.112.0/22 DIR0 +ALL 195.242.148.0/22 DIR0 +ALL 195.242.161.0/24 DIR0 +ALL 195.242.200.0/22 DIR0 +ALL 195.244.4.0/23 DIR0 +ALL 195.244.8.0/23 DIR0 +ALL 195.245.76.0/23 DIR0 +ALL 195.245.80.0/23 DIR0 +ALL 195.245.96.0/23 DIR0 +ALL 195.245.112.0/23 DIR0 +ALL 195.245.118.0/23 DIR0 +ALL 195.245.120.0/23 DIR0 +ALL 195.245.200.0/24 DIR0 +ALL 195.245.215.0/24 DIR0 +ALL 195.245.221.0/24 DIR0 +ALL 195.245.249.0/24 DIR0 +ALL 195.245.253.0/24 DIR0 +ALL 195.246.104.0/23 DIR0 +ALL 195.246.217.0/24 DIR0 +ALL 195.248.93.0/24 DIR0 +ALL 195.248.160.0/19 DIR0 +ALL 195.248.234.0/23 DIR0 +ALL 195.250.36.0/24 DIR0 +ALL 195.250.43.0/24 DIR0 +ALL 195.250.62.0/24 DIR0 +ALL 195.254.142.0/23 DIR0 +ALL 195.254.150.0/23 DIR0 +ALL 209.62.181.0/24 DIR0 +ALL 209.62.187.0/24 DIR0 +ALL 209.62.189.0/24 DIR0 +ALL 209.85.128.0/17 DIR0 +ALL 212.1.64.0/18 DIR0 +ALL 212.2.128.0/19 DIR0 +ALL 212.3.96.0/19 DIR0 +ALL 212.8.32.0/19 DIR0 +ALL 212.9.224.0/19 DIR0 +ALL 212.15.128.0/19 DIR0 +ALL 212.22.192.0/20 DIR0 +ALL 212.26.128.0/19 DIR0 +ALL 212.28.64.0/19 DIR0 +ALL 212.35.160.0/19 DIR0 +ALL 212.40.32.0/19 DIR0 +ALL 212.42.64.0/19 DIR0 +ALL 212.58.160.0/19 DIR0 +ALL 212.66.32.0/19 DIR0 +ALL 212.68.160.0/19 DIR0 +ALL 212.74.234.0/24 DIR0 +ALL 212.80.32.0/19 DIR0 +ALL 212.82.192.0/19 DIR0 +ALL 212.86.96.0/19 DIR0 +ALL 212.86.225.0/24 DIR0 +ALL 212.86.226.0/24 DIR0 +ALL 212.86.228.0/22 DIR0 +ALL 212.86.232.0/21 DIR0 +ALL 212.86.240.0/20 DIR0 +ALL 212.87.160.0/19 DIR0 +ALL 212.90.96.0/20 DIR0 +ALL 212.90.112.0/23 DIR0 +ALL 212.90.116.0/23 DIR0 +ALL 212.90.124.0/22 DIR0 +ALL 212.90.160.0/19 DIR0 +ALL 212.92.224.0/19 DIR0 +ALL 212.109.32.0/19 DIR0 +ALL 212.111.192.0/19 DIR0 +ALL 212.113.32.0/20 DIR0 +ALL 212.115.224.0/19 DIR0 +ALL 212.178.0.0/19 DIR0 +ALL 213.130.0.0/20 DIR0 +ALL 213.130.16.0/23 DIR0 +ALL 213.130.18.0/24 DIR0 +ALL 213.130.20.0/22 DIR0 +ALL 213.130.24.0/21 DIR0 +ALL 213.133.160.0/19 DIR0 +ALL 213.135.64.0/23 DIR0 +ALL 213.135.67.0/24 DIR0 +ALL 213.135.68.0/22 DIR0 +ALL 213.135.72.0/21 DIR0 +ALL 213.151.0.0/19 DIR0 +ALL 213.154.192.0/19 DIR0 +ALL 213.155.0.0/19 DIR0 +ALL 213.156.64.0/19 DIR0 +ALL 213.159.224.0/19 DIR0 +ALL 213.160.128.0/19 DIR0 +ALL 213.169.64.0/19 DIR0 +ALL 213.180.192.0/19 DIR0 +ALL 213.186.112.0/20 DIR0 +ALL 213.186.192.0/19 DIR0 +ALL 213.200.32.0/23 DIR0 +ALL 213.200.43.0/24 DIR0 +ALL 213.208.160.0/19 DIR0 +ALL 213.227.192.0/18 DIR0 +ALL 213.231.0.0/18 DIR0 +ALL 213.238.0.0/22 DIR0 +ALL 213.238.16.0/24 DIR0 +ALL 213.238.20.0/24 DIR0 +ALL 213.238.24.0/24 DIR0 +ALL 213.238.28.0/24 DIR0 +ALL 216.73.80.0/24 DIR0 +ALL 216.73.87.0/24 DIR0 +ALL 216.239.32.0/19 DIR0 +ALL 217.9.0.0/24 DIR0 +ALL 217.9.4.0/24 DIR0 +ALL 217.12.192.0/19 DIR0 +ALL 217.19.208.0/20 DIR0 +ALL 217.24.160.0/20 DIR0 +ALL 217.25.192.0/20 DIR0 +ALL 217.27.144.0/20 DIR0 +ALL 217.28.254.0/24 DIR0 +ALL 217.65.240.0/21 DIR0 +ALL 217.66.96.0/20 DIR0 +ALL 217.73.128.0/20 DIR0 +ALL 217.76.192.0/20 DIR0 +ALL 217.77.208.0/20 DIR0 +ALL 217.112.208.0/20 DIR0 +ALL 217.114.32.0/20 DIR0 +ALL 217.117.64.0/20 DIR0 +ALL 217.144.64.0/20 DIR0 +ALL 217.146.240.0/20 DIR0 +ALL 217.147.160.0/21 DIR0 +ALL 217.147.168.0/24 DIR0 +ALL 217.175.80.0/20 DIR0 +ALL 217.196.160.0/20 DIR0 +ALL 217.198.128.0/20 DIR0 +ALL 217.199.208.0/20 DIR0 +ALL 217.199.224.0/20 DIR0 diff --git a/projects/traffcounter/rules.cpp b/projects/traffcounter/rules.cpp new file mode 100644 index 00000000..165addbb --- /dev/null +++ b/projects/traffcounter/rules.cpp @@ -0,0 +1,442 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.1.1.1 $ + $Date: 2009/02/24 08:13:03 $ + $Author: faust $ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rules.h" +#include "utils.h" + +using namespace std; + +STG::RULES_PARSER::RULES_PARSER() + : rules(), + error(false), + errorStream(""), + protocols() +{ +error = InitProtocols(); +} + +STG::RULES_PARSER::RULES_PARSER(const string & fileName) + : rules(), + error(false), + errorStream(""), + protocols() +{ +error = InitProtocols(); +SetFile(fileName); +} + +void STG::RULES_PARSER::SetFile(const string & fileName) +{ +errorStream.str(""); + +ifstream rulesFile(fileName.c_str()); + +int lineNumber = 0; + +if (!rulesFile) + { + error = true; + errorStream << "RULES_PARSER::SetFile - Error opening file '" << fileName << "'\n"; + return; + } + +string line; + +rules.erase(rules.begin(), rules.end()); + +while (getline(rulesFile, line)) + { + lineNumber++; + if (ParseLine(line)) + { + error = true; + errorStream << "RULES_PARSER::SetFile - Error parsing line at '" << fileName << ":" << lineNumber << "'\n"; + return; + } + } + +STG::RULE rule; + +// Adding lastest rule: ALL 0.0.0.0/0 NULL +rule.dir = -1; //NULL +rule.ip = 0; //0.0.0.0 +rule.mask = 0; +rule.port1 = 0; +rule.port2 = 65535; +rule.proto = -1; + +rules.push_back(rule); + +errorStream.str(""); + +return; +} + +bool STG::RULES_PARSER::ParseLine(string line) +{ +size_t pos; + +pos = line.find('#'); +if (pos != string::npos) + { + line = line.substr(0, pos); + } + +if (line.empty()) + { + return false; + } + +size_t lpos = line.find_first_not_of("\t ", 0, 2); + +if (lpos == string::npos) + { + return false; + } + +size_t rpos = line.find_first_of("\t ", lpos, 2); + +if (rpos == string::npos) + { + return false; + } + +string proto(line.begin() + lpos, line.begin() + rpos); + +lpos = line.find_first_not_of("\t ", rpos, 2); + +if (lpos == string::npos) + { + return false; + } + +rpos = line.find_first_of("\t ", lpos, 2); + +if (rpos == string::npos) + { + return false; + } + +string address(line.begin() + lpos, line.begin() + rpos); + +lpos = line.find_first_not_of("\t ", rpos, 2); + +if (lpos == string::npos) + { + return false; + } +string direction(line.begin() + lpos, line.end()); + +if (proto.empty() || + address.empty() || + direction.empty()) + { + return false; + } + +map::const_iterator it(protocols.find(proto)); + +if (it == protocols.end()) + { + errorStream << "RULES_PARSER::ParseLine - Invalid protocol\n"; + return true; + } + +STG::RULE rule; + +rule.proto = it->second; + +if (direction.length() < 4) + { + errorStream << "RULES_PARSER::ParseLine - Invalid direction\n"; + return true; + } + +if (direction == "NULL") + { + rule.dir = -1; + } +else + { + string prefix(direction.begin(), direction.begin() + 3); + direction = direction.substr(3, direction.length() - 3); + if (prefix != "DIR") + { + errorStream << "RULES_PARSER::ParseLine - Invalid direction prefix\n"; + return true; + } + char * endptr; + /* + * 'cause strtol don't change errno on success + * according to: http://www.opengroup.org/onlinepubs/000095399/functions/strtol.html + */ + errno = 0; + rule.dir = strtol(direction.c_str(), &endptr, 10); + + // Code from strtol(3) release 3.10 + if ((errno == ERANGE && (rule.dir == numeric_limits::max() || + rule.dir == numeric_limits::min())) + || (errno != 0 && rule.dir == 0)) + { + errorStream << "RULES_PARSER::ParseLine - Direction out of range\n"; + return true; + } + if (endptr == direction.c_str()) + { + errorStream << "RULES_PARSER::ParseLine - Invalid direction\n"; + return true; + } + } + +if (ParseAddress(address, &rule)) + { + errorStream << "RULES_PARSER::ParseLine - Invalid address\n"; + return true; + } + +rules.push_back(rule); + +return false; +} + +bool STG::RULES_PARSER::ParseAddress(const string & address, RULE * rule) const +{ +// Format:
[/[:[-]]] +size_t pos = address.find('/'); +string ip; +string mask; +string ports; + +if (pos != string::npos) + { + ip = address.substr(0, pos); + mask = address.substr(pos + 1, address.length() - pos - 1); + pos = mask.find(':'); + if (pos != string::npos) + { + ports = mask.substr(pos + 1, mask.length() - pos - 1); + mask = mask.substr(0, pos); + } + else + { + ports = "0-65535"; + } + } +else + { + mask = "32"; + pos = address.find(':'); + if (pos != string::npos) + { + ip = address.substr(0, pos); + ports = address.substr(pos + 1, address.length() - pos - 1); + } + else + { + ip = address; + ports = "0-65536"; + } + } + +struct in_addr ipaddr; + +if (!inet_aton(ip.c_str(), &ipaddr)) + { + errorStream << "RULES_PARSER::ParseAddress - Invalid IP\n"; + return true; + } + +rule->ip = ntohl(ipaddr.s_addr); + +if (ParseMask(mask, rule)) + { + errorStream << "RULES_PARSER::ParseAddress - Error parsing mask\n"; + return true; + } + +pos = ports.find('-'); +string port1; +string port2; + +if (pos != string::npos) + { + port1 = ports.substr(0, pos); + port2 = ports.substr(pos + 1, ports.length() - pos - 1); + } +else + { + port1 = port2 = ports; + } + +if (ParsePorts(port1, port2, rule)) + { + errorStream << "RULES_PARSER::ParseAddress - Error pasing ports\n"; + return true; + } + +return false; +} + +bool STG::RULES_PARSER::ParseMask(const string & mask, RULE * rule) const +{ +char * endptr; + +errno = 0; +/* + * 'cause strtol don't change errno on success + * according to: http://www.opengroup.org/onlinepubs/000095399/functions/strtol.html + */ +rule->mask = strtol(mask.c_str(), &endptr, 10); + +if ((errno == ERANGE && (rule->mask == numeric_limits::max() || + rule->mask == numeric_limits::min())) + || (errno != 0 && rule->mask == 0)) + { + errorStream << "RULES_PARSER::ParseMask - Mask is out of range\n"; + return true; + } + +if (endptr == NULL) + { + errorStream << "RULES_PARSER::ParseMask - NULL endptr\n"; + return true; + } + +if (*endptr != '\0') + { + errorStream << "RULES_PARSER::ParseMask - Invalid mask\n"; + return true; + } + +if (rule->mask > 32) + { + errorStream << "RULES_PARSER::ParseMask - Mask is greater than 32\n"; + return true; + } + +rule->mask = 0xffFFffFF >> (32 - rule->mask); + +return false; +} + +bool STG::RULES_PARSER::ParsePorts(const string & port1, + const string & port2, + RULE * rule) const +{ +char * endptr; + +errno = 0; +/* + * 'cause strtol don't change errno on success + * according to: http://www.opengroup.org/onlinepubs/000095399/functions/strtol.html + */ +rule->port1 = strtol(port1.c_str(), &endptr, 10); + +if ((errno == ERANGE && (rule->port1 == numeric_limits::max() || + rule->port1 == numeric_limits::min())) + || (errno != 0 && rule->port1 == 0)) + { + errorStream << "RULES_PARSER::ParsePorts - Min port is out of range\n"; + return true; + } + +if (endptr == NULL) + { + errorStream << "RULES_PARSER::ParsePorts - NULL endptr on min port\n"; + return true; + } + +if (*endptr != '\0') + { + errorStream << "RULES_PARSER::ParsePorts - Invalid min port\n"; + return true; + } + +errno = 0; +/* + * 'cause strtol don't change errno on success + * according to: http://www.opengroup.org/onlinepubs/000095399/functions/strtol.html + */ +rule->port2 = strtol(port2.c_str(), &endptr, 10); + +if ((errno == ERANGE && (rule->port2 == numeric_limits::max() || + rule->port2 == numeric_limits::min())) + || (errno != 0 && rule->port2 == 0)) + { + errorStream << "RULES_PARSER::ParseAddress - Max port is out of range\n"; + return true; + } + +if (endptr == NULL) + { + errorStream << "RULES_PARSER::ParsePorts - NULL endptr on max port\n"; + return true; + } + +if (*endptr != '\0') + { + errorStream << "RULES_PARSER::ParsePorts - Invalid max port\n"; + return true; + } + +return false; +} + +bool STG::RULES_PARSER::InitProtocols() +{ +struct protoent * pe; + +locale loc(""); + +protocols.erase(protocols.begin(), protocols.end()); + +setprotoent(true); // Open link to /etc/protocols + +while ((pe = getprotoent()) != NULL) + { + string proto(pe->p_name); + protocols.insert(make_pair(STG::ToUpper(pe->p_name, loc), pe->p_proto)); + } + +endprotoent(); + +protocols["ALL"] = -1; +protocols["TCP_UDP"] = -2; + +errorStream.str(""); + +return protocols.empty(); +} diff --git a/projects/traffcounter/rules.h b/projects/traffcounter/rules.h new file mode 100644 index 00000000..049f4376 --- /dev/null +++ b/projects/traffcounter/rules.h @@ -0,0 +1,94 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.1.1.1 $ + $Date: 2009/02/24 08:13:03 $ + $Author: faust $ + */ + + +#ifndef __RULES_H__ +#define __RULES_H__ + +#include +#include +#include +#include + +#ifdef HAVE_STDINT + #include +#else + #ifdef HAVE_INTTYPES + #include + #else + #error "You need either stdint.h or inttypes.h to compile this!" + #endif +#endif + +namespace STG +{ + + //----------------------------------------------------------------------------- + struct RULE + { + uint32_t ip; // IP + uint32_t mask; // Netmask + uint16_t port1; // Port 1 + uint16_t port2; // Port 2 + int proto; // Protocol + int dir; // Direction + }; + //----------------------------------------------------------------------------- + typedef std::list RULES; + //----------------------------------------------------------------------------- + class RULES_PARSER + { + public: + RULES_PARSER(); + + RULES_PARSER(const std::string & fileName); + + ~RULES_PARSER() {}; + + void SetFile(const std::string & fileName); + + const RULES & GetRules() const { return rules; }; + bool IsError() const { return error; }; + const std::string ErrorMsg() const { return errorStream.str(); }; + + private: + RULES rules; + bool error; + mutable std::stringstream errorStream; + std::map protocols; + + bool InitProtocols(); + bool ParseLine(std::string line); + bool ParseAddress(const std::string & address, RULE * rule) const; + bool ParseMask(const std::string & mask, RULE * rule) const; + bool ParsePorts(const std::string & port1, + const std::string & port2, + RULE * rule) const; + }; + +} + +#endif // __RULES_H__ diff --git a/projects/traffcounter/rules_finder.cpp b/projects/traffcounter/rules_finder.cpp new file mode 100644 index 00000000..82b398a3 --- /dev/null +++ b/projects/traffcounter/rules_finder.cpp @@ -0,0 +1,155 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.3 $ + $Date: 2009/10/12 08:46:05 $ + $Author: faust $ + */ + +#include "rules_finder.h" +#include "logger.h" +#include "lock.h" + +STG::RULES_FINDER::RULES_FINDER() +{ + pthread_mutex_init(&mutex, NULL); +} + +STG::RULES_FINDER::~RULES_FINDER() +{ + pthread_mutex_destroy(&mutex); +} + +void STG::RULES_FINDER::SetRules(const RULES & r) +{ +SCOPED_LOCK lock(mutex); +rules = r; +} + +int STG::RULES_FINDER::GetDir(const PENDING_PACKET & packet) const +{ +bool addrMatch; +bool portMatch; + +STG::RULES::const_iterator ln; +int ruleLine(1); + +SCOPED_LOCK lock(mutex); + +ln = rules.begin(); + +while (ln != rules.end()) + { + addrMatch = false; + portMatch = false; + + // Port range + switch (packet.direction) { + case PENDING_PACKET::INCOMING: + portMatch = (packet.sport >= ln->port1) && + (packet.sport <= ln->port2); + break; + case PENDING_PACKET::OUTGOING: + portMatch = (packet.dport >= ln->port1) && + (packet.dport <= ln->port2); + break; + case PENDING_PACKET::LOCAL: + portMatch = ((packet.sport >= ln->port1) && + (packet.sport <= ln->port2)) || + ((packet.dport >= ln->port1) && + (packet.dport <= ln->port2)); + break; + default: + ++ruleLine; + ++ln; + continue; + } + + if (!portMatch) { + ++ruleLine; + ++ln; + continue; + } + + /*portMatch = ((packet.sport >= ln->port1) && + (packet.sport <= ln->port2) && + (packet.direction == PENDING_PACKET::INCOMING)) || + ((packet.dport >= ln->port1) && + (packet.dport <= ln->port2) && + (packet.direction == PENDING_PACKET::OUTGOING));*/ + + if (ln->proto != packet.proto) + { + // Is it a normal protcol number? + if (ln->proto >= 0) + { + ++ruleLine; + ++ln; + continue; + } + else if (ln->proto == -2) + { + // -2 - TCP_UDP + if (packet.proto != 6 && + packet.proto != 17) + { + ++ruleLine; + ++ln; + continue; + } + } + // -1 - ALL + } + + switch (packet.direction) { + case PENDING_PACKET::INCOMING: + // From outer world to us + addrMatch = (packet.saddr & ln->mask) == ln->ip; + break; + case PENDING_PACKET::OUTGOING: + // From us to outer world + addrMatch = (packet.daddr & ln->mask) == ln->ip; + break; + case PENDING_PACKET::LOCAL: + // From us to us + addrMatch = (packet.saddr & ln->mask) == ln->ip || + (packet.daddr & ln->mask) == ln->ip; + break; + default: + // From outer world to outer world + ++ruleLine; + ++ln; + continue; + } + + + if (addrMatch) + { + // At this point ports and protocol are matched + return ln->dir; + } + + ++ruleLine; + ++ln; + } //while (ln != rules.end()) + +return -1; +} diff --git a/projects/traffcounter/rules_finder.h b/projects/traffcounter/rules_finder.h new file mode 100644 index 00000000..8c110810 --- /dev/null +++ b/projects/traffcounter/rules_finder.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.2 $ + $Date: 2009/02/26 18:32:59 $ + $Author: faust $ + */ + + +#ifndef __RULES_FINDER_H__ +#define __RULES_FINDER_H__ + +#include + +#include "rules.h" +#include "tc_packets.h" + +namespace STG +{ + + class RULES_FINDER + { + public: + RULES_FINDER(); + ~RULES_FINDER(); + + void SetRules(const RULES & r); + + int GetDir(const PENDING_PACKET & packet) const; + + private: + RULES rules; + mutable pthread_mutex_t mutex; + }; + +} + +#endif diff --git a/projects/traffcounter/rules_tester.cpp b/projects/traffcounter/rules_tester.cpp new file mode 100644 index 00000000..e9fd30d9 --- /dev/null +++ b/projects/traffcounter/rules_tester.cpp @@ -0,0 +1,148 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.1.1.1 $ + $Date: 2009/02/24 08:13:03 $ + $Author: faust $ + */ + +#include +#include +#include +#include +#include +#include + +#include "rules.h" +#include "logger.h" + +using namespace STG; + +STGLogger log; + +typedef std::pair TEST_ENTRY; +struct TEST_INFO { + bool wantedError; + bool actualError; // Parser error status + bool stdException; // Parser throws an std execption + bool otherException; // Parser throws another exception + std::string message; // Parser error message +}; + +class RULES_PARSER_TESTER : public std::unary_function, void> { +public: + RULES_PARSER_TESTER(RULES_PARSER & p) : parser(p), + testLog(), + testResult(), + result(true) + { + }; + ~RULES_PARSER_TESTER() + { + PrintLog(); + if (result) + exit(EXIT_SUCCESS); + exit(EXIT_FAILURE); + } + void operator()(const std::pair & entry) + { + testLog[entry.first].wantedError = entry.second; + testLog[entry.first].actualError = false; + testLog[entry.first].stdException = false; + testLog[entry.first].otherException = false; + testLog[entry.first].message = ""; + testResult[entry.first] = true; + try + { + parser.SetFile(entry.first); + } + catch (std::exception & ex) + { + testLog[entry.first].stdException = true; + testResult[entry.first] &= false; + } + catch (...) + { + testLog[entry.first].otherException = true; + testResult[entry.first] &= false; + } + testLog[entry.first].actualError = parser.IsError(); + testLog[entry.first].message = parser.ErrorMsg(); + testResult[entry.first] &= (parser.IsError() == entry.second); + result &= testResult[entry.first]; + }; + + void PrintLog() + { + std::cout << "RULES_PARSER_TESTER results:\n"; + std::cout << "-----------------------------------------------------------------\n"; + std::map::const_iterator it; + for (it = testResult.begin(); it != testResult.end(); ++it) + { + std::cout << "File: '" << it->first << "'\t" + << "Correct: " << testLog[it->first].wantedError << "\t" + << "Actual:" << testLog[it->first].actualError << "\t" + << "STD exceptions: " << testLog[it->first].stdException << "\t" + << "Other exceptions: " << testLog[it->first].otherException << "\t" + << "Result: " << it->second << "\n"; + if (!testLog[it->first].message.empty()) + { + std::cout << "Messages: \n" << testLog[it->first].message << "\n"; + } + } + std::cout << "-----------------------------------------------------------------\n"; + std::cout << "Final result: " << (result ? "passed" : "failed") << std::endl; + } + + bool Result() const { return result; }; +private: + RULES_PARSER & parser; + std::map testLog; + std::map testResult; + bool result; +}; + +int main(int argc, char ** argv) +{ +RULES_PARSER parser; +std::map tests; + +tests["./test_rules"] = false; +tests["./rules"] = false; +tests["./test_rules_bad_address"] = true; +tests["./test_rules_bad_port"] = true; +tests["./test_rules_bad_mask"] = true; +tests["./test_rules_bad_proto"] = true; +tests["./test_rules_bad_dir_prefix"] = true; +tests["./test_rules_bad_dir_range"] = true; +tests["./test_rules_bad_dir"] = true; + +/*parser.SetFile("./rules"); +std::cout << parser.ErrorMsg() << std::endl;*/ + +// TODO: find errors and write checks for regression + +std::for_each(tests.begin(), + tests.end(), + RULES_PARSER_TESTER(parser)); + +return EXIT_FAILURE; +} diff --git a/projects/traffcounter/table b/projects/traffcounter/table new file mode 100644 index 00000000..5759561e --- /dev/null +++ b/projects/traffcounter/table @@ -0,0 +1,14 @@ + | ips | pendingPackets| sessions | ip2sessions | rules | +________________|_______________|_______________|_______________|_______________|_______________| +SetRules | | | | | w | +________________|_______________|_______________|_______________|_______________|_______________| +AddPacket | r | w | | | | +________________|_______________|_______________|_______________|_______________|_______________| +AddIP | w | | | | | +________________|_______________|_______________|_______________|_______________|_______________| +GetIP | | | w | r | | +________________|_______________|_______________|_______________|_______________|_______________| +DeleteIP | w | | w | w | | +________________|_______________|_______________|_______________|_______________|_______________| +Process | | w | w | w | r | +________________|_______________|_______________|_______________|_______________|_______________| diff --git a/projects/traffcounter/tc_packets.h b/projects/traffcounter/tc_packets.h new file mode 100644 index 00000000..e0fa13d1 --- /dev/null +++ b/projects/traffcounter/tc_packets.h @@ -0,0 +1,176 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.3 $ + $Date: 2009/04/10 14:14:49 $ + $Author: faust $ + */ + + +#ifndef __TC_PACKETS_H__ +#define __TC_PACKETS_H__ + +#include + +#ifdef HAVE_STDINT + #include +#else + #ifdef HAVE_INTTYPES + #include + #else + #error "You need either stdint.h or inttypes.h to compile this!" + #endif +#endif + +namespace STG +{ + + //----------------------------------------------------------------------------- + /* + * Session identifier + * A session is an amount of bytes transfered in one direction between two + * fixed addresses by one protocol. + * In case of UDP/TCP session is also identified by ports. + */ + struct SESSION_ID + { + SESSION_ID() + : saddr(0), + daddr(0), + sport(0), + dport(0), + proto(0) + { + } + + SESSION_ID(const iphdr & ipHdr, uint16_t sp, uint16_t dp) + : saddr(ipHdr.saddr), + daddr(ipHdr.daddr), + sport(sp), + dport(dp), + proto(ipHdr.protocol) + { + } + + uint32_t saddr; + uint32_t daddr; + uint16_t sport; + uint16_t dport; + uint8_t proto; + + bool operator ==(const SESSION_ID & rval) + { + return saddr == rval.saddr && + sport == rval.sport && + daddr == rval.daddr && + dport == rval.dport && + proto == rval.proto; + } + }; + //----------------------------------------------------------------------------- + /* + * Ordering functor to use SESSION_ID as key-type in maps + */ + struct SESSION_LESS + : public std::binary_function { + bool operator()(const SESSION_ID & lval, const SESSION_ID & rval) const + { + if (lval.saddr > rval.saddr) + return false; + if (lval.saddr < rval.saddr) + return true; + if (lval.daddr > rval.daddr) + return false; + if (lval.daddr < rval.daddr) + return true; + if (lval.sport > rval.sport) + return false; + if (lval.sport < rval.sport) + return true; + if (lval.dport > rval.dport) + return false; + if (lval.dport < rval.dport) + return true; + if (lval.proto > rval.proto) + return false; + if (lval.proto < rval.proto) + return true; + return false; + }; + }; + //----------------------------------------------------------------------------- + /* + * A packet in the incoming queue + * Can create a new session or be attached to an existing one + */ + struct PENDING_PACKET : public SESSION_ID + { + PENDING_PACKET() + { + } + PENDING_PACKET(const iphdr & ipHdr, uint16_t sp, uint16_t dp) + : SESSION_ID(ipHdr, sp, dp), + length(ipHdr.tot_len), + direction(FOREIGN) + { + } + + uint16_t length; + enum DIRECTION + { + INCOMING = 0, // From outer world to user + OUTGOING, // From user to outer world + LOCAL, // From user to user + FOREIGN // From outer world to outer world + } direction; + }; + //----------------------------------------------------------------------------- + /* + * Session length and meta-information + * Used to identify data cost + */ + struct SESSION_DATA + { + SESSION_DATA() + { + dir = -1; // NULL direction + length = 0; + }; + + SESSION_DATA(const SESSION_DATA & sp) + { + dir = sp.dir; + length = sp.length; + }; + + int dir; + uint32_t length; + }; + //----------------------------------------------------------------------------- + /* + * User-related types + */ + typedef std::pair TRAFF_ITEM; + typedef std::list TRAFF_DATA; + +} + +#endif diff --git a/projects/traffcounter/tc_tester.cpp b/projects/traffcounter/tc_tester.cpp new file mode 100644 index 00000000..1ccc3945 --- /dev/null +++ b/projects/traffcounter/tc_tester.cpp @@ -0,0 +1,303 @@ +/* + * Network: + * - server: 192.168.0.1 + * - user A: 192.168.0.2 + * - user B: 192.168.0.3 + * + * External resources: + * - host 1: 216.239.59.104 + * - host 2: 72.14.221.104 + * - host 3: 66.249.93.104 + * - host 4: 195.5.61.68 + * + * Directions: + * - Local: ALL 192.168.0.0/24 + * - DNS: TCP_UDP 195.5.61.68/32:53 + * - FTP: TCP 129.22.8.159/32:20-21 + * - World: ALL 0.0.0.0/0 + * + */ + + + +#include +#include +#include + +#include +#include +#include + +#include "rules.h" +#include "traffcounter.h" +#include "logger.h" + +using namespace std; +using namespace STG; + +class StatPrinter: public unary_function { +public: + void operator()(const TRAFF_ITEM & item) const + { + LOG_IT << inet_ntoa(*(in_addr *)(&item.first.saddr)); + cout << ":" << item.first.sport; + cout << " -> " << inet_ntoa(*(in_addr *)(&item.first.daddr)); + cout << ":" << item.first.dport; + cout << "\tproto: " << item.first.proto; + cout << "\tlength: " << item.second.length; + cout << endl; + } +}; + +STGLogger log; + +struct PACKET +{ + iphdr hdr; + uint16_t sport; + uint16_t dport; +}; + +RULE MakeRule(const string & ip, + const string & mask, + uint16_t port1, + uint16_t port2, + int proto, + int dir) +{ + RULE rule; + + rule.ip = inet_addr(ip.c_str()); + rule.mask = inet_addr(mask.c_str()); + rule.port1 = port1; + rule.port2 = port2; + rule.proto = proto; + rule.dir = dir; + + return rule; +} + +RULES PrepareRules() +{ + RULES rules; + RULE local(MakeRule("192.168.0.0", + "255.255.255.0", + 0, + 65535, + -1, + 0)); + RULE dns(MakeRule("195.5.61.68", + "255.255.255.255", + 53, + 53, + -1, + 1)); + RULE ftp(MakeRule("129.22.8.159", + "255.255.255.255", + 20, + 21, + -1, + 2)); + RULE world(MakeRule("0.0.0.0", + "0.0.0.0", + 0, + 65535, + -1, + 3)); + + rules.push_back(local); + + rules.push_back(dns); + + rules.push_back(ftp); + + rules.push_back(world); + + return rules; +} + +iphdr MakePacket(const string & from, + const string & to, + int proto, + uint16_t length) +{ + iphdr hdr; + + hdr.ihl = 5; + hdr.version = 4; + hdr.tos = 0; + hdr.tot_len = length; + hdr.id = 0; + hdr.frag_off = 50; + hdr.ttl = 64; + hdr.protocol = proto; + hdr.check = 0; + hdr.saddr = inet_addr(from.c_str()); + hdr.daddr = inet_addr(to.c_str()); + + return hdr; +} + +void PrepareTraffic(vector & pckts, + const iphdr & skel, + uint16_t sport, + uint16_t dport, + uint32_t in, + uint32_t out, + int packets) +{ + PACKET inpacket; + PACKET outpacket; + + inpacket.hdr = skel; + outpacket.hdr = skel; + + outpacket.hdr.saddr ^= outpacket.hdr.daddr; + outpacket.hdr.daddr ^= outpacket.hdr.saddr; + outpacket.hdr.saddr ^= outpacket.hdr.daddr; + + inpacket.sport = sport; + inpacket.dport = dport; + outpacket.sport = dport; + outpacket.dport = sport; + + inpacket.hdr.tot_len = in / packets; + outpacket.hdr.tot_len = out / packets; + + for (int i = 0; i < packets; ++i) { + //inpacket.hdr.daddr = outpacket.hdr.saddr = rand() * 32768 + rand(); + pckts.push_back(inpacket); + pckts.push_back(outpacket); + } +} + +struct TC_TESTER : public unary_function +{ +public: + TC_TESTER(TRAFFCOUNTER & t) + : tc(t) + {} + + void operator () (const PACKET & p) + { + tc.AddPacket(p.hdr, p.sport, p.dport); + } +private: + TRAFFCOUNTER & tc; +}; + +int main() +{ + RULES rules(PrepareRules()); + + TRAFFCOUNTER tc; + + vector packets; + + tc.SetRules(rules); + + if (tc.Start()) { + LOG_IT << "::main() Error: traffcounter not started" << endl; + return EXIT_FAILURE; + } + + tc.AddIP(inet_addr("192.168.0.1")); // Server + tc.AddIP(inet_addr("192.168.0.2")); // User A + tc.AddIP(inet_addr("192.168.0.3")); // User B + + for (int i = 0; i < 10000; ++i) { + tc.AddIP(rand() * 32768 + rand()); + } + + /* + * A -> S + * S -> A + * A -> B + * B -> A + * A -> h1 + * h1 -> A + * A -> h2 + * h2 -> A + * A -> h3 + * h3 -> A + * A -> h4 + * h4 -> A + */ + + timeval tv_from; + timeval tv_to; + gettimeofday(&tv_from, NULL); + // S - local, A - local + PrepareTraffic(packets, MakePacket("192.168.0.2", "192.168.0.1", 6, 0), 3215, 20, 1024 * 1024, 2048 * 1024, 512 * 2); + // S - local, B - local + PrepareTraffic(packets, MakePacket("192.168.0.3", "192.168.0.1", 6, 0), 5432, 22, 2048 * 1024, 1024 * 1024, 512 * 2); + // A - local, B - local + PrepareTraffic(packets, MakePacket("192.168.0.3", "192.168.0.2", 6, 0), 9875, 21, 2048 * 1024, 2048 * 1024, 512 * 2); + // A - DNS + //PrepareTraffic(packets, MakePacket("192.168.0.2", "195.5.61.68", 6, 0), 4521, 53, 1024 * 1024, 2048 * 1024, 512 * 2); + // A - World + //PrepareTraffic(packets, MakePacket("192.168.0.2", "195.5.61.68", 6, 0), 4521, 80, 1024 * 1024, 2048 * 1024, 512 * 2); + // A - FTP + //PrepareTraffic(packets, MakePacket("192.168.0.2", "129.22.8.159", 6, 0), 4522, 20, 512 * 1024, 512 * 1024, 512 * 2); + // A - FTP + //PrepareTraffic(packets, MakePacket("192.168.0.2", "129.22.8.159", 6, 0), 4522, 21, 512 * 1024, 4096 * 1024, 512 * 2); + // B - World + //PrepareTraffic(packets, MakePacket("192.168.0.3", "66.249.93.104", 6, 0), 3541, 80, 1024 * 1024, 1024 * 1024, 512 * 2); + gettimeofday(&tv_to, NULL); + + uint64_t diff = tv_to.tv_sec - tv_from.tv_sec; + diff *= 1000000; + diff -= tv_from.tv_usec; + diff += tv_to.tv_usec; + + LOG_IT << "::main() Prepared " << packets.size() << " packets by " << diff << " usecs" << endl; + + gettimeofday(&tv_from, NULL); + for_each(packets.begin(), + packets.end(), + TC_TESTER(tc)); + gettimeofday(&tv_to, NULL); + + diff = tv_to.tv_sec - tv_from.tv_sec; + diff *= 1000000; + diff -= tv_from.tv_usec; + diff += tv_to.tv_usec; + + LOG_IT << "::main() Recorded " << packets.size() << " packets by " << diff << " usecs" << endl; + + int p; + while ((p = tc.PendingCount())) { + LOG_IT << "::main() Pending packets: " << p << " at " << time(NULL) << endl; + sleep(1); + } + + TRAFF_DATA data; + + tc.DeleteIP(inet_addr("192.168.0.1"), &data); + for_each(data.begin(), + data.end(), + StatPrinter()); + data.erase(data.begin(), data.end()); + tc.DeleteIP(inet_addr("192.168.0.2"), &data); + for_each(data.begin(), + data.end(), + StatPrinter()); + data.erase(data.begin(), data.end()); + tc.DeleteIP(inet_addr("192.168.0.3"), &data); + for_each(data.begin(), + data.end(), + StatPrinter()); + data.erase(data.begin(), data.end()); + + if (tc.Stop()) { + LOG_IT << "::main() Error: traffcounter not stopped" << endl; + return EXIT_FAILURE; + } + + LOG_IT << "::main() Sessions: " << tc.SessionsCount() << endl; + LOG_IT << "::main() Cache hits: " << tc.CacheHits() << endl; + LOG_IT << "::main() Cache misses: " << tc.CacheMisses() << endl; + LOG_IT << "::main() Stream quality: " << tc.StreamQuality() << endl; + + return EXIT_SUCCESS; +} diff --git a/projects/traffcounter/test_rules b/projects/traffcounter/test_rules new file mode 100644 index 00000000..bb36d93b --- /dev/null +++ b/projects/traffcounter/test_rules @@ -0,0 +1,12 @@ +# foo bar baz + +UDP 10.0.0.0/24:1024-65535 DIR0 #blah-blah-blah + +TCP 192.168.1.1:21-22 DIR1 + +ALL 192.168.2.0/16 DIR2 + +GRE 192.168.3.0/24 NULL + +ALL 0.0.0.0 DIR3 + diff --git a/projects/traffcounter/test_rules_bad_address b/projects/traffcounter/test_rules_bad_address new file mode 100644 index 00000000..7ab64ac3 --- /dev/null +++ b/projects/traffcounter/test_rules_bad_address @@ -0,0 +1,7 @@ + +UDP 10.0.0.0/24:1024-65535 DIR0 + +TCP 192.168.1.1:21-22 DIR1 +ALL 192.168.2.0/16 DIR2 +ALL 0.0.0.a DIR3 + diff --git a/projects/traffcounter/test_rules_bad_dir b/projects/traffcounter/test_rules_bad_dir new file mode 100644 index 00000000..327f6624 --- /dev/null +++ b/projects/traffcounter/test_rules_bad_dir @@ -0,0 +1,7 @@ + +UDP 10.0.0.0/24:1024-65535 DIR0 + +TCP 192.168.1.1:21-22 DIRA +ALL 192.168.2.0/16 DIR2 +ALL 0.0.0.0 DIR3 + diff --git a/projects/traffcounter/test_rules_bad_dir_prefix b/projects/traffcounter/test_rules_bad_dir_prefix new file mode 100644 index 00000000..57489f3c --- /dev/null +++ b/projects/traffcounter/test_rules_bad_dir_prefix @@ -0,0 +1,8 @@ + +UDP 10.0.0.0/24:1024-65535 DIR0 + +TCP 192.168.1.1:21-22 WOR1 +ALL 192.168.2.0/16 DIR2 +ALL 0.0.0.0 DIR3 + + diff --git a/projects/traffcounter/test_rules_bad_dir_range b/projects/traffcounter/test_rules_bad_dir_range new file mode 100644 index 00000000..9f633567 --- /dev/null +++ b/projects/traffcounter/test_rules_bad_dir_range @@ -0,0 +1,8 @@ + + +UDP 10.0.0.0/24:1024-65535 DIR0 + +TCP 192.168.1.1:21-22 DIR8945298579834755982745892734958 +ALL 192.168.2.0/16 DIR2 +ALL 0.0.0.0 DIR3 + diff --git a/projects/traffcounter/test_rules_bad_mask b/projects/traffcounter/test_rules_bad_mask new file mode 100644 index 00000000..60ebb1d6 --- /dev/null +++ b/projects/traffcounter/test_rules_bad_mask @@ -0,0 +1,7 @@ + +UDP 10.0.0.0/2a:1024-65535 DIR0 + +TCP 192.168.1.1:21-22 DIR1 +ALL 192.168.2.0/16 DIR2 +ALL 0.0.0.0 DIR3 + diff --git a/projects/traffcounter/test_rules_bad_port b/projects/traffcounter/test_rules_bad_port new file mode 100644 index 00000000..93f246ce --- /dev/null +++ b/projects/traffcounter/test_rules_bad_port @@ -0,0 +1,7 @@ + +UDP 10.0.0.0/24:1024-65535 DIR0 + +TCP 192.168.1.1:21-2a DIR1 +ALL 192.168.2.0/16 DIR2 +ALL 0.0.0.0 DIR3 + diff --git a/projects/traffcounter/test_rules_bad_proto b/projects/traffcounter/test_rules_bad_proto new file mode 100644 index 00000000..8a19a031 --- /dev/null +++ b/projects/traffcounter/test_rules_bad_proto @@ -0,0 +1,7 @@ + +UDP 10.0.0.0/24:1024-65535 DIR0 + +TCP 192.168.1.1:21-22 DIR1 +QMTSP 192.168.2.0/16 DIR2 +ALL 0.0.0.0 DIR3 + diff --git a/projects/traffcounter/traffcounter.cpp b/projects/traffcounter/traffcounter.cpp new file mode 100644 index 00000000..baa999a9 --- /dev/null +++ b/projects/traffcounter/traffcounter.cpp @@ -0,0 +1,460 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + +/* + $Revision: 1.5 $ + $Date: 2009/10/12 08:43:32 $ + $Author: faust $ + */ + +#include +#include +#include + +#include "traffcounter.h" +#include "logger.h" +#include "lock.h" +#include "utils.h" + +//----------------------------------------------------------------------------- +STG::TRAFFCOUNTER::TRAFFCOUNTER() + : rulesFinder(), + pendingPackets(), + sessions(), + cacheHits(0), + cacheMisses(0), + pendingCount(0), + maxPending(0), + ip2sessions(), + stopped(true), + running(false) +{ +LOG_IT << "TRAFFCOUNTER::TRAFFCOUNTER()\n"; +pthread_mutex_init(&sessionMutex, NULL); +pthread_mutex_init(&pendingMutex, NULL); +pthread_mutex_init(&ipMutex, NULL); +pthread_mutex_init(&rulesMutex, NULL); +pthread_cond_init(&pendingCond, NULL); +} +//----------------------------------------------------------------------------- +STG::TRAFFCOUNTER::~TRAFFCOUNTER() +{ +LOG_IT << "TRAFFCOUNTER::~TRAFFCOUNTER()\n"; +pthread_cond_destroy(&pendingCond); +pthread_mutex_destroy(&rulesMutex); +pthread_mutex_destroy(&ipMutex); +pthread_mutex_destroy(&pendingMutex); +pthread_mutex_destroy(&sessionMutex); +} +//----------------------------------------------------------------------------- +// Starting processing thread +bool STG::TRAFFCOUNTER::Start() +{ +LOG_IT << "TRAFFCOUNTER::Start()\n"; + +if (running) + return false; + +running = true; +stopped = true; + +if (pthread_create(&thread, NULL, Run, this)) + { + LOG_IT << "TRAFFCOUNTER::Start() Error: Cannot start thread!\n"; + return true; + } + +return false; +} +//----------------------------------------------------------------------------- +bool STG::TRAFFCOUNTER::Stop() +{ +LOG_IT << "TRAFFCOUNTER::Stop()\n"; +LOG_IT << "maxPending: " << maxPending << std::endl; + +if (!running) + return false; + +running = false; +// Awake thread +pthread_cond_signal(&pendingCond); + +//5 seconds to thread stops itself +for (int i = 0; i < 25 && !stopped; ++i) + { + usleep(200000); + } + +//after 5 seconds waiting thread still running. now kill it +if (!stopped) + { + LOG_IT << "TRAFFCOUNTER::Stop() Killing thread\n"; + if (pthread_kill(thread, SIGINT)) + { + return true; + } + LOG_IT << "TRAFFCOUNTER::Stop() Thread killed\n"; + } + +return false; +} +//----------------------------------------------------------------------------- +double STG::TRAFFCOUNTER::StreamQuality() const +{ +if (!cacheHits && !cacheMisses) + { + return 0; + } + +double quality = cacheHits; +return quality / (quality + cacheMisses); +} +//----------------------------------------------------------------------------- +void STG::TRAFFCOUNTER::AddPacket(const iphdr & ipHdr, uint16_t sport, uint16_t dport) +{ +/* + * Intersects with AddIP (from user thread), DeleteIP (from user thread) and + * Process (internal thread). AddPacket is calling from capturer's thread + * + * ips is affected by AddIP (logarithmic lock time) and + * DeleteIP (from user thread) + * + * May be locked by AddIP or DeleteIP (from user thread) + * + * Lock AddIP (user thread) or DeleteIP (user thread) + * Logarithmic lock time + */ + +bool srcExists; +bool dstExists; + + { + SCOPED_LOCK lock(ipMutex); + srcExists = std::binary_search(ips.begin(), ips.end(), ipHdr.saddr); + dstExists = std::binary_search(ips.begin(), ips.end(), ipHdr.daddr); + } + +if (!srcExists && + !dstExists) + { + // Just drop the packet + return; + } + +STG::PENDING_PACKET p(ipHdr, sport, dport); + +// Packet classification +if (srcExists) + { + if (dstExists) + { + // Both src and dst are countable + p.direction = PENDING_PACKET::LOCAL; + } + else + { + // Src is countable + p.direction = PENDING_PACKET::OUTGOING; + } + } +else + { + if (dstExists) + { + // Dst is countable + p.direction = PENDING_PACKET::INCOMING; + } + else + { + assert(0); + // Not src nor dst are countable + p.direction = PENDING_PACKET::FOREIGN; + } + } + +/* + * pendingPackets is affected by Process (from internal thread) + * + * May be locked by Process (internal thread) + * + * Lock Process (internal thread) + * Constant lock time + */ +SCOPED_LOCK lock(pendingMutex); +pendingPackets.push_back(p); +pendingCount++; +#ifdef STATISTIC +if (pendingCount > maxPending) + maxPending = pendingCount; +#endif +pthread_cond_signal(&pendingCond); + +} +//----------------------------------------------------------------------------- +void STG::TRAFFCOUNTER::AddIP(uint32_t ip) +{ +/* + * AddIP is calling from users and affect DeleteIP and AddPacket. + * DeleteIP cannot be called concurrently with AddIP - it's the same + * thread. AddPacket is calling from capturer's thread - concurrently + * with AddIP. + * + * May be locked by AddPacket (from capturer's thread) + * Logarithmic lock time + * + * Lock AddPacket (capturer's thread) + * Logarithmic lock time + */ +SCOPED_LOCK lock(ipMutex); +IP_ITER it(std::lower_bound(ips.begin(), ips.end(), ip)); + +if (it != ips.end() && *it == ip) + { + return; + } +// Insertion +ips.insert(it, ip); +} +//----------------------------------------------------------------------------- +void STG::TRAFFCOUNTER::DeleteIP(uint32_t ip, STG::TRAFF_DATA * traff) +{ +/* + * DeleteIP is calling from users and affect AddIP, AddPacket, GetIP and + * Process. AddIP and GetIP cannot be called concurrently with DeleteIP - it's + * the same thread. AddPacket is calling from capturer's thread - concurrently + * with DeleteIP. Process is calling from internal thread - concurrently with + * DeleteIP. + * + * May be locked by AddPacket (from capturer's thread) + * Logarithmic lock time + * + * Lock AddPacket (capturer's thread) + * Logarithmic lock time + */ + + { + SCOPED_LOCK lock(ipMutex); + + IP_ITER it(std::lower_bound(ips.begin(), ips.end(), ip)); + if (it == ips.end()) + { + return; + } + if (*it != ip) + { + return; + } + + ips.erase(it); + } + +// Get sessions for this ip +std::pair range; + +SCOPED_LOCK lock(sessionMutex); +range = ip2sessions.equal_range(ip); +std::list toDelete; + +// Lock session growing +for (INDEX_ITER it = range.first; it != range.second; ++it) + { + traff->push_back(STG::TRAFF_ITEM(it->second->first, it->second->second)); + + // Include self + toDelete.push_back(it); + + /*if (ip == it->second->first.saddr) + { + toDelete.push_back(it->second->second.dIdx); + } + else + { + toDelete.push_back(it->second->second.sIdx); + }*/ + + --it->second->second.refCount; + + // Remove session + /* + * Normally we will lock here only in case of session between + * two users from ips list + */ + if (!it->second->second.refCount) + { + sessions.erase(it->second); + } + } + +// Remove indexes +for (std::list::iterator it = toDelete.begin(); + it != toDelete.end(); + ++it) + { + ip2sessions.erase(*it); + } +} +//----------------------------------------------------------------------------- +void STG::TRAFFCOUNTER::GetIP(uint32_t ip, STG::TRAFF_DATA * traff) +{ +/* + * Normally we will lock here only in case of session between + * two users from ips list + */ +std::pair range; + +SCOPED_LOCK lock(sessionMutex); +range = ip2sessions.equal_range(ip); +std::list toDelete; + +// TODO: replace with foreach +for (SESSION_INDEX::iterator it = range.first; + it != range.second; + ++it) + { + traff->push_back(STG::TRAFF_ITEM(it->second->first, it->second->second)); + toDelete.push_back(it); + --it->second->second.refCount; + if (!it->second->second.refCount) + { + sessions.erase(it->second); + } + } + +for (std::list::iterator it = toDelete.begin(); + it != toDelete.end(); + ++it) + { + ip2sessions.erase(*it); + } +} +//----------------------------------------------------------------------------- +void * STG::TRAFFCOUNTER::Run(void * data) +{ +STG::TRAFFCOUNTER * tc = static_cast(data); +tc->stopped = false; + +while (tc->running) + { + STG::PENDING_PACKET packet; + { + SCOPED_LOCK lock(tc->pendingMutex); + if (tc->pendingPackets.empty()) + { + pthread_cond_wait(&tc->pendingCond, &tc->pendingMutex); + } + if (!tc->running) + { + break; + } + packet = *tc->pendingPackets.begin(); + tc->pendingPackets.pop_front(); + --tc->pendingCount; + } + tc->Process(packet); + } + +tc->stopped = true; +return NULL; +} +//----------------------------------------------------------------------------- +void STG::TRAFFCOUNTER::Process(const STG::PENDING_PACKET & p) +{ +// Bypass on stop +if (!running) + return; + +// Fail on foreign packets +if (p.direction == PENDING_PACKET::FOREIGN) { + assert(0); +} + +// Searching a new packet in a tree. +SESSION_ITER si; + { + SCOPED_LOCK lock(sessionMutex); + si = sessions.find(STG::SESSION_ID(p)); + } + +// Packet found - update length and time +if (si != sessions.end()) + { + // Grow session + SCOPED_LOCK lock(sessionMutex); + si->second.length += p.length; + ++cacheHits; + return; + } + +++cacheMisses; + +// Packet not found - add new packet + +// This packet is alowed to create session +STG::SESSION_ID sid(p); +SESSION_FULL_DATA sd; + +// Identify a packet + { + SCOPED_LOCK lock(rulesMutex); + sd.dir = rulesFinder.GetDir(p); + } + +sd.length = p.length; + +if (p.direction == PENDING_PACKET::LOCAL) + { + sd.refCount = 2; + } +else + { + sd.refCount = 1; + } + +// Create a session +std::pair sIt(sessions.insert(std::make_pair(sid, sd))); + { + SCOPED_LOCK lock(sessionMutex); + std::pair sIt(sessions.insert(std::make_pair(sid, sd))); + + // Create an indexes + sIt.first->second.sIdx = ip2sessions.insert(std::make_pair(p.saddr, sIt.first)); + sIt.first->second.dIdx = ip2sessions.insert(std::make_pair(p.daddr, sIt.first)); + } + +} +//----------------------------------------------------------------------------- +void STG::TRAFFCOUNTER::SetRules(const STG::RULES & data) +{ +/* + * SetRules is calling from outside internel thread. Process is calling + * from internal thread and calls DeterminateDir which use rules data. + * + * May be locked by DeterminateDir (Process) from internal thread. + * + * Lock DeterminateDir (Process) - internal thread. + * Linear lock time + */ +SCOPED_LOCK lock(rulesMutex); +rulesFinder.SetRules(data); +} diff --git a/projects/traffcounter/traffcounter.h b/projects/traffcounter/traffcounter.h new file mode 100644 index 00000000..418620b1 --- /dev/null +++ b/projects/traffcounter/traffcounter.h @@ -0,0 +1,165 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.3 $ + $Date: 2009/04/10 14:15:46 $ + $Author: faust $ + */ + + +#ifndef TRAFFCOUNTER_H +#define TRAFFCOUNTER_H + +#include +#include + +#ifdef HAVE_STDINT + #include +#else + #ifdef HAVE_INTTYPES + #include + #else + #error "You need either stdint.h or inttypes.h to compile this!" + #endif +#endif + +#include +#include +#include +#include + +#include "rules.h" +#include "rules_finder.h" +#include "tc_packets.h" +#include "user_tc_iface.h" +#include "capturer_tc_iface.h" + +#define PACKET_TIMEOUT 300 + +namespace STG +{ + + class TRAFFCOUNTER : public IUSER_TC, public ICAPTURER_TC + { + public: + TRAFFCOUNTER(); + ~TRAFFCOUNTER(); + + void SetRules(const RULES & data); + + bool Start(); + bool Stop(); + + // Capturer API + void AddPacket(const iphdr & ipHdr, uint16_t sport, uint16_t dport); + + // User API + void AddIP(uint32_t ip); + void DeleteIP(uint32_t ip, TRAFF_DATA * traff); + void GetIP(uint32_t ip, TRAFF_DATA * traff); + + /* + * Stream quality represents a "scatterness" of data stream + * When sessions represend a large amount of information - it's a good + * stream. Most of common-use protocols (HTTP, FTP, etc.) shows a good + * stream quality. + * When there are a lot of packet that creates a new streams - it's a + * bad stream. p2p traffic has a bias to show a bad stream quality. + */ + double StreamQuality() const; + uint64_t PendingCount() const { return pendingCount; }; + uint64_t SessionsCount() const { return sessions.size(); }; + uint64_t IndexesCount() const { return ip2sessions.size(); }; + uint64_t CacheHits() const { return cacheHits; }; + uint64_t CacheMisses() const { return cacheMisses; }; + + private: + static void * Run(void * data); + + void Process(const PENDING_PACKET & p); + + RULES_FINDER rulesFinder; + + /* + * SESSION_INDEX: ip -> SESSION_ITER + * SESSIONS: SESSION_ID -> SESSION_DATA + * -> SESSION_INDEX (saddr) + * -> SESSION_INDEX (daddr) + */ + struct SESSION_FULL_DATA; // Forward declaration + typedef std::map SESSIONS; + typedef SESSIONS::iterator SESSION_ITER; + /* + * This structure is used to take a fast session access by IP + * Normally, one IP can reffer multiple sessions. For each data stream there + * are 2 sessions: incoming data and outgoing data. + */ + typedef std::multimap SESSION_INDEX; + typedef SESSION_INDEX::iterator INDEX_ITER; + /* + * Append session meta-information with back-indexes + * In process of removing IP from TRAFFCOUNTER we need to remove indexes of + * sessions, reffered by this IP. To prevent slow searching by index tree we + * use 2 back-references: for source and destination IP. + */ + struct SESSION_FULL_DATA : public SESSION_DATA + { + INDEX_ITER sIdx; // Back reference for fast index removing + INDEX_ITER dIdx; // Back reference for fast index removing + int refCount; // Reference count for packet removing + }; + + std::list pendingPackets; + + SESSIONS sessions; // A map with sessions data + + /* + * When pending packet appends a session - it's a "cache hit" + * When pending packet creates a new session - it's a "cache miss" + */ + uint64_t cacheHits; + uint64_t cacheMisses; + uint64_t pendingCount; + uint64_t maxPending; + + SESSION_INDEX ip2sessions; // IP index for sessions data + + /* + * A sorted vector of allowed/disallowed ips + */ + std::vector ips; + typedef std::vector::iterator IP_ITER; + + bool stopped; + bool running; + + mutable pthread_mutex_t sessionMutex; // For sessions + mutable pthread_mutex_t pendingMutex; // For pendinPackets + mutable pthread_cond_t pendingCond; // + mutable pthread_mutex_t ipMutex; // For ip list + mutable pthread_mutex_t rulesMutex; // For rules list + pthread_t thread; + + }; + +} + +#endif //TRAFFCOUNTER_H diff --git a/projects/traffcounter/user_tc_iface.h b/projects/traffcounter/user_tc_iface.h new file mode 100644 index 00000000..d70034a8 --- /dev/null +++ b/projects/traffcounter/user_tc_iface.h @@ -0,0 +1,30 @@ +#ifndef __USER_TC_IFACE_H__ +#define __USER_TC_IFACE_H__ + +#ifdef HAVE_STDINT + #include +#else + #ifdef HAVE_INTTYPES + #include + #else + #error "You need either stdint.h or inttypes.h to compile this!" + #endif +#endif + +#include "tc_packets.h" + +namespace STG +{ + + class IUSER_TC + { + public: + virtual ~IUSER_TC() {}; + virtual void AddIP(uint32_t) = 0; + virtual void DeleteIP(uint32_t, TRAFF_DATA *) = 0; + virtual void GetIP(uint32_t, TRAFF_DATA *) = 0; + }; + +} + +#endif diff --git a/projects/traffcounter/utils.cpp b/projects/traffcounter/utils.cpp new file mode 100644 index 00000000..ac8634c8 --- /dev/null +++ b/projects/traffcounter/utils.cpp @@ -0,0 +1,55 @@ +#include +#include + +#include +#include +#include + +#include + +#include "utils.h" + +using namespace std; + +string STG::ToLower(const string & val, const locale & loc) +{ + std::string res; + transform(val.begin(), + val.end(), + back_inserter(res), + STG::ToLowerHelper(loc)); + return res; +} + +string STG::ToUpper(const string & val, const locale & loc) +{ + std::string res; + transform(val.begin(), + val.end(), + back_inserter(res), + STG::ToUpperHelper(loc)); + return res; +} + +string STG::Trim(const string & val, const locale & loc) +{ + if (val.empty()) + return std::string(); + string::const_iterator first(find_if( + val.begin(), + val.end(), + STG::IsNotSpace(loc))); + string::const_reverse_iterator last(find_if( + val.rbegin(), + val.rend(), + STG::IsNotSpace(loc))); + if (first == val.end()) + return std::string(); + return std::string(first, last.base()); +} +std::string inet_ntostring(uint32_t ip) +{ + char buf[INET_ADDRSTRLEN + 1]; + + return inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN); +} diff --git a/projects/traffcounter/utils.h b/projects/traffcounter/utils.h new file mode 100644 index 00000000..92d32e78 --- /dev/null +++ b/projects/traffcounter/utils.h @@ -0,0 +1,68 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include + +namespace STG +{ + +class IsNotSpace : public std::unary_function { +public: + IsNotSpace(const std::locale & l) : loc(l) {}; + bool operator() (char c) + { + return !std::use_facet(loc).is(std::ctype_base::space, c); + }; +private: + const std::locale & loc; + + typedef std::ctype casefacet; +}; + +class ToLowerHelper : public std::unary_function { +public: + ToLowerHelper(const std::locale & l) : loc(l) {}; + char operator() (char c) + { + return std::tolower(c, loc); + }; +private: + const std::locale & loc; +}; + +class ToUpperHelper : public std::unary_function { +public: + ToUpperHelper(const std::locale & l) : loc(l) {}; + char operator() (char c) + { + return std::toupper(c, loc); + }; +private: + const std::locale & loc; +}; + +std::string Trim(const std::string & val, const std::locale & loc); +std::string ToLower(const std::string & val, const std::locale & loc); +std::string ToUpper(const std::string & val, const std::locale & loc); + +inline std::string Trim(const std::string & val) + { + return Trim(val, std::locale("")); + } + +inline std::string ToLower(const std::string & val) + { + return ToLower(val, std::locale("")); + } + +inline std::string ToUpper(const std::string & val) + { + return ToUpper(val, std::locale("")); + } + +} + +std::string inet_ntostring(uint32_t ip); + +#endif diff --git a/stglibs/Makefile b/stglibs/Makefile new file mode 100644 index 00000000..f7d5e45e --- /dev/null +++ b/stglibs/Makefile @@ -0,0 +1,22 @@ +############################################################################### +# $Id: Makefile,v 1.7 2008/03/10 14:00:00 faust Exp $ +############################################################################### + +include ../Makefile.conf + +.PHONY: all $(STG_LIBS) +.PHONY: clean install uninstall includes +all: $(STG_LIBS) + +$(STG_LIBS): + $(MAKE) $(MAKECMDGOALS) -C $@ + +includes: all + +clean: all + rm -f $(DIR_LIB)/* + +install: all + +uninstall: all + diff --git a/stglibs/Makefile.in b/stglibs/Makefile.in new file mode 100644 index 00000000..0c42dd51 --- /dev/null +++ b/stglibs/Makefile.in @@ -0,0 +1,94 @@ +############################################################################### +# $Id: Makefile.in,v 1.28 2009/03/03 15:50:14 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +PROG = lib$(LIB_NAME).so + +ifeq ($(PROT), on) +DEFS += -DLINPROT +endif + +ifeq ($(DEMO), on) +DEFS += -DDEMO +endif + +ifeq ($(OS), bsd) + ifeq ($(PROT), on) + DEFS += -DFREEBDSPROT + endif +MAKE = gmake +endif + +ifeq ($(OS), bsd5) + ifeq ($(PROT), on) + DEFS += -DFREEBDSPROT + endif +MAKE = gmake +endif + +ifeq ($(STG_TIME), yes) +DEFS += -DSTG_TIME +endif + +ifneq ($(ADD_DEFS_1),) +DEFS += $(ADD_DEFS_1) +endif + +SEARCH_DIRS = -I $(DIR_INCLUDE) -I ./ + +OBJS = $(notdir $(patsubst %.cpp, %.o, $(patsubst %.c, %.o, $(SRCS)))) + +INST_INCS = $(addprefix $(DIR_INCLUDE)/, $(notdir $(INCS))) +INST_LIBS = $(DIR_LIB)/lib$(LIB_NAME) + +CXXFLAGS += -fPIC +LDFLAGS += -shared -Wl,-rpath,$(PREFIX)/usr/lib/stg + +ifneq ($(ADD_CXXFLAGS_1),) +CXXFLAGS += $(ADD_CXXFLAGS_1) +endif + +vpath %.so $(DIR_LIB) + +all: $(PROG) + +$(PROG): $(OBJS) $(STGLIBS) + g++ $(LDFLAGS) -Wl,-soname,$(PROG) $^ $(LIBS) -o $(PROG) -L $(DIR_LIB) + ar rc lib$(LIB_NAME).a $(OBJS) + ranlib lib$(LIB_NAME).a + cp *.so $(DIR_LIB) + cp *.a $(DIR_LIB) + +includes: $(INCS) + cp -p $(INCS) $(DIR_INCLUDE) + +clean: + rm -f deps $(PROG) *.o *.a *.so tags *.*~ + for file in $(INCS); do \ + rm -f $(DIR_INCLUDE)/$$file; \ + done + +install: $(PROG) + mkdir -m $(BIN_MODE) -p $(PREFIX)/usr/lib/stg + install -m $(BIN_MODE) -o $(OWNER) -s $(PROG) $(PREFIX)/usr/lib/stg/$(PROG) + +uninstall: + rm -f $(PREFIX)/usr/lib/stg/$(PROG) + +ifneq ($(MAKECMDGOALS),includes) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),uninstall) +-include deps +endif +endif +endif + +deps: $(SRCS) ../../Makefile.conf + @>deps ;\ + for file in $(SRCS); do\ + echo "`$(CC) $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS) -MM -MG $$file` Makefile ../../Makefile.conf" >> deps ;\ + echo -e '\t$$(CC) $(CXXFLAGS) $(SEARCH_DIRS) $(DEFS) -c $$<' >> deps ;\ + done + diff --git a/stglibs/common.lib/Makefile b/stglibs/common.lib/Makefile new file mode 100644 index 00000000..d1c816b6 --- /dev/null +++ b/stglibs/common.lib/Makefile @@ -0,0 +1,23 @@ +############################################################################### +# $Id: Makefile,v 1.9 2010/01/21 13:02:12 faust Exp $ +############################################################################### + +include ../../Makefile.conf + +LIB_NAME = stg_common +PROG = lib$(LIB_NAME) + +SRCS = debug.c \ + stg_error.c \ + common.cpp \ + stg_strptime.cpp + +INCS = debug.h \ + stg_error.h \ + common.h + +ifneq ($(OS),linux) +LIBS += -liconv +endif + +include ../Makefile.in diff --git a/stglibs/common.lib/common.bpf b/stglibs/common.lib/common.bpf new file mode 100644 index 00000000..e3c42ce0 --- /dev/null +++ b/stglibs/common.lib/common.bpf @@ -0,0 +1,9 @@ +//--------------------------------------------------------------------------- + +#include +#pragma hdrstop +#define Library + +// To add a file to the library use the Project menu 'Add to Project'. + + \ No newline at end of file diff --git a/stglibs/common.lib/common.bpr b/stglibs/common.lib/common.bpr new file mode 100644 index 00000000..d7cb659f --- /dev/null +++ b/stglibs/common.lib/common.bpr @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Version Info] +IncludeVerInfo=0 +AutoIncBuild=0 +MajorVer=1 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=1049 +CodePage=1251 + +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=1.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=1.0.0.0 +Comments= + +[HistoryLists\hlIncludePath] +Count=2 +Item0=$(BCB)\include;$(BCB)\include\vcl;..\..\include +Item1=$(BCB)\include;$(BCB)\include\vcl + +[HistoryLists\hlLibraryPath] +Count=2 +Item0=$(BCB)\lib\obj;$(BCB)\lib;..\..\lib +Item1=$(BCB)\lib\obj;$(BCB)\lib + +[HistoryLists\hlDebugSourcePath] +Count=1 +Item0=$(BCB)\source\vcl + +[HistoryLists\hlConditionals] +Count=2 +Item0=_DEBUG;WIN32 +Item1=_DEBUG + +[HistoryLists\hlFinalOutputDir] +Count=2 +Item0=..\..\lib\ +Item1=..\..\lib + +[Debugging] +DebugSourceDirs=$(BCB)\source\vcl + +[Parameters] +RunParams= +Launcher= +UseLauncher=0 +DebugCWD= +HostApplication= +RemoteHost= +RemotePath= +RemoteLauncher= +RemoteCWD= +RemoteDebug=0 + +[Compiler] +ShowInfoMsgs=0 +LinkDebugVcl=0 +LinkCGLIB=0 + +[CORBA] +AddServerUnit=1 +AddClientUnit=1 +PrecompiledHeaders=1 + + \ No newline at end of file diff --git a/stglibs/common.lib/common.cpp b/stglibs/common.lib/common.cpp new file mode 100644 index 00000000..a5899dd2 --- /dev/null +++ b/stglibs/common.lib/common.cpp @@ -0,0 +1,885 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.41 $ + $Date: 2010/11/03 10:26:30 $ + $Author: faust $ + */ + + +/*#include +#include +#include +#include +#include +#include +#include + + +#include + + +#include +#include + +#ifdef WIN32 +#include +#else +#include +#include +#include +#include +#include +#endif*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#ifndef INET_ADDRSTRLEN +# define INET_ADDRSTRLEN 16 +#endif + +using namespace std; + +//----------------------------------------------------------------------------- +int strtodouble2(const char * s, double &a) +{ +char *res; + +a = strtod(s, &res); + +if (*res != 0) + return EINVAL; + +return 0; +} +//----------------------------------------------------------------------------- +#ifdef DEBUG +int printfd(const char * __file__, const char * fmt, ...) +#else +int printfd(const char *, const char *, ...) +#endif +{ +#ifdef DEBUG +char buff[1024]; + +time_t t = time(NULL); + +va_list vl; +va_start(vl, fmt); +vsnprintf(buff, sizeof(buff), fmt, vl); +va_end(vl); + +printf("%18s > %s > ", __file__, LogDate(t)+11); +printf("%s", buff); + +#endif +return 0; +} +//----------------------------------------------------------------------------- +int strprintf(string * str, const char * fmt, ...) +{ +char buff[1024]; + +va_list vl; +va_start(vl, fmt); +int n = vsnprintf(buff, sizeof(buff), fmt, vl); +va_end(vl); +buff[1023] = 0; +*str = buff; + +return n; +} +//----------------------------------------------------------------------------- +const char *IntToKMG(long long a, int stat) +{ +static int64_t M = 1024*1024; +static int64_t G = 1024*1024*1024; +static char str[30]; + +switch (stat) + { + case ST_B: + #ifdef __WIN32__ + sprintf(str, "%Ld", a); + #else + sprintf(str, "%lld", a); + #endif + break; + case ST_KB: + sprintf(str, "%.2f kb", double(a)/1024.0); + break; + case ST_MB: + sprintf(str, "%.2f Mb", double(a)/(1024.0*1024.0)); + break; + default: + if (a > G) + { + sprintf(str, "%.2f Gb", double(a)/double(G)); + return &str[0]; + } + if (a < -G) + { + sprintf(str, "%.2f Gb", double(a)/double(G)); + return &str[0]; + } + if (a > M) + { + sprintf(str, "%.2f Mb", double(a)/double(M)); + return &str[0]; + } + if (a < -M) + { + sprintf(str, "%.2f Mb", double(a)/double(M)); + return &str[0]; + } + + sprintf(str, "%.2f kb", double(a)/1024.0); + break; + } +return str; +} +//--------------------------------------------------------------------------- +unsigned char koi2win[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xB8, 0xBA, 0xA5, 0xB3, 0xBF, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xB4, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xA8, 0xAA, 0xB5, 0xB2, 0xAF, + 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xA5, 0xBE, 0xBF, + 0xFE, 0xE0, 0xE1, 0xF6, 0xE4, 0xE5, 0xF4, 0xE3, + 0xF5, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, + 0xEF, 0xFF, 0xF0, 0xF1, 0xF2, 0xF3, 0xE6, 0xE2, + 0xFC, 0xFB, 0xE7, 0xF8, 0xFD, 0xF9, 0xF7, 0xFA, + 0xDE, 0xC0, 0xC1, 0xD6, 0xC4, 0xC5, 0xD4, 0xC3, + 0xD5, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, + 0xCF, 0xDF, 0xD0, 0xD1, 0xD2, 0xD3, 0xC6, 0xC2, + 0xDC, 0xDB, 0xC7, 0xD8, 0xDD, 0xD9, 0xD7, 0xDA}; + + +unsigned char win2koi[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xBD, 0xA6, 0xA7, + 0xB3, 0xA9, 0xB4, 0xAB, 0xAC, 0xAD, 0xAE, 0xB7, + 0xB0, 0xB1, 0xB6, 0xA6, 0xAD, 0xB5, 0xB6, 0xB7, + 0xA3, 0xB9, 0xA4, 0xBB, 0xBC, 0xBD, 0xBE, 0xA7, + 0xE1, 0xE2, 0xF7, 0xE7, 0xE4, 0xE5, 0xF6, 0xFA, + 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, + 0xF2, 0xF3, 0xF4, 0xF5, 0xE6, 0xE8, 0xE3, 0xFE, + 0xFB, 0xFD, 0xFF, 0xF9, 0xF8, 0xFC, 0xE0, 0xF1, + 0xC1, 0xC2, 0xD7, 0xC7, 0xC4, 0xC5, 0xD6, 0xDA, + 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, + 0xD2, 0xD3, 0xD4, 0xD5, 0xC6, 0xC8, 0xC3, 0xDE, + 0xDB, 0xDD, 0xDF, 0xD9, 0xD8, 0xDC, 0xC0, 0xD1}; +//--------------------------------------------------------------------------- +void KOIToWin(const char * s1, char * s2, int l) +{ +unsigned char t; +for (int j = 0; j < l; j++) + { + t = s1[j]; + s2[j] = koi2win[t]; + + if (s1[j] == 0) + break; + } +} +//--------------------------------------------------------------------------- +void WinToKOI(const char * s1, char * s2, int l) +{ +unsigned char t; +for (int j = 0; j < l; j++) + { + t = s1[j]; + s2[j] = win2koi[t]; + + if (s1[j] == 0) + break; + } +} +//--------------------------------------------------------------------------- +void KOIToWin(const string & s1, string * s2) +{ +s2->erase(s2->begin(), s2->end()); +unsigned char t; +s2->reserve(s1.length()); +for (int j = 0; j < (int)s1.length(); j++) + { + t = s1[j]; + s2->push_back(koi2win[t]); + } +} +//--------------------------------------------------------------------------- +void WinToKOI(const string & s1, string * s2) +{ +s2->erase(s2->begin(), s2->end()); +unsigned char t; +s2->reserve(s1.length()); +for (int j = 0; j < (int)s1.length(); j++) + { + t = s1[j]; + s2->push_back(win2koi[t]); + } +} +//--------------------------------------------------------------------------- +void Encode12str(string & dst, const string & src) +{ +dst.erase(dst.begin(), dst.end()); +for (size_t i = 0; i < src.length(); i++) + { + dst.push_back('a' + (src[i] & 0x0f)); + dst.push_back('a' + ((src[i] & 0xf0) >> 4)); + } +} +//--------------------------------------------------------------------------- +void Decode21str(std::string & dst, const std::string & src) +{ +dst.erase(dst.begin(), dst.end()); +for (size_t i = 0; i < src.length() / 2; i++) + { + char c1 = src[i * 2]; + char c2 = src[i * 2 + 1]; + + c1 -= 'a'; + c2 -= 'a'; + + dst.push_back(c1 + (c2 << 4)); + } +} +//--------------------------------------------------------------------------- +void Encode12(char * dst, const char * src, size_t srcLen) +{ +for (size_t i = 0; i <= srcLen; i++) + { + if (src[i] == 0) + { + dst[i * 2] = 'a'; + dst[i * 2 + 1] = 'a'; + break; + } + char c1 = src[i] & 0x0f; + char c2 = (src[i] & 0xf0) >> 4; + + c1 += 'a'; + c2 += 'a'; + + dst[i * 2] = c1; + dst[i * 2 + 1] = c2; + } +dst[srcLen * 2] = 0; +} +//--------------------------------------------------------------------------- +void Decode21(char * dst, const char * src) +{ +for (size_t i = 0; ; i++) + { + if (src[i * 2] == 0) + break; + + char c1 = src[i * 2]; + char c2 = src[i * 2 + 1]; + + c1 -= 'a'; + c2 -= 'a'; + + dst[i] = c1 + (c2 << 4); + } +dst[strlen(src) / 2] = 0; +} +//--------------------------------------------------------------------------- +int ParseIPString(const char * str, uint32_t * ips, int maxIP) +{ +/* + *Function Name:ParseIPString + * + *Parameters: + ÓÔÒÏËÁ ÄÌÑ ÒÁÚÂÏÒÁ É ÍÁÓÓÉ× ËÕÄÁ ÚÁÎÏÓÉÔØ ÐÏÌÕÞÅÎÎÙÅ ÁÄÒÅÓÁ + * + *Description: + îÁ ×ÈÏÄÅ ÄÏÌÖÎÁ ÂÙÔØ ÓÔÒÏËÁ ×ÉÄÁ "ip1,ip2,ip3" ÉÌÉ "*" + ÷ ÐÅÒ×ÏÍ ÓÌÕÞÁÅ × ÍÁÓÓÉ× ÚÁÎÏÓÑÔÓÑ ÒÁÚÏÂÒÁÎÎÙÅ ÁÄÒÅÓÁ. + åÓÌÉ ÉÈ ÍÅÎØÛÅ MAX_IP?, ÔÏ ÐÏÓÌÅÄÎÉÊ ÁÄÒÅÓ ÂÕÄÅÔ 255.255.255.255 + åÓÌÉ ÓÔÒÏËÁ * , ÔÏ ÐÅÒ×ÁÙÊ ÁÄÒÅÓ ÂÕÄÅÔ 0.0.0.0, Ô.Å. ÌÀÂÏÊ + * + *Returns: 0 ÅÓÌÉ ×ÓÅ ïë + * + */ + +char p[255]; +char * p1, *pp; +int n = 0; + +strncpy(p, str, 254); +pp = p; + +memset(ips, 0xFF, sizeof(unsigned long) * maxIP); + +if (str[0] == '*' && strlen(str) == 1) + { + ips[0] = 0; + return 0; + } + +for (int i = 0; i < maxIP; i++) + { + p1 = strtok(pp, ",\n "); + pp = NULL; + + if (p1 == NULL && n == 0)// ÕËÁÚÁÔÅÌØ ÎÕÌØ É ÐÒÏÞÉÔÁÎÏ ÁÄÒÅÓÏ× ÔÏÖÅ ÎÏÌØ + { + return EINVAL; + } + + if (p1 == NULL && n) + { + return 0; + } + + struct in_addr in; + if (!inet_aton(p1, &in)) + { + //printf("INADDR_NONE\n"); + return EINVAL; + } + + ips[n] = in.s_addr; + + /*if (ips[n] == INADDR_NONE) + return EINVAL;*/ + + n++; + + if (n >= maxIP) + return 0; + + } + +return 0; +} +//----------------------------------------------------------------------------- +int DaysInCurrentMonth() +{ +time_t t = time(NULL); + +struct tm * lt = localtime(&t); + +return DaysInMonth(lt->tm_year, lt->tm_mon); +} +//----------------------------------------------------------------------------- +int DaysInMonth(unsigned year, unsigned mon) +{ +assert(mon < 12 && "Month number should be 0 - 11"); +switch (mon) + { + case 0: return 31; //jan + case 1: + if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) + return 29; + return 28; //feb + case 2: return 31; //mar + case 3: return 30; //apr + case 4: return 31; //may + case 5: return 30; //june + case 6: return 31; //jule + case 7: return 31; //aug + case 8: return 30; //sep + case 9: return 31; //oct + case 10: return 30; //nov + case 11: return 31; //dec + } +return -1; // We will never reach here +} +//----------------------------------------------------------------------------- +int Min8(int a) +{ +/* +æÕÎËÃÉÑ ×ÏÚ×ÒÁÝÁÅÔ ÎÁÉÍÅÎØÛÅÅ ÞÉÓÌÏ ËÒÁÔÎÏÅ 8-ÍÉ ÂÏÌØÛÅÅ ÉÌÉ ÒÁ×ÎÏÅ ÚÁÄÁÎÎÏÍÕ + * */ +if (a % 8 == 0) + return a; + +return a + (8 - a % 8); +} +//----------------------------------------------------------------------------- +/*char * inet_ntostr(unsigned long ip) +{ +struct in_addr addr = {ip}; +return inet_ntoa(addr); +}*/ +//----------------------------------------------------------------------------- +std::string inet_ntostring(uint32_t ip) +{ + char buf[INET_ADDRSTRLEN + 1]; + return inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN); +} +//----------------------------------------------------------------------------- +uint32_t inet_strington(const std::string & value) +{ + uint32_t result; + + if (inet_pton(AF_INET, value.c_str(), &result) <= 0) + return 0; + + return result; +} +//----------------------------------------------------------------------------- +int ParseTariffTimeStr(const char * str, int &h1, int &m1, int &h2, int &m2) +{ +char hs1[10], ms1[10], hs2[10], ms2[10]; +char s1[25], s2[25]; +char ss[49]; +char *p1, *p2; + +strncpy(ss, str, 48); + +p1 = strtok(ss, "-"); +if (!p1) + return -1; + +strncpy(s1, p1, 24); + +p2 = strtok(NULL, "-"); +if (!p2) + return -1; + +strncpy(s2, p2, 24); + +p1 = strtok(s1, ":"); +if (!p1) + return -1; + +strncpy(hs1, p1, 9); + +p2 = strtok(NULL, ":"); +if (!p2) + return -1; + +strncpy(ms1, p2, 9); + +p1 = strtok(s2, ":"); +if (!p1) + return -1; + +strncpy(hs2, p1, 9); + +p2 = strtok(NULL, ":"); +if (!p2) + return -1; + +strncpy(ms2, p2, 9); + +if (str2x(hs1, h1) != 0) + return -1; + +if (str2x(ms1, m1) != 0) + return -1; + +if (str2x(hs2, h2) != 0) + return -1; + +if (str2x(ms2, m2) != 0) + return -1; + +return 0; +} +/*//--------------------------------------------------------------------------- +bool IsDigit(char c) +{ +if (c >= '0' && c <= '9') + return true; +return false; +} +//----------------------------------------------------------------------------- +bool IsAlpha(char c) +{ +if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + return true; +return false; +}*/ +//----------------------------------------------------------------------------- +const char * LogDate(time_t t) +{ +static char s[32]; +struct tm * tt = localtime(&t); + +snprintf(s, 20, "%d-%s%d-%s%d %s%d:%s%d:%s%d", + tt->tm_year + 1900, + tt->tm_mon + 1 < 10 ? "0" : "", tt->tm_mon + 1, + tt->tm_mday < 10 ? "0" : "", tt->tm_mday, + tt->tm_hour < 10 ? "0" : "", tt->tm_hour, + tt->tm_min < 10 ? "0" : "", tt->tm_min, + tt->tm_sec < 10 ? "0" : "", tt->tm_sec); + +return s; +} +//----------------------------------------------------------------------------- +uint32_t CalcMask(uint32_t msk) +{ +if (msk >= 32) return 0xFFffFFff; +if (msk == 0) return 0; +return htonl(0xFFffFFff << (32 - msk)); +} +//--------------------------------------------------------------------------- +void TouchFile(const string & fileName) +{ +FILE * f = fopen(fileName.c_str(), "w"); +if (f) + fclose(f); +} +//--------------------------------------------------------------------------- +#ifdef WIN32 +void EncodeStr(char * str, unsigned long serial, int useHDD) +{ +int len = strlen(str); +char stren[100]; +int i, j = 0; +char c1, c2; +char serial_c[sizeof(serial)]; +memcpy(serial_c, &serial, sizeof(serial)); + +for (i = 0; i < len; i++) + { + if (!useHDD) + str[i] = str[i]^49; + else + { + str[i] = str[i]^serial_c[j%sizeof(serial)]; + j++; + } + } + +for (i = 0; i < 2*len; i++) + { + if (i%2) + { + c1 = (str[i/2] >> 4); + c1 = c1 + 50; + stren[i] = c1; + } + else + { + c2 = (str[i/2] & 0x0f); + c2 += 50; + stren[i] = c2; + } + } +stren[i] = 0; +strcpy(str, stren); +} +//--------------------------------------------------------------------------- +void DecodeStr(char * str, unsigned long serial, int useHDD) +{ +int len = strlen(str); +char strdc[100]; +int i, j = 0; +char c1, c2; +char serial_c[sizeof(serial)]; +memcpy(serial_c, &serial, sizeof(serial)); + +for (i = 0; i < len; i += 2) + { + c1 = (str[i] - 50); + c2 = (str[i+1] - 50)<<4; + strdc[i/2] = c1+c2; + } +for (i = 0; i < len/2; i++) + { + if (!useHDD) + strdc[i] = strdc[i]^49; + else + { + strdc[i] = strdc[i]^serial_c[j%sizeof(serial)]; + j++; + } + } +strdc[i] = 0; +strcpy(str, strdc); +} +//--------------------------------------------------------------------------- +#endif //WIN32 +void SwapBytes(uint16_t & value) +{ + value = (value >> 8) | + (value << 8); +} +//--------------------------------------------------------------------------- +void SwapBytes(uint32_t & value) +{ + value = (value >> 24) | + ((value << 8) & 0x00FF0000L)| + ((value >> 8) & 0x0000FF00L)| + (value << 24); +} +//--------------------------------------------------------------------------- +void SwapBytes(uint64_t & value) +{ + value = (value >> 56) | + ((value << 40) & 0x00FF000000000000LL) | + ((value << 24) & 0x0000FF0000000000LL) | + ((value << 8) & 0x000000FF00000000LL) | + ((value >> 8) & 0x00000000FF000000LL) | + ((value >> 24) & 0x0000000000FF0000LL) | + ((value >> 40) & 0x000000000000FF00LL) | + (value << 56); +} +//--------------------------------------------------------------------------- +void SwapBytes(int16_t & value) +{ + uint16_t temp = value; + SwapBytes(temp); + value = temp; +} +//--------------------------------------------------------------------------- +void SwapBytes(int32_t & value) +{ + uint32_t temp = value; + SwapBytes(temp); + value = temp; +} +//--------------------------------------------------------------------------- +void SwapBytes(int64_t & value) +{ + uint64_t temp = value; + SwapBytes(temp); + value = temp; +} +//--------------------------------------------------------------------------- +int str2x(const std::string & str, int & x) +{ +x = strtol(str.c_str(), NULL, 10); + +if (errno == ERANGE) + return -1; + +return 0; +} +//--------------------------------------------------------------------------- +int str2x(const std::string & str, unsigned & x) +{ +x = strtoul(str.c_str(), NULL, 10); + +if (errno == ERANGE) + return -1; + +return 0; +} +//--------------------------------------------------------------------------- +int str2x(const std::string & str, long long & x) +{ +x = strtoll(str.c_str(), NULL, 10); + +if (errno == ERANGE) + return -1; + +return 0; +} +//--------------------------------------------------------------------------- +int str2x(const std::string & str, unsigned long long & x) +{ +x = strtoull(str.c_str(), NULL, 10); + +if (errno == ERANGE) + return -1; + +return 0; +} +//--------------------------------------------------------------------------- +const std::string & x2str(unsigned x, std::string & s) +{ +return unsigned2str(x, s); +} +//--------------------------------------------------------------------------- +const std::string & x2str(unsigned long long x, std::string & s) +{ +return unsigned2str(x, s); +} +//--------------------------------------------------------------------------- +std::string & TrimL(std::string & val) +{ +size_t pos = val.find_first_not_of(" \t"); +if (pos == std::string::npos) + { + val.erase(val.begin(), val.end()); + } +else + { + val.erase(0, pos); + } +return val; +} +//--------------------------------------------------------------------------- +std::string & TrimR(std::string & val) +{ +size_t pos = val.find_last_not_of(" \t"); +if (pos != std::string::npos) + { + val.erase(pos + 1); + } +return val; +} +//--------------------------------------------------------------------------- +std::string & Trim(std::string & val) +{ +return TrimR(TrimL(val)); +} +//--------------------------------------------------------------------------- +time_t stg_timegm(struct tm * brokenTime) +{ +#ifdef HAVE_TIMEGM +return timegm(brokenTime); +#else +time_t ret; +char *tz; +tz = getenv("TZ"); +setenv("TZ", "", 1); +tzset(); +ret = mktime(brokenTime); +if (tz) + setenv("TZ", tz, 1); +else + unsetenv("TZ"); +tzset(); +return ret; +#endif +} +//--------------------------------------------------------------------------- +std::string IconvString(const std::string & source, + const std::string & from, + const std::string & to) +{ +if (source.empty()) + return std::string(); + +size_t inBytesLeft = source.length() + 1; +size_t outBytesLeft = source.length() * 2 + 1; + +char * inBuf = new char[inBytesLeft]; +char * outBuf = new char[outBytesLeft]; + +strncpy(inBuf, source.c_str(), source.length()); + +inBuf[source.length()] = 0; + +#if defined(FREE_BSD) || defined(FREE_BSD5) +const char * srcPos = inBuf; +#else +char * srcPos = inBuf; +#endif +char * dstPos = outBuf; + +iconv_t handle = iconv_open(to.c_str(), + from.c_str()); + +if (handle == iconv_t(-1)) + { + if (errno == EINVAL) + { + printfd(__FILE__, "IconvString(): iconv from %s to %s failed\n", from.c_str(), to.c_str()); + delete[] outBuf; + delete[] inBuf; + return source; + } + else + printfd(__FILE__, "IconvString(): iconv_open error\n"); + + delete[] outBuf; + delete[] inBuf; + return source; + } + +size_t res = iconv(handle, + &srcPos, &inBytesLeft, + &dstPos, &outBytesLeft); + +if (res == size_t(-1)) + { + printfd(__FILE__, "IconvString(): '%s'\n", strerror(errno)); + + iconv_close(handle); + delete[] outBuf; + delete[] inBuf; + return source; + } + +dstPos = 0; + +std::string dst(outBuf); + +iconv_close(handle); + +delete[] outBuf; +delete[] inBuf; + +return dst; +} +//--------------------------------------------------------------------------- diff --git a/stglibs/common.lib/common.h b/stglibs/common.lib/common.h new file mode 100644 index 00000000..971a2e72 --- /dev/null +++ b/stglibs/common.lib/common.h @@ -0,0 +1,227 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.31 $ + $Date: 2010/11/03 10:26:30 $ + $Author: faust $ + */ + +#ifndef common_h +#define common_h + +#include +#include + +#include "os_int.h" +#include "stg_const.h" + +#define STAT_TIME_3 (1) +#define STAT_TIME_2 (2) +#define STAT_TIME_1 (3) +#define STAT_TIME_1_2 (4) +#define STAT_TIME_1_4 (5) +#define STAT_TIME_1_6 (6) + +#define FN_STR_LEN (NAME_MAX) + +#define ST_F 0 +#define ST_B 1 +#define ST_KB 2 +#define ST_MB 3 + +//----------------------------------------------------------------------------- +const char * IntToKMG(long long a, int statType = ST_F); +const char * LogDate(time_t t); +int ParesTimeStat(const char * str); +int IsTimeStat(struct tm * t, int statTime); +/*bool IsDigit(char c); +bool IsAlpha(char c);*/ +int strtodouble2(const char * s, double &a); +int printfd(const char * __file__, const char * fmt, ...); +void Encode12(char * dst, const char * src, size_t srcLen); +void Decode21(char * dst, const char * src); + +void Encode12str(std::string & dst, const std::string & src); +void Decode21str(std::string & dst, const std::string & src); + +int ParseIPString(const char * str, uint32_t * ips, int maxIP); +void KOIToWin(const char * s1, char * s2, int l); +void WinToKOI(const char * s1, char * s2, int l); +void KOIToWin(const std::string & s1, std::string * s2); +void WinToKOI(const std::string & s1, std::string * s2); +int DaysInMonth(unsigned year, unsigned mon); +int DaysInCurrentMonth(); +int Min8(int a); +//char * inet_ntostr(unsigned long); +std::string inet_ntostring(uint32_t); +uint32_t inet_strington(const std::string & value); +int strprintf(std::string * str, const char * fmt, ...); +int ParseTariffTimeStr(const char * str, int &h1, int &m1, int &h2, int &m2); +uint32_t CalcMask(uint32_t msk); +void TouchFile(const std::string & fileName); +#ifdef WIN32 +void EncodeStr(char * str, unsigned long serial, int useHDD); +void DecodeStr(char * str, unsigned long serial, int useHDD); +#endif //WIN32 +void SwapBytes(uint16_t & value); +void SwapBytes(uint32_t & value); +void SwapBytes(uint64_t & value); +void SwapBytes(int16_t & value); +void SwapBytes(int32_t & value); +void SwapBytes(int64_t & value); + +std::string & TrimL(std::string & val); +std::string & TrimR(std::string & val); +std::string & Trim(std::string & val); + +std::string IconvString(const std::string & source, const std::string & from, const std::string & to); + +//----------------------------------------------------------------------------- +template +int str2x(const std::string & str, varT & x) +{ + int pos = 0; + int minus = 1; + + if (str.empty()) + return -1; + + if (str[0] == '+') + pos++; + + if (str[0] == '-') + { + pos++; + minus = -1; + } + + if ((str[pos] < '0' || str[pos] > '9')) + return -1; + + x = str[pos++] - '0'; + + for (unsigned i = pos; i < str.size(); i++) + { + if ((str[i] < '0' || str[i] > '9')) + return -1; + + x *= 10; + x += str[i] - '0'; + } + + x*= minus; + + return 0; +} +//----------------------------------------------------------------------------- +template +const std::string & x2str(varT x, std::string & s) +{ + varT xx = x; + int pos = 1; + + x /= 10; + while (x != 0) + { + x /= 10; + pos++; + } + + if (xx < 0) + { + pos++; + s.resize(pos, 0); + s[0] = '-'; + } + else if (xx > 0) + { + s.resize(pos, 0); + } + else + { + s.resize(1, 0); + s[0] = '0'; + return s; + } + + x = xx; + + while (x != 0) + { + if (x < 0) + s[--pos] = -(x % 10) + '0'; + else + s[--pos] = x % 10 + '0'; + + x /= 10; + } + + return s; +} +//----------------------------------------------------------------------------- +template +const std::string & unsigned2str(varT x, std::string & s) +{ + varT xx = x; + int pos = 1; + + x /= 10; + while (x != 0) + { + x /= 10; + pos++; + } + + if (xx > 0) + { + s.resize(pos, 0); + } + else + { + s.resize(1, 0); + s[0] = '0'; + return s; + } + + x = xx; + + while (x != 0) + { + s[--pos] = x % 10 + '0'; + + x /= 10; + } + + return s; +} +//----------------------------------------------------------------------------- +int str2x(const std::string & str, int & x); +int str2x(const std::string & str, unsigned & x); +int str2x(const std::string & str, long long & x); +int str2x(const std::string & str, unsigned long long & x); +//----------------------------------------------------------------------------- +const std::string & x2str(unsigned x, std::string & s); +const std::string & x2str(unsigned long long x, std::string & s); +//----------------------------------------------------------------------------- +char * stg_strptime(const char *, const char *, struct tm *); +time_t stg_timegm(struct tm *); + +#endif diff --git a/stglibs/common.lib/debug.c b/stglibs/common.lib/debug.c new file mode 100644 index 00000000..8a2d15c6 --- /dev/null +++ b/stglibs/common.lib/debug.c @@ -0,0 +1,49 @@ +/* + ***************************************************************************** + * + * File: debug.c + * + * Description: ÷Ù×ÏÄ ÏÔÌÁÄÏÞÎÏÊ ÉÎÆÏÒÍÁÃÉÉ × log ÆÁÊÌ + * + * $Id: debug.c,v 1.2 2005/11/16 16:19:40 nobunaga Exp $ + * + ***************************************************************************** + */ + +#include +#include +#include + +#include "debug.h" + + +/* + ***************************************************************************** + * -= óÏÚÄÁÎÉÅ ÚÁÐÉÓÉ × log-ÆÁÊÌÅ =- + ***************************************************************************** + */ +void PrintfLog(FILE * logFile, char * scriptName, char * fmt, ...) +{ + #ifndef DEMO + va_list vaList; + char buff[MAX_LOG_BUFF_LEN]; + time_t curTime; + char curTimeCh[26]; + + if (logFile) + { + va_start(vaList, fmt); + vsprintf(buff, fmt, vaList); + va_end(vaList); + + curTime = time(NULL); + ctime_r(&curTime, curTimeCh); + curTimeCh[strlen(curTimeCh)-1] = 0; + fprintf(logFile, "%s [%s]: %s\n", scriptName, curTimeCh, buff); + } + #endif + return; +} /* PrintfLog() */ + +/* EOF */ + diff --git a/stglibs/common.lib/debug.h b/stglibs/common.lib/debug.h new file mode 100644 index 00000000..d80de9f7 --- /dev/null +++ b/stglibs/common.lib/debug.h @@ -0,0 +1,28 @@ +/* + ***************************************************************************** + * + * File: debug.h + * + * Description: ÷Ù×ÏÄ ÏÔÌÁÄÏÞÎÏÊ ÉÎÆÏÒÍÁÃÉÉ × log ÆÁÊÌ + * + * $Id: debug.h,v 1.2 2006/03/07 18:33:56 nobunaga Exp $ + * + ***************************************************************************** + */ + +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + + +#include + + +#define MAX_LOG_BUFF_LEN (2048) + + +void PrintfLog(FILE * logFile, char * scriptName, char * fmt, ...); + +#endif /* _DEBUG_H_ */ + +/* EOF */ + diff --git a/stglibs/common.lib/stg_common.h b/stglibs/common.lib/stg_common.h new file mode 100644 index 00000000..43a43d3c --- /dev/null +++ b/stglibs/common.lib/stg_common.h @@ -0,0 +1,23 @@ +/* + ***************************************************************************** + * + * File: stg_common.h + * + * Description: çÌÏÂÁÌØÎÏÅ ÄÌÑ ×ÓÅÇÏ ÐÒÏÅËÔÁ STG + * + * $Id: stg_common.h,v 1.1.1.1 2005/09/29 11:33:18 boris Exp $ + * + ***************************************************************************** + */ + +#ifndef _STG_COMMON_H_ +#define _STG_COMMON_H_ + + +#define LOGIN_LEN (32) +#define PASSWD_LEN (32) + +#endif /* _STG_COMMON_H_ */ + +/* EOF */ + diff --git a/stglibs/common.lib/stg_error.c b/stglibs/common.lib/stg_error.c new file mode 100644 index 00000000..17aa912d --- /dev/null +++ b/stglibs/common.lib/stg_error.c @@ -0,0 +1,166 @@ +/* + ***************************************************************************** + * + * File: stg_error.c + * + * Description: ëÏÄÙ ïÛÉÂÏË ÐÒÏÅËÔÁ StarGazer + * + * $Id: stg_error.c,v 1.1.1.1 2005/09/29 11:33:18 boris Exp $ + * + ***************************************************************************** + */ + +#include "stg_error.h" +//#include "debug.h" + + +/* + ***************************************************************************** + * -= ðÏÉÓË ÓÏÏÂÝÅÎÉÑ Ï ÏÛÉÂËÅ ÐÏ ËÏÄÕ ÏÛÉÂËÉ =- + ***************************************************************************** + */ +char * GetErrorString(RESULT_DATA res) +{ + char * errorString; + + switch (res) + { + case SUCCESS: + { + errorString = "OK: Work finished successfully"; + break; + } + /* astat.cgi */ + case ERROR_CONFIG_READ: + { + errorString = "FAIL: Read config file"; + break; + } + case ERROR_PORT_NUM: + { + errorString = "FAIL: Port value incorrect"; + break; + } + case ERROR_CLEAR_SID_DIR: + { + errorString = "FAIL: ClearSidDir() return fail"; + break; + } + case ERROR_UNKNOWN_HTTP_METHOD: + { + errorString = "FAIL: Umknown HTTP method"; + break; + } + case ERROR_NULL_HTTP_METHOD: + { + errorString = "FAIL: NULL HTTP method"; + break; + } + case ERROR_UNKNOWN_QUERY: + { + errorString = "FAIL: Unknown query"; + break; + } + case ERROR_LOGIN: + { + errorString = "FAIL: Login Error"; + break; + } + case ERROR_PREPARE_USER_SELECTION_PAGE_0: + { + errorString = "FAIL: Prepare user selection page [0]"; + break; + } + case ERROR_ADD_IFACE: + { + errorString = "FAIL: Add iface"; + break; + } + case ERROR_ADD_TARIFF: + { + errorString = "FAIL: Add tariff"; + break; + } + case ERROR_ADD_GROUP: + { + errorString = "FAIL: Add group"; + break; + } + case ERROR_ADD_USER: + { + errorString = "FAIL: Add user"; + break; + } + case ERROR_CREATE_SID: + { + errorString = "FAIL: Create sid"; + break; + } + case ERROR_SET_SID: + { + errorString = "FAIL: Set sid"; + break; + } + case ERROR_UPDATE_SID: + { + errorString = "FAIL: Update sid"; + break; + } + case ERROR_READ_SID_DATA: + { + errorString = "FAIL: Read sid data"; + break; + } + case ERROR_WRITE_SID_DATA: + { + errorString = "FAIL: Write sid data"; + break; + } + case ERROR_REMOVE_EXPIRED_SID: + { + errorString = "FAIL: Remove expired sids"; + break; + } + /* qParam.lib */ + case ERROR_MEMORY_ALLOCATE: + { + errorString = "FAIL: Error memory allocation"; + break; + } + case ERROR_MEMORY_DESPOSE: + { + errorString = "FAIL: Error memory depose"; + break; + } + case ERROR_NULL_QUERY: + { + errorString = "FAIL: Query is NULL"; + break; + } + case ERROR_QUERY: + { + errorString = "FAIL: Error query"; + break; + } + /* diagram.lib */ + case ERROR_ARC_DATA_FULL: + { + errorString = "FAIL: Arc data is full"; + break; + } + case ERROR_ARC_PERCENT: + { + errorString = "FAIL: Arc percent != 100%"; + break; + } + default: + { + errorString = "FAIL: Unknown error"; + } + } /* switch (res) */ + + return (errorString); +}/* GetErrorString() */ + +/* EOF */ + diff --git a/stglibs/common.lib/stg_error.h b/stglibs/common.lib/stg_error.h new file mode 100644 index 00000000..39ae1457 --- /dev/null +++ b/stglibs/common.lib/stg_error.h @@ -0,0 +1,70 @@ +/* + ***************************************************************************** + * + * File: stg_error.h + * + * Description: ëÏÄÙ ÏÛÉÂÏË + * + * $Id: stg_error.h,v 1.1.1.1 2005/09/29 11:33:18 boris Exp $ + * + ***************************************************************************** + */ + +#ifndef _STG_ERROR_H_ +#define _STG_ERROR_H_ + + +/* îÁÞÁÌÏ ÏÂÌÁÓÔÉ ÏÛÉÂÏË ÍÏÄÕÌÑ astat.cgi */ +#define ERROR_ASTAT_START (100000) +/* îÁÞÁÌÏ ÏÂÌÁÓÔÉ ÏÛÉÂÏË ÂÉÂÌÉÔÅËÉ qparam.lib */ +#define ERROR_QPARAM_START (102000) +/* îÁÞÁÌÏ ÏÂÌÁÓÔÉ ÏÛÉÂÏË ÂÉÂÌÉÔÅËÉ diagram.lib */ +#define ERROR_DIAGRAM_START (103000) + + +typedef enum +{ + SUCCESS = 0, + + ERROR_CONFIG_READ = ERROR_ASTAT_START, + ERROR_PORT_NUM, + ERROR_CLEAR_SID_DIR, + ERROR_UNKNOWN_HTTP_METHOD, + ERROR_NULL_HTTP_METHOD, + ERROR_UNKNOWN_QUERY, + ERROR_LOGIN, // ÐÏËÁ ÞÔÏ ÏÄÎÁ ÏÛÉÂËÁ ÎÁ ÍÎÏÇÏ ÓÉÔÕÁÃÉÊ: + // * ÎÅ ×ÅÒÎÏÅ ÉÍÑ É ÐÁÒÏÌØ + // * ÎÅÔ Ó×ÑÚÉ Ó ÓÅÒ×ÅÒÏÍ + // * .... + // ÜÔÏ Ó×ÑÚÁÎÏ Ó ËÏÄÁÍÉ ÏÛÉÂÏË ÍÏÄÕÌÑ srvconf.lib + // × ÄÁÌØÎÅÊÛÅÍ ÎÁÄÏ ÐÅÒÅÄÁÌÁÔØ ×ÓÅ ÎÁ ÏÄÉÎ + // enum ÏÛÉÂÏË + ERROR_PREPARE_USER_SELECTION_PAGE_0, + ERROR_ADD_IFACE, + ERROR_ADD_TARIFF, + ERROR_ADD_GROUP, + ERROR_ADD_USER, + ERROR_CREATE_SID, + ERROR_SET_SID, + ERROR_UPDATE_SID, + ERROR_READ_SID_DATA, + ERROR_WRITE_SID_DATA, + ERROR_REMOVE_EXPIRED_SID, + + ERROR_MEMORY_ALLOCATE = ERROR_QPARAM_START, + ERROR_MEMORY_DESPOSE, + ERROR_NULL_QUERY, // ÚÁÐÒÏÓ ÎÅ ÐÏÌÕÞÅÎ + ERROR_QUERY, // ÏÛÉÂËÁ × ÚÁÐÒÏÓÅ - ÎÅÓÏÏÔ×ÅÔÓÔ×ÉÅ ÓÔÁÎÄÁÒÔÕ + + ERROR_ARC_DATA_FULL = ERROR_DIAGRAM_START, + ERROR_ARC_PERCENT, + TODO +} RESULT_DATA; + + +char * GetErrorString(RESULT_DATA res); + +#endif /* _STG_ERROR_H_ */ + +/* EOF */ + diff --git a/stglibs/common.lib/stg_strptime.cpp b/stglibs/common.lib/stg_strptime.cpp new file mode 100644 index 00000000..0e1fde27 --- /dev/null +++ b/stglibs/common.lib/stg_strptime.cpp @@ -0,0 +1,17 @@ +/* + * $Revision: 1.1 $ + * $Date: 2007/05/17 08:25:58 $ + * + * This file contain a replacement of commonly used function strptime + * Under some OS's it appears only with _XOPEN_SOURCE definition + * + */ + +#define _XOPEN_SOURCE +#include + +char * stg_strptime(const char * a, const char * b, struct tm * tm) +{ +return strptime(a, b, tm); +} + diff --git a/stglibs/common.lib/test.cpp b/stglibs/common.lib/test.cpp new file mode 100644 index 00000000..2d0cff74 --- /dev/null +++ b/stglibs/common.lib/test.cpp @@ -0,0 +1,383 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.6 $ + $Date: 2009/06/10 10:31:15 $ + $Author: faust $ + */ + +#include +#include +#include + +using namespace std; + +#include "common.h" +#include "test.h" + +time_t stgTime; + +int main(void) +{ +char buf1[256]; +BLOWFISH_CTX ctx; +int functions = 0, ok = 0; + +cout << "Testing common.lib" << endl << "---------------\ +--------------------" << endl; + +if (!TestIntToKMG()) + ok++; +functions++; +if (!Teststrtodouble2()) + ok++; +functions++; +if (!TestIsDigit()) + ok++; +functions++; +if (!TestIsAlpha()) + ok++; +functions++; +if (!TestEncodeDecode()) + ok++; +functions++; +if (!TestParseIPString()) + ok++; +functions++; +if (!TestKOIToWIN()) + ok++; +functions++; +if (!TestDaysInMonth()) + ok++; +functions++; +if (!TestBlowfish()) + ok++; +functions++; +if (!TestMin8()) + ok++; +functions++; +if (!Testinet_ntostr()) + ok++; +functions++; +if (!TestParseTariffTimeStr()) + ok++; +functions++; +if (!TestStr2XX2Str()) + ok++; +functions++; + +cout << "------------------------------------" << endl; +cout << "Functions: \t\t\t" << functions << endl; +cout << "OK's: \t\t\t\t" << ok << endl; +cout << "Fails: \t\t\t\t" << functions - ok << endl; + +return (functions != ok); +} + +int TestIntToKMG() +{ +int res = 1; +cout << "Testing IntToKMG: \t\t"; +res = res && (strcmp(IntToKMG(LONG_LONG_MAX), TEST1_LLMAX) == 0); +//cout << IntToKMG(LONG_LONG_MAX) << " " << TEST1_LLMAX << endl; + +res = res && (strcmp(IntToKMG(1024 * 1024 + 1), TEST1_1) == 0); +//cout << IntToKMG(1024 * 1024 + 1) << " " << TEST1_1 << endl; + +res = res && (strcmp(IntToKMG(0), TEST1_0) == 0); +//cout << IntToKMG(0) << " " << TEST1_0 << endl; + +res = res && (strcmp(IntToKMG(LONG_LONG_MIN), TEST1_LLMIN) == 0); +//cout << IntToKMG(LONG_LONG_MIN) << " " << TEST1_LLMIN << endl; + +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int Teststrtodouble2() +{ +double a; +int res = 1; +cout << "Testing strtodouble2: \t\t"; +res = res && !strtodouble2("0.0", a); +res = res && (a == 0.0); +res = res && !strtodouble2("0.123456", a); +res = res && (a == 0.123456); +res = res && !strtodouble2("123456.0", a); +res = res && (a == 123456.0); +res = res && !strtodouble2("123456.123456", a); +res = res && (a == 123456.123456); +res = res && !strtodouble2("-0.123456", a); +res = res && (a == -0.123456); +res = res && !strtodouble2("-123456.0", a); +res = res && (a == -123456.0); +res = res && !strtodouble2("-123456.123456", a); +res = res && (a == -123456.123456); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestIsDigit() +{ +char a; +int res = 1; +cout << "Testing IsDigit: \t\t"; +for(a = '0'; a < '9'; a++) + res = res && IsDigit(a); +for(a = 'a'; a < 'z'; a++) + res = res && !IsDigit(a); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestIsAlpha() +{ +char a; +int res = 1; +cout << "Testing IsAlpha: \t\t"; +for(a = '0'; a < '9'; a++) + res = res && !IsAlpha(a); +for(a = 'a'; a < 'z'; a++) + res = res && IsAlpha(a); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestEncodeDecode() +{ +char enc[256], dec[512]; +int res = 1; +cout << "Testing EncodeDecode: \t\t"; +Encode12(enc, TEST2_STRING, strlen(TEST2_STRING)); +Decode21(dec, enc); +res = res && !strcmp(dec, TEST2_STRING); +Encode12(enc, TEST2_STRING, 256); // Overflow +Decode21(dec, enc); +res = res && !strcmp(dec, TEST2_STRING); +Encode12(enc, TEST2_STRING, 5); // Underflow +Decode21(dec, enc); +res = res && !strcmp(dec, TEST2_PART); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestParseIPString() +{ +unsigned int ips[4]; +int res = 1; +cout << "Testing ParseIPString: \t\t"; +res = res && (ParseIPString("127.0.0.1, 192.168.58.1, 10.0.0.1", ips, 4) == 0); +res = res && ips[0] == 0x0100007F; +res = res && ips[1] == 0x013AA8C0; +res = res && ips[2] == 0x0100000A; +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestKOIToWIN() +{ +char enc[256], dec[256]; +int res = 1; +cout << "Testing KOIToWin: \t\t"; +KOIToWin(TEST3_STRING, enc, 256); +WinToKOI(enc, dec, 256); +res = res && !strcmp(dec, TEST3_STRING); +KOIToWin(TEST3_STRING, enc, strlen(TEST3_STRING) - 5); +WinToKOI(enc, dec, strlen(TEST3_STRING) - 5); +res = res && !strcmp(dec, TEST3_STRING); +KOIToWin(TEST3_STRING, enc, strlen(TEST3_STRING) + 5); +WinToKOI(enc, dec, strlen(TEST3_STRING) + 5); +res = res && !strcmp(dec, TEST3_STRING); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestDaysInMonth() +{ +int res = 1; +cout << "Testing DaysInMonth: \t\t"; +res = res && (DaysInMonth(2000, 0) == 31); +res = res && (DaysInMonth(2000, 1) == 29); +res = res && (DaysInMonth(2001, 1) == 28); +res = res && (DaysInMonth(2100, 1) == 28); +res = res && (DaysInMonth(2400, 1) == 29); +res = res && (DaysInMonth(2000, 2) == 31); +res = res && (DaysInMonth(2000, 3) == 30); +res = res && (DaysInMonth(2000, 4) == 31); +res = res && (DaysInMonth(2000, 5) == 30); +res = res && (DaysInMonth(2000, 6) == 31); +res = res && (DaysInMonth(2000, 7) == 31); +res = res && (DaysInMonth(2000, 8) == 30); +res = res && (DaysInMonth(2000, 9) == 31); +res = res && (DaysInMonth(2000, 10) == 30); +res = res && (DaysInMonth(2000, 11) == 31); +res = res && (DaysInMonth(2000, 20) == 33); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestBlowfish() +{ +BLOWFISH_CTX ctx; +char enc[256], dec[256]; +int res = 1, i, len = strlen(TEST4_STRING); +cout << "Testing Blowfish: \t\t"; +EnDecodeInit(TEST4_PASSWORD, strlen(TEST4_PASSWORD), &ctx); +strcpy(dec, TEST4_STRING); +for(i = 0; i < len; i += 8) + EncodeString(&enc[i], &dec[i], &ctx); +for(i = 0; i < len; i += 8) + DecodeString(&dec[i], &enc[i], &ctx); +res = res && !strcmp(dec, TEST4_STRING); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestMin8() +{ +int res = 1; +cout << "Testing Min8: \t\t\t"; +res = res && (Min8(INT_MAX) == INT_MAX + 1); +res = res && (Min8(INT_MIN) == INT_MIN); +res = res && (Min8(0) == 0); +res = res && (Min8(7) == 8); +res = res && (Min8(8) == 8); +res = res && (Min8(9) == 16); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res;; +} + +int Testinet_ntostr() +{ +unsigned long ip; +char buf[32]; +int res = 1; +cout << "Testing inet_ntostr: \t\t"; +res = res && (strcmp(inet_ntostr(inet_addr("127.0.0.1")), "127.0.0.1") == 0); +res = res && (strcmp(inet_ntostr(inet_addr("255.255.255.255")), "255.255.255.255") == 0); +res = res && (strcmp(inet_ntostr(inet_addr("0.0.0.0")), "0.0.0.0") == 0); +res = res && (strcmp(inet_ntostr(inet_addr("10.0.0.1")), "10.0.0.1") == 0); +res = res && (strcmp(inet_ntostr(inet_addr("192.168.58.240")), "192.168.58.240") == 0); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} + +int TestParseTariffTimeStr() +{ +int h1, m1, h2, m2; +int res = 1; +cout << "Testing ParseTariffTimeStr: \t"; +res = res && !ParseTariffTimeStr("00:00-00:00", h1, m1, h2, m2); +res = res && (h1 == 0 && m1 == 0 && h2 == 0 && m2 == 0); +res = res && !ParseTariffTimeStr("0:0-0:0", h1, m1, h2, m2); +res = res && (h1 == 0 && m1 == 0 && h2 == 0 && m2 == 0); +res = res && !ParseTariffTimeStr("99:99-99:99", h1, m1, h2, m2); +res = res && (h1 == 99 && m1 == 99 && h2 == 99 && m2 == 99); +res = res && !ParseTariffTimeStr("12:34-56:78", h1, m1, h2, m2); +res = res && (h1 == 12 && m1 == 34 && h2 == 56 && m2 == 78); +if (res) + cout << "OK" << endl; +else + cout << "Fail" << endl; +return !res; +} +//----------------------------------------------------------------------------- +int TestStr2XX2Str() +{ +cout << "Testing Str2XX2Str: \t\t"; + +# define INT8_MIN (-128) +# define INT16_MIN (-32767-1) +# define INT32_MIN (-2147483647-1) +# define INT64_MIN (-__INT64_C(9223372036854775807)-1) +# define INT8_MAX (127) +# define INT16_MAX (32767) +# define INT32_MAX (2147483647) +# define INT64_MAX (__INT64_C(9223372036854775807)) + +int xx; +string s; +for (int i = -5000000; i < 5000000; i+=10) +{ + x2str(i, s); + str2x(s, xx); + if (i != xx) + { + cout << "Fail" << endl; + return 1; + } +} + +x2str(INT32_MIN, s); +str2x(s, xx); +if (xx != INT32_MIN) +{ + cout << INT32_MIN << " " << s << endl; + cout << INT32_MIN << " " << xx << endl; +cout << "Fail" << endl; + return 1; +} + +x2str(INT32_MAX, s); +str2x(s, xx); +if (xx != INT32_MAX) +{ +cout << "Fail" << endl; + return 1; +} +cout << "OK" << endl; +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/stglibs/common.lib/test.h b/stglibs/common.lib/test.h new file mode 100644 index 00000000..31f0ba08 --- /dev/null +++ b/stglibs/common.lib/test.h @@ -0,0 +1,53 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Maxim Mamontov + */ + + /* + $Revision: 1.4 $ + $Date: 2007/12/02 18:52:05 $ + $Author: nobunaga $ + */ + +#undef STG_TIME + +#define TEST1_LLMAX "8589934592.00 Gb" +#define TEST1_LLMIN "-8589934592.00 Gb" +#define TEST1_0 "0.00 kb" +#define TEST1_1 "1.00 Mb" +#define TEST2_STRING "This is a test string! 0123456789+-*/" +#define TEST2_PART "This i" +#define TEST3_STRING "üÔÏ ÔÅÓÔÏ×ÁÑ ÓÔÒÏËÁ! 0123456789+-*/" +#define TEST3_PART "üÔÏ ÔÅÓÔÏ×ÁÑ ÓÔÒÏËÁ! 012345678" +#define TEST4_STRING "Try to encode this using blowfish" +#define TEST4_PASSWORD "Ha*yN).3zqL!" + +int TestIntToKMG(); +int Teststrtodouble2(); +int TestIsDigit(); +int TestIsAlpha(); +int TestEncodeDecode(); +int TestParseIPString(); +int TestKOIToWIN(); +int TestDaysInMonth(); +int TestBlowfish(); +int TestMin8(); +int Testinet_ntostr(); +int TestParseTariffTimeStr(); +int TestStr2XX2Str(); + diff --git a/stglibs/common_settings.lib/Makefile b/stglibs/common_settings.lib/Makefile new file mode 100644 index 00000000..7829e445 --- /dev/null +++ b/stglibs/common_settings.lib/Makefile @@ -0,0 +1,12 @@ +############################################################################### +# $Id: Makefile,v 1.3 2007/05/08 14:40:09 nobunaga Exp $ +############################################################################### + +LIB_NAME = common_settings +PROG = lib$(LIB_NAME) + +SRCS = common_settings.cpp + +INCS = common_settings.h + +include ../Makefile.in diff --git a/stglibs/common_settings.lib/common_settings.cpp b/stglibs/common_settings.lib/common_settings.cpp new file mode 100644 index 00000000..53a61c16 --- /dev/null +++ b/stglibs/common_settings.lib/common_settings.cpp @@ -0,0 +1,128 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 29.03.2007 + * Author : Boris Mikhailenko + */ + +/* +$Revision: 1.2 $ +$Date: 2007/04/07 13:29:07 $ +*/ + +#include +#include +#include +#include +#include + +using namespace std; + +#include "common_settings.h" +#include "common.h" + +//----------------------------------------------------------------------------- +COMMON_SETTINGS::COMMON_SETTINGS() +{ + +} +//----------------------------------------------------------------------------- +COMMON_SETTINGS::~COMMON_SETTINGS() +{ + +} +//----------------------------------------------------------------------------- +int COMMON_SETTINGS::ParseYesNo(const string & value, bool * val) +{ +if (0 == strcasecmp(value.c_str(), "yes")) + { + *val = true; + return 0; + } +if (0 == strcasecmp(value.c_str(), "no")) + { + *val = false; + return 0; + } + +strError = "Incorrect value \'" + value + "\'."; +return -1; +} +//----------------------------------------------------------------------------- +int COMMON_SETTINGS::ParseInt(const string & value, int * val) +{ +char *res; +*val = strtol(value.c_str(), &res, 10); +if (*res != 0) + { + strError = "Cannot convert \'" + value + "\' to integer."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int COMMON_SETTINGS::ParseIntInRange(const string & value, int min, int max, int * val) +{ +if (ParseInt(value, val) != 0) + return -1; + +if (*val < min || *val > max) + { + strError = "Value \'" + value + "\' out of range."; + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int COMMON_SETTINGS::ParseDouble(const std::string & value, double * val) +{ +char *res; +*val = strtod(value.c_str(), &res); +if (*res != 0) + { + strError = "Cannot convert \'" + value + "\' to double."; + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +int COMMON_SETTINGS::ParseDoubleInRange(const std::string & value, double min, double max, double * val) +{ +if (ParseDouble(value, val) != 0) + return -1; + +if (*val < min || *val > max) + { + strError = "Value \'" + value + "\' out of range."; + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +string COMMON_SETTINGS::GetStrError() const +{ +return strError; +} +//----------------------------------------------------------------------------- +int COMMON_SETTINGS::Reload () +{ +return ReadSettings(); +} +//----------------------------------------------------------------------------- + diff --git a/stglibs/common_settings.lib/common_settings.h b/stglibs/common_settings.lib/common_settings.h new file mode 100644 index 00000000..4a15910d --- /dev/null +++ b/stglibs/common_settings.lib/common_settings.h @@ -0,0 +1,68 @@ + /* + $Revision: 1.3 $ + $Date: 2007/10/24 08:04:07 $ + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 29.03.2007 + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.3 $ + $Date: 2007/10/24 08:04:07 $ + */ + + +#ifndef common_settingsh_h +#define common_settingsh_h 1 + +#include +#include +//#include + +#include "common.h" +#include "base_settings.h" + +//----------------------------------------------------------------------------- +class COMMON_SETTINGS +{ +public: + COMMON_SETTINGS(); + virtual ~COMMON_SETTINGS(); + virtual int Reload(); + virtual int ReadSettings() = 0; + + virtual std::string GetStrError() const; + +protected: + + virtual int ParseInt(const std::string & value, int * val); + virtual int ParseIntInRange(const std::string & value, int min, int max, int * val); + + virtual int ParseDouble(const std::string & value, double * val); + virtual int ParseDoubleInRange(const std::string & value, double min, double max, double * val); + + virtual int ParseYesNo(const std::string & value, bool * val); + + mutable std::string strError; +}; +//----------------------------------------------------------------------------- +#endif + diff --git a/stglibs/conffiles.lib/Makefile b/stglibs/conffiles.lib/Makefile new file mode 100644 index 00000000..2cd4fb3d --- /dev/null +++ b/stglibs/conffiles.lib/Makefile @@ -0,0 +1,12 @@ +############################################################################### +# $Id: Makefile,v 1.4 2007/05/08 14:29:19 faust Exp $ +############################################################################### + +LIB_NAME = conffiles +PROG = lib$(LIB_NAME) + +SRCS = conffiles.cpp + +INCS = conffiles.h + +include ../Makefile.in diff --git a/stglibs/conffiles.lib/conffiles.cpp b/stglibs/conffiles.lib/conffiles.cpp new file mode 100644 index 00000000..549cb22d --- /dev/null +++ b/stglibs/conffiles.lib/conffiles.cpp @@ -0,0 +1,455 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.5 $ + $Date: 2009/10/22 11:40:22 $ + */ + +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include "conffiles.h" +#include "common.h" + +using namespace std; + +//--------------------------------------------------------------------------- +bool StringCaseCmp(const string & str1, const string & str2) +{ +return (strcasecmp(str1.c_str(), str2.c_str()) < 0); +} +//--------------------------------------------------------------------------- +CONFIGFILE::CONFIGFILE(const string &fn): +param_val(StringCaseCmp) +{ +fileName = fn; +f = fopen(fn.c_str(), "rt"); + +error = 0; +param_val.clear(); + +if (!f) + { + error = -1; + return; + } + +string line, parameter, value; + +unsigned long pos; +bool emptyLine; +unsigned char c; + +while (!feof(f)) + { + line.erase(line.begin(), line.end()); + + c = fgetc(f); + while (!feof(f)) + { + //printf("%c", c); + if (c == '\n') + break; + line.push_back(c); + c = fgetc(f); + } + + pos = line.find('#'); + if (pos != string::npos) + line.resize(pos); + + emptyLine = true; + for (unsigned int i = 0; i < line.size(); i++) + { + if (line[i] != ' ' && line[i] != '\t' && line[i] != '\n' && line[i] != '\r') + { + emptyLine = false; + break; + } + } + if (emptyLine) + { + continue; + } + + pos = line.find("="); + if (pos == string::npos) + { + fclose(f); + error = -1; + //printf("%s find(=) error\n", __FILE__); + return; + } + parameter = line.substr(0, pos); + //transform(parameter.begin(), parameter.end(), parameter.begin(), tolower); + value = line.substr(pos + 1); + //cout << parameter << "==" << value << endl; + param_val[parameter] = value; + //cout << parameter << "==" << param_val[parameter] << endl; + } + +fclose(f); +} +//--------------------------------------------------------------------------- +CONFIGFILE::~CONFIGFILE() +{ + +} +//--------------------------------------------------------------------------- +const string & CONFIGFILE::GetFileName() const +{ +return fileName; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::Error() +{ +int e = error; +error = 0; +return e; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::FindParameter(const string ¶meter, string * value) const +{ +it = param_val.find(parameter); +if (it == param_val.end()) + return -1; + +*value = param_val[parameter]; +return 0; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::Flush() +{ +fstream f(fileName.c_str(), ios::out); +if (!f.is_open()) + { + error = EIO; + return EIO; + } + +it = param_val.begin(); +while (it != param_val.end()) + { + f << it->first << "=" << it->second << endl; + it++; + } + +f.close(); + +return 0; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadString(const string & param, char * str, int * maxLen, const char * defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + strncpy(str, param_val[param].c_str(), *maxLen); + *maxLen = param_val[param].size(); + return 0; + } + +strncpy(str, defaultVal, *maxLen); +*maxLen = strlen(defaultVal); +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadString(const string & param, string * val, const string & defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + *val = param_val[param]; + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::WriteString(const string & param, const char * val) +{ +WriteString(param, string(val)); +return 0; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::WriteString(const string & param, const string &val) +{ +param_val[param] = val; +Flush(); +return 0; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadTime(const string & param, time_t * val, time_t defaultVal) const +{ +it = param_val.find(param); + +if (it != param_val.end()) + { + char *res; + *val = strtol(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadInt(const string & param, int * val, int defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = strtol(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadUInt(const string & param, unsigned int * val, unsigned int defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = strtoul(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadLongInt(const string & param, long int * val, long int defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = strtol(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadULongInt(const string & param, unsigned long int * val, unsigned long int defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = strtoul(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadLongLongInt(const string & param, int64_t * val, int64_t defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = strtoll(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadULongLongInt(const string & param, uint64_t * val, uint64_t defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = strtoull(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadShortInt(const string & param, short int * val, short int defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = (short)strtol(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadUShortInt(const string & param, unsigned short int * val, unsigned short int defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = (short)strtoul(param_val[param].c_str(), &res, 10); + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::WriteInt(const string & param, int64_t val) +{ +string s; +//sprintf(s, "%lld", val); +x2str(val, s); +param_val[param] = s; +Flush(); +return 0; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::ReadDouble(const string & param, double * val, double defaultVal) const +{ +it = param_val.find(param); +// îÁÛÌÉ ÎÕÖÎÕÀ ÐÅÒÅÍÅÎÎÕÀ + +if (it != param_val.end()) + { + // þÔÏ-ÔÏ ÓÔÏÉÔ + char *res; + *val = strtod(param_val[param].c_str(), &res); + if (*res != 0) + { + //cout << param << "=" << param_val[param] << " Error!!!\n"; + *val = defaultVal; //Error! + return EINVAL; + } + return 0; + } + +//cout << "îÉÞÅÇÏ ÎÅÔ!!!\n"; + +*val = defaultVal; +return -1; +} +//--------------------------------------------------------------------------- +int CONFIGFILE::WriteDouble(const string & param, double val) +{ +char s[30]; +sprintf(s, "%f", val); +param_val[param] = s; +Flush(); +return 0; +} +//--------------------------------------------------------------------------- + + diff --git a/stglibs/conffiles.lib/conffiles.h b/stglibs/conffiles.lib/conffiles.h new file mode 100644 index 00000000..ee575d65 --- /dev/null +++ b/stglibs/conffiles.lib/conffiles.h @@ -0,0 +1,94 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.5 $ + $Date: 2009/06/22 16:00:38 $ + */ + +//--------------------------------------------------------------------------- + +#ifndef ConfFilesH +#define ConfFilesH +#include +#include +#include +#include + +#include "os_int.h" + +using namespace std; +//--------------------------------------------------------------------------- +//#define CONF_STR_LEN 300 +//typedef char STRING[CONF_STR_LEN]; + +typedef bool (*StringCaseCmp_t)(const string & str1, const string & str2); + +class CONFIGFILE +{ +private: + mutable map param_val; + mutable map::iterator it; + + FILE * f; + int Flush(); + //int ReadFile(); + string fileName; + int error; + +public: + CONFIGFILE(const string &fn); + ~CONFIGFILE(); + const string & GetFileName() const; + + // 5 ÆÕÎËÃÉÉ Read* ×ÏÚ×ÒÁÝÁÀÔ 0 ÐÒÉ ÕÓÐÅÛÎÏÍ ÓÞÉÔÙ×ÁÎÉÉ + // É EINVAL ÐÒÉ ÏÔÓÕÔÓ×ÉÉ ÐÁÒÁÍÅÔÒÁ É ×ÙÓÔÁ×ÌÑÀÔ defaulValue + int ReadString(const string & param, char * val, int * maxLen, const char * defaultVal) const; + int ReadString(const string & param, string * val, const string & defaultVal) const; + + int FindParameter(const string ¶meter, string * value) const; + + int ReadTime (const string & param, time_t *, time_t) const; + + int ReadShortInt (const string & param, short int *, short int) const; + int ReadInt (const string & param, int *, int) const; + int ReadLongInt (const string & param, long int *, long int) const; + int ReadLongLongInt(const string & param, int64_t *, int64_t) const; + + int ReadUShortInt (const string & param, unsigned short int *, unsigned short int) const; + int ReadUInt (const string & param, unsigned int *, unsigned int) const; + int ReadULongInt (const string & param, unsigned long int *, unsigned long int) const; + int ReadULongLongInt(const string & param, uint64_t *, uint64_t) const; + + int ReadDouble (const string & param, double * val, double defaultVal) const; + + int WriteString(const string & param, const char * val); + int WriteString(const string & param, const string & val); + int WriteInt (const string & param, int64_t val); + int WriteDouble(const string & param, double val); + + int Error(); +}; +//--------------------------------------------------------------------------- +#endif diff --git a/stglibs/crypto.lib/Makefile b/stglibs/crypto.lib/Makefile new file mode 100644 index 00000000..6fe383ad --- /dev/null +++ b/stglibs/crypto.lib/Makefile @@ -0,0 +1,14 @@ +############################################################################### +# $Id: Makefile,v 1.5 2009/10/09 07:15:48 nobunaga Exp $ +############################################################################### + +LIB_NAME = stg_crypto +PROG = lib$(LIB_NAME) + +SRCS = ag_md5.cpp \ + blowfish.cpp + +INCS = ag_md5.h \ + blowfish.h + +include ../Makefile.in diff --git a/stglibs/crypto.lib/ag_md5.cpp b/stglibs/crypto.lib/ag_md5.cpp new file mode 100644 index 00000000..fef284a5 --- /dev/null +++ b/stglibs/crypto.lib/ag_md5.cpp @@ -0,0 +1,457 @@ + +#ifdef WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include "ag_md5.h" + + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + + +int i64c(int i) +{ + if (i <= 0) + return ('.'); + + if (i == 1) + return ('/'); + + if (i >= 2 && i < 12) + return ('0' - 2 + i); + + if (i >= 12 && i < 38) + return ('A' - 12 + i); + + if (i >= 38 && i < 63) + return ('a' - 38 + i); + + return ('z'); +} + +char * l64a_(long l) +{ + static char buf[8]; + int i = 0; + + if (l < 0L) + return ((char *) 0); + + do { + buf[i++] = i64c ((int) (l % 64)); + buf[i] = '\0'; + } while (l /= 64L, l > 0 && i < 6); + + return (buf); +} + +char * crypt_make_salt(void) +{ + + static char result[40]; + #ifdef WIN32 + unsigned int tsec; + #else + struct timeval tv; + #endif + + result[0] = '\0'; + strcpy(result, "$1$"); /* magic for the new MD5 crypt() */ + + /* + * Generate 8 chars of salt, the old crypt() will use only first 2. + */ + #ifdef WIN32 + strcat(result, l64a_(GetTickCount())); + tsec = time(NULL); + strcat(result, l64a_(tsec + getpid() + clock())); + #else + gettimeofday(&tv, (struct timezone *) 0); + strcat(result, l64a_(tv.tv_usec)); + strcat(result, l64a_(tv.tv_sec + getpid() + clock())); + #endif + + if (strlen(result) > 3 + 8) /* magic+salt */ + result[11] = '\0'; + + return result; +} + +void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, char const *buf, unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset((char *) ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void +to64(char *s, unsigned long v, int n) +{ + while (--n >= 0) { + *s++ = itoa64[v&0x3f]; + v >>= 6; + } +} + +/* + * UNIX password + * + * Use MD5 for what it is best at... + */ + +char * +libshadow_md5_crypt(const char *pw, const char *salt) +{ + static const char *magic = "$1$"; /* + * This string is magic for + * this algorithm. Having + * it this way, we can get + * get better later on + */ + static char passwd[120], *p; + static const char *sp,*ep; + unsigned char final[16]; + int sl,pl,i,j; + MD5_CTX ctx,ctx1; + unsigned long l; + + /* Refine the Salt first */ + sp = salt; + + /* If it starts with the magic string, then skip that */ + if(!strncmp(sp,magic,strlen(magic))) + sp += strlen(magic); + + /* It stops at the first '$', max 8 chars */ + for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++) + continue; + + /* get the length of the true salt */ + sl = ep - sp; + + MD5Init(&ctx); + + /* The password first, since that is what is most unknown */ + MD5Update(&ctx, pw, strlen(pw)); + + /* Then our magic string */ + MD5Update(&ctx, magic, strlen(magic)); + + /* Then the raw salt */ + MD5Update(&ctx, sp, sl); + + /* Then just as many characters of the MD5(pw,salt,pw) */ + MD5Init(&ctx1); + MD5Update(&ctx1,pw,strlen(pw)); + MD5Update(&ctx1,sp,sl); + MD5Update(&ctx1,pw,strlen(pw)); + MD5Final(final,&ctx1); + for(pl = strlen(pw); pl > 0; pl -= 16) + MD5Update(&ctx, (char*)final, pl>16 ? 16 : pl); + + /* Don't leave anything around in vm they could use. */ + memset(final,0,sizeof final); + + /* Then something really weird... */ + for (j=0,i = strlen(pw); i ; i >>= 1) + if(i&1) + MD5Update(&ctx, (char*)final+j, 1); + else + MD5Update(&ctx, pw+j, 1); + + /* Now make the output string */ + strcpy(passwd,magic); + strncat(passwd,sp,sl); + strcat(passwd,"$"); + + MD5Final(final,&ctx); + + /* + * and now, just to make sure things don't run too fast + * On a 60 Mhz Pentium this takes 34 msec, so you would + * need 30 seconds to build a 1000 entry dictionary... + */ + /* + for(i=0;i<1000;i++) { + MD5Init(&ctx1); + if(i & 1) + MD5Update(&ctx1,pw,strlen(pw)); + else + MD5Update(&ctx1,final,16); + + if(i % 3) + MD5Update(&ctx1,sp,sl); + + if(i % 7) + MD5Update(&ctx1,pw,strlen(pw)); + + if(i & 1) + MD5Update(&ctx1,final,16); + else + MD5Update(&ctx1,pw,strlen(pw)); + MD5Final(final,&ctx1); + }*/ + + p = passwd + strlen(passwd); + + l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4; + l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4; + l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4; + l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4; + l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4; + l = final[11] ; to64(p,l,2); p += 2; + *p = '\0'; + + /* Don't leave anything around in vm they could use. */ + memset(final,0,sizeof final); + + return passwd; +} + +char *pw_encrypt(const char *clear, const char *salt) { + + /* + * If the salt string from the password file or from crypt_make_salt() + * begins with the magic string, use the new algorithm. + */ + if (strncmp(salt, "$1$", 3) == 0) + return(libshadow_md5_crypt(clear, salt)); + else return(NULL); + +} +/* AG MD5 functions */ +char *make_ag_hash(time_t salt, const char *clear) { + char salt_str[20]; + char *res=NULL; + char *p; + + unsigned long slt = salt; + sprintf(salt_str, "$1$%08lx", slt); + res=libshadow_md5_crypt(clear, salt_str); + p=strrchr(res, '$'); + return(++p); +} + +int check_ag_hash(time_t salt, const char *clear, const char *hash) { + return(strcmp(hash, make_ag_hash(salt, clear))); +} + diff --git a/stglibs/crypto.lib/ag_md5.h b/stglibs/crypto.lib/ag_md5.h new file mode 100644 index 00000000..3b54b502 --- /dev/null +++ b/stglibs/crypto.lib/ag_md5.h @@ -0,0 +1,30 @@ +#ifndef _MD5_H +#define _MD5_H + +#include + +#include "os_int.h" + +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +}; + +typedef struct MD5Context MD5_CTX; + +char *crypt_make_salt(void); +void byteReverse(unsigned char*, unsigned); +void MD5Init(struct MD5Context *ctx); +void MD5Update(struct MD5Context*, char const*, unsigned); +void MD5Final(unsigned char digest[16], struct MD5Context *ctx); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); +/* static void to64(char*, unsigned long, int); */ +char *libshadow_md5_crypt(const char*, const char*); +char *pw_encrypt(const char*, const char*); + +/* AG functions */ +char *make_ag_hash(time_t salt, const char *clear); +int check_ag_hash(time_t salt, const char *clear, const char *hash); + +#endif /* _MD5_H */ diff --git a/stglibs/crypto.lib/blowfish.cpp b/stglibs/crypto.lib/blowfish.cpp new file mode 100644 index 00000000..ab3122b9 --- /dev/null +++ b/stglibs/crypto.lib/blowfish.cpp @@ -0,0 +1,503 @@ +/* + * Author : Paul Kocher + * E-mail : pck@netcom.com + * Date : 1997 + * Description: C implementation of the Blowfish algorithm. + */ + +#include + +#include "blowfish.h" +#include "stg_const.h" + +/*typedef struct _BCoptions + { + unsigned char remove; + unsigned char standardout; + unsigned char compression; + unsigned char type; + uint32_t origsize; + unsigned char securedelete; + } BCoptions;*/ + +#define ENCRYPT 0 +#define DECRYPT 1 + +#define endianBig ((unsigned char) 0x45) +#define endianLittle ((unsigned char) 0x54) + +#ifdef WIN32 /* Win32 doesn't have random() or lstat */ + #define random() rand() + #define initstate(x,y,z) srand(x) + #define lstat(x,y) stat(x,y) +#endif + +#ifndef S_ISREG + #define S_ISREG(x) ( ((x)&S_IFMT)==S_IFREG ) +#endif + + +#define N 16 + +static uint32_t F(BLOWFISH_CTX *ctx, uint32_t x); +static const uint32_t ORIG_P[16 + 2] = { +0x243F6A88L, 0x85A308D3L, 0x13198A2EL, 0x03707344L, +0xA4093822L, 0x299F31D0L, 0x082EFA98L, 0xEC4E6C89L, +0x452821E6L, 0x38D01377L, 0xBE5466CFL, 0x34E90C6CL, +0xC0AC29B7L, 0xC97C50DDL, 0x3F84D5B5L, 0xB5470917L, +0x9216D5D9L, 0x8979FB1BL +}; + +static const uint32_t ORIG_S[4][256] = { +{ 0xD1310BA6L, 0x98DFB5ACL, 0x2FFD72DBL, 0xD01ADFB7L, + 0xB8E1AFEDL, 0x6A267E96L, 0xBA7C9045L, 0xF12C7F99L, + 0x24A19947L, 0xB3916CF7L, 0x0801F2E2L, 0x858EFC16L, + 0x636920D8L, 0x71574E69L, 0xA458FEA3L, 0xF4933D7EL, + 0x0D95748FL, 0x728EB658L, 0x718BCD58L, 0x82154AEEL, + 0x7B54A41DL, 0xC25A59B5L, 0x9C30D539L, 0x2AF26013L, + 0xC5D1B023L, 0x286085F0L, 0xCA417918L, 0xB8DB38EFL, + 0x8E79DCB0L, 0x603A180EL, 0x6C9E0E8BL, 0xB01E8A3EL, + 0xD71577C1L, 0xBD314B27L, 0x78AF2FDAL, 0x55605C60L, + 0xE65525F3L, 0xAA55AB94L, 0x57489862L, 0x63E81440L, + 0x55CA396AL, 0x2AAB10B6L, 0xB4CC5C34L, 0x1141E8CEL, + 0xA15486AFL, 0x7C72E993L, 0xB3EE1411L, 0x636FBC2AL, + 0x2BA9C55DL, 0x741831F6L, 0xCE5C3E16L, 0x9B87931EL, + 0xAFD6BA33L, 0x6C24CF5CL, 0x7A325381L, 0x28958677L, + 0x3B8F4898L, 0x6B4BB9AFL, 0xC4BFE81BL, 0x66282193L, + 0x61D809CCL, 0xFB21A991L, 0x487CAC60L, 0x5DEC8032L, + 0xEF845D5DL, 0xE98575B1L, 0xDC262302L, 0xEB651B88L, + 0x23893E81L, 0xD396ACC5L, 0x0F6D6FF3L, 0x83F44239L, + 0x2E0B4482L, 0xA4842004L, 0x69C8F04AL, 0x9E1F9B5EL, + 0x21C66842L, 0xF6E96C9AL, 0x670C9C61L, 0xABD388F0L, + 0x6A51A0D2L, 0xD8542F68L, 0x960FA728L, 0xAB5133A3L, + 0x6EEF0B6CL, 0x137A3BE4L, 0xBA3BF050L, 0x7EFB2A98L, + 0xA1F1651DL, 0x39AF0176L, 0x66CA593EL, 0x82430E88L, + 0x8CEE8619L, 0x456F9FB4L, 0x7D84A5C3L, 0x3B8B5EBEL, + 0xE06F75D8L, 0x85C12073L, 0x401A449FL, 0x56C16AA6L, + 0x4ED3AA62L, 0x363F7706L, 0x1BFEDF72L, 0x429B023DL, + 0x37D0D724L, 0xD00A1248L, 0xDB0FEAD3L, 0x49F1C09BL, + 0x075372C9L, 0x80991B7BL, 0x25D479D8L, 0xF6E8DEF7L, + 0xE3FE501AL, 0xB6794C3BL, 0x976CE0BDL, 0x04C006BAL, + 0xC1A94FB6L, 0x409F60C4L, 0x5E5C9EC2L, 0x196A2463L, + 0x68FB6FAFL, 0x3E6C53B5L, 0x1339B2EBL, 0x3B52EC6FL, + 0x6DFC511FL, 0x9B30952CL, 0xCC814544L, 0xAF5EBD09L, + 0xBEE3D004L, 0xDE334AFDL, 0x660F2807L, 0x192E4BB3L, + 0xC0CBA857L, 0x45C8740FL, 0xD20B5F39L, 0xB9D3FBDBL, + 0x5579C0BDL, 0x1A60320AL, 0xD6A100C6L, 0x402C7279L, + 0x679F25FEL, 0xFB1FA3CCL, 0x8EA5E9F8L, 0xDB3222F8L, + 0x3C7516DFL, 0xFD616B15L, 0x2F501EC8L, 0xAD0552ABL, + 0x323DB5FAL, 0xFD238760L, 0x53317B48L, 0x3E00DF82L, + 0x9E5C57BBL, 0xCA6F8CA0L, 0x1A87562EL, 0xDF1769DBL, + 0xD542A8F6L, 0x287EFFC3L, 0xAC6732C6L, 0x8C4F5573L, + 0x695B27B0L, 0xBBCA58C8L, 0xE1FFA35DL, 0xB8F011A0L, + 0x10FA3D98L, 0xFD2183B8L, 0x4AFCB56CL, 0x2DD1D35BL, + 0x9A53E479L, 0xB6F84565L, 0xD28E49BCL, 0x4BFB9790L, + 0xE1DDF2DAL, 0xA4CB7E33L, 0x62FB1341L, 0xCEE4C6E8L, + 0xEF20CADAL, 0x36774C01L, 0xD07E9EFEL, 0x2BF11FB4L, + 0x95DBDA4DL, 0xAE909198L, 0xEAAD8E71L, 0x6B93D5A0L, + 0xD08ED1D0L, 0xAFC725E0L, 0x8E3C5B2FL, 0x8E7594B7L, + 0x8FF6E2FBL, 0xF2122B64L, 0x8888B812L, 0x900DF01CL, + 0x4FAD5EA0L, 0x688FC31CL, 0xD1CFF191L, 0xB3A8C1ADL, + 0x2F2F2218L, 0xBE0E1777L, 0xEA752DFEL, 0x8B021FA1L, + 0xE5A0CC0FL, 0xB56F74E8L, 0x18ACF3D6L, 0xCE89E299L, + 0xB4A84FE0L, 0xFD13E0B7L, 0x7CC43B81L, 0xD2ADA8D9L, + 0x165FA266L, 0x80957705L, 0x93CC7314L, 0x211A1477L, + 0xE6AD2065L, 0x77B5FA86L, 0xC75442F5L, 0xFB9D35CFL, + 0xEBCDAF0CL, 0x7B3E89A0L, 0xD6411BD3L, 0xAE1E7E49L, + 0x00250E2DL, 0x2071B35EL, 0x226800BBL, 0x57B8E0AFL, + 0x2464369BL, 0xF009B91EL, 0x5563911DL, 0x59DFA6AAL, + 0x78C14389L, 0xD95A537FL, 0x207D5BA2L, 0x02E5B9C5L, + 0x83260376L, 0x6295CFA9L, 0x11C81968L, 0x4E734A41L, + 0xB3472DCAL, 0x7B14A94AL, 0x1B510052L, 0x9A532915L, + 0xD60F573FL, 0xBC9BC6E4L, 0x2B60A476L, 0x81E67400L, + 0x08BA6FB5L, 0x571BE91FL, 0xF296EC6BL, 0x2A0DD915L, + 0xB6636521L, 0xE7B9F9B6L, 0xFF34052EL, 0xC5855664L, + 0x53B02D5DL, 0xA99F8FA1L, 0x08BA4799L, 0x6E85076AL}, + +{ 0x4B7A70E9L, 0xB5B32944L, 0xDB75092EL, 0xC4192623L, + 0xAD6EA6B0L, 0x49A7DF7DL, 0x9CEE60B8L, 0x8FEDB266L, + 0xECAA8C71L, 0x699A17FFL, 0x5664526CL, 0xC2B19EE1L, + 0x193602A5L, 0x75094C29L, 0xA0591340L, 0xE4183A3EL, + 0x3F54989AL, 0x5B429D65L, 0x6B8FE4D6L, 0x99F73FD6L, + 0xA1D29C07L, 0xEFE830F5L, 0x4D2D38E6L, 0xF0255DC1L, + 0x4CDD2086L, 0x8470EB26L, 0x6382E9C6L, 0x021ECC5EL, + 0x09686B3FL, 0x3EBAEFC9L, 0x3C971814L, 0x6B6A70A1L, + 0x687F3584L, 0x52A0E286L, 0xB79C5305L, 0xAA500737L, + 0x3E07841CL, 0x7FDEAE5CL, 0x8E7D44ECL, 0x5716F2B8L, + 0xB03ADA37L, 0xF0500C0DL, 0xF01C1F04L, 0x0200B3FFL, + 0xAE0CF51AL, 0x3CB574B2L, 0x25837A58L, 0xDC0921BDL, + 0xD19113F9L, 0x7CA92FF6L, 0x94324773L, 0x22F54701L, + 0x3AE5E581L, 0x37C2DADCL, 0xC8B57634L, 0x9AF3DDA7L, + 0xA9446146L, 0x0FD0030EL, 0xECC8C73EL, 0xA4751E41L, + 0xE238CD99L, 0x3BEA0E2FL, 0x3280BBA1L, 0x183EB331L, + 0x4E548B38L, 0x4F6DB908L, 0x6F420D03L, 0xF60A04BFL, + 0x2CB81290L, 0x24977C79L, 0x5679B072L, 0xBCAF89AFL, + 0xDE9A771FL, 0xD9930810L, 0xB38BAE12L, 0xDCCF3F2EL, + 0x5512721FL, 0x2E6B7124L, 0x501ADDE6L, 0x9F84CD87L, + 0x7A584718L, 0x7408DA17L, 0xBC9F9ABCL, 0xE94B7D8CL, + 0xEC7AEC3AL, 0xDB851DFAL, 0x63094366L, 0xC464C3D2L, + 0xEF1C1847L, 0x3215D908L, 0xDD433B37L, 0x24C2BA16L, + 0x12A14D43L, 0x2A65C451L, 0x50940002L, 0x133AE4DDL, + 0x71DFF89EL, 0x10314E55L, 0x81AC77D6L, 0x5F11199BL, + 0x043556F1L, 0xD7A3C76BL, 0x3C11183BL, 0x5924A509L, + 0xF28FE6EDL, 0x97F1FBFAL, 0x9EBABF2CL, 0x1E153C6EL, + 0x86E34570L, 0xEAE96FB1L, 0x860E5E0AL, 0x5A3E2AB3L, + 0x771FE71CL, 0x4E3D06FAL, 0x2965DCB9L, 0x99E71D0FL, + 0x803E89D6L, 0x5266C825L, 0x2E4CC978L, 0x9C10B36AL, + 0xC6150EBAL, 0x94E2EA78L, 0xA5FC3C53L, 0x1E0A2DF4L, + 0xF2F74EA7L, 0x361D2B3DL, 0x1939260FL, 0x19C27960L, + 0x5223A708L, 0xF71312B6L, 0xEBADFE6EL, 0xEAC31F66L, + 0xE3BC4595L, 0xA67BC883L, 0xB17F37D1L, 0x018CFF28L, + 0xC332DDEFL, 0xBE6C5AA5L, 0x65582185L, 0x68AB9802L, + 0xEECEA50FL, 0xDB2F953BL, 0x2AEF7DADL, 0x5B6E2F84L, + 0x1521B628L, 0x29076170L, 0xECDD4775L, 0x619F1510L, + 0x13CCA830L, 0xEB61BD96L, 0x0334FE1EL, 0xAA0363CFL, + 0xB5735C90L, 0x4C70A239L, 0xD59E9E0BL, 0xCBAADE14L, + 0xEECC86BCL, 0x60622CA7L, 0x9CAB5CABL, 0xB2F3846EL, + 0x648B1EAFL, 0x19BDF0CAL, 0xA02369B9L, 0x655ABB50L, + 0x40685A32L, 0x3C2AB4B3L, 0x319EE9D5L, 0xC021B8F7L, + 0x9B540B19L, 0x875FA099L, 0x95F7997EL, 0x623D7DA8L, + 0xF837889AL, 0x97E32D77L, 0x11ED935FL, 0x16681281L, + 0x0E358829L, 0xC7E61FD6L, 0x96DEDFA1L, 0x7858BA99L, + 0x57F584A5L, 0x1B227263L, 0x9B83C3FFL, 0x1AC24696L, + 0xCDB30AEBL, 0x532E3054L, 0x8FD948E4L, 0x6DBC3128L, + 0x58EBF2EFL, 0x34C6FFEAL, 0xFE28ED61L, 0xEE7C3C73L, + 0x5D4A14D9L, 0xE864B7E3L, 0x42105D14L, 0x203E13E0L, + 0x45EEE2B6L, 0xA3AAABEAL, 0xDB6C4F15L, 0xFACB4FD0L, + 0xC742F442L, 0xEF6ABBB5L, 0x654F3B1DL, 0x41CD2105L, + 0xD81E799EL, 0x86854DC7L, 0xE44B476AL, 0x3D816250L, + 0xCF62A1F2L, 0x5B8D2646L, 0xFC8883A0L, 0xC1C7B6A3L, + 0x7F1524C3L, 0x69CB7492L, 0x47848A0BL, 0x5692B285L, + 0x095BBF00L, 0xAD19489DL, 0x1462B174L, 0x23820E00L, + 0x58428D2AL, 0x0C55F5EAL, 0x1DADF43EL, 0x233F7061L, + 0x3372F092L, 0x8D937E41L, 0xD65FECF1L, 0x6C223BDBL, + 0x7CDE3759L, 0xCBEE7460L, 0x4085F2A7L, 0xCE77326EL, + 0xA6078084L, 0x19F8509EL, 0xE8EFD855L, 0x61D99735L, + 0xA969A7AAL, 0xC50C06C2L, 0x5A04ABFCL, 0x800BCADCL, + 0x9E447A2EL, 0xC3453484L, 0xFDD56705L, 0x0E1E9EC9L, + 0xDB73DBD3L, 0x105588CDL, 0x675FDA79L, 0xE3674340L, + 0xC5C43465L, 0x713E38D8L, 0x3D28F89EL, 0xF16DFF20L, + 0x153E21E7L, 0x8FB03D4AL, 0xE6E39F2BL, 0xDB83ADF7L}, + +{ 0xE93D5A68L, 0x948140F7L, 0xF64C261CL, 0x94692934L, + 0x411520F7L, 0x7602D4F7L, 0xBCF46B2EL, 0xD4A20068L, + 0xD4082471L, 0x3320F46AL, 0x43B7D4B7L, 0x500061AFL, + 0x1E39F62EL, 0x97244546L, 0x14214F74L, 0xBF8B8840L, + 0x4D95FC1DL, 0x96B591AFL, 0x70F4DDD3L, 0x66A02F45L, + 0xBFBC09ECL, 0x03BD9785L, 0x7FAC6DD0L, 0x31CB8504L, + 0x96EB27B3L, 0x55FD3941L, 0xDA2547E6L, 0xABCA0A9AL, + 0x28507825L, 0x530429F4L, 0x0A2C86DAL, 0xE9B66DFBL, + 0x68DC1462L, 0xD7486900L, 0x680EC0A4L, 0x27A18DEEL, + 0x4F3FFEA2L, 0xE887AD8CL, 0xB58CE006L, 0x7AF4D6B6L, + 0xAACE1E7CL, 0xD3375FECL, 0xCE78A399L, 0x406B2A42L, + 0x20FE9E35L, 0xD9F385B9L, 0xEE39D7ABL, 0x3B124E8BL, + 0x1DC9FAF7L, 0x4B6D1856L, 0x26A36631L, 0xEAE397B2L, + 0x3A6EFA74L, 0xDD5B4332L, 0x6841E7F7L, 0xCA7820FBL, + 0xFB0AF54EL, 0xD8FEB397L, 0x454056ACL, 0xBA489527L, + 0x55533A3AL, 0x20838D87L, 0xFE6BA9B7L, 0xD096954BL, + 0x55A867BCL, 0xA1159A58L, 0xCCA92963L, 0x99E1DB33L, + 0xA62A4A56L, 0x3F3125F9L, 0x5EF47E1CL, 0x9029317CL, + 0xFDF8E802L, 0x04272F70L, 0x80BB155CL, 0x05282CE3L, + 0x95C11548L, 0xE4C66D22L, 0x48C1133FL, 0xC70F86DCL, + 0x07F9C9EEL, 0x41041F0FL, 0x404779A4L, 0x5D886E17L, + 0x325F51EBL, 0xD59BC0D1L, 0xF2BCC18FL, 0x41113564L, + 0x257B7834L, 0x602A9C60L, 0xDFF8E8A3L, 0x1F636C1BL, + 0x0E12B4C2L, 0x02E1329EL, 0xAF664FD1L, 0xCAD18115L, + 0x6B2395E0L, 0x333E92E1L, 0x3B240B62L, 0xEEBEB922L, + 0x85B2A20EL, 0xE6BA0D99L, 0xDE720C8CL, 0x2DA2F728L, + 0xD0127845L, 0x95B794FDL, 0x647D0862L, 0xE7CCF5F0L, + 0x5449A36FL, 0x877D48FAL, 0xC39DFD27L, 0xF33E8D1EL, + 0x0A476341L, 0x992EFF74L, 0x3A6F6EABL, 0xF4F8FD37L, + 0xA812DC60L, 0xA1EBDDF8L, 0x991BE14CL, 0xDB6E6B0DL, + 0xC67B5510L, 0x6D672C37L, 0x2765D43BL, 0xDCD0E804L, + 0xF1290DC7L, 0xCC00FFA3L, 0xB5390F92L, 0x690FED0BL, + 0x667B9FFBL, 0xCEDB7D9CL, 0xA091CF0BL, 0xD9155EA3L, + 0xBB132F88L, 0x515BAD24L, 0x7B9479BFL, 0x763BD6EBL, + 0x37392EB3L, 0xCC115979L, 0x8026E297L, 0xF42E312DL, + 0x6842ADA7L, 0xC66A2B3BL, 0x12754CCCL, 0x782EF11CL, + 0x6A124237L, 0xB79251E7L, 0x06A1BBE6L, 0x4BFB6350L, + 0x1A6B1018L, 0x11CAEDFAL, 0x3D25BDD8L, 0xE2E1C3C9L, + 0x44421659L, 0x0A121386L, 0xD90CEC6EL, 0xD5ABEA2AL, + 0x64AF674EL, 0xDA86A85FL, 0xBEBFE988L, 0x64E4C3FEL, + 0x9DBC8057L, 0xF0F7C086L, 0x60787BF8L, 0x6003604DL, + 0xD1FD8346L, 0xF6381FB0L, 0x7745AE04L, 0xD736FCCCL, + 0x83426B33L, 0xF01EAB71L, 0xB0804187L, 0x3C005E5FL, + 0x77A057BEL, 0xBDE8AE24L, 0x55464299L, 0xBF582E61L, + 0x4E58F48FL, 0xF2DDFDA2L, 0xF474EF38L, 0x8789BDC2L, + 0x5366F9C3L, 0xC8B38E74L, 0xB475F255L, 0x46FCD9B9L, + 0x7AEB2661L, 0x8B1DDF84L, 0x846A0E79L, 0x915F95E2L, + 0x466E598EL, 0x20B45770L, 0x8CD55591L, 0xC902DE4CL, + 0xB90BACE1L, 0xBB8205D0L, 0x11A86248L, 0x7574A99EL, + 0xB77F19B6L, 0xE0A9DC09L, 0x662D09A1L, 0xC4324633L, + 0xE85A1F02L, 0x09F0BE8CL, 0x4A99A025L, 0x1D6EFE10L, + 0x1AB93D1DL, 0x0BA5A4DFL, 0xA186F20FL, 0x2868F169L, + 0xDCB7DA83L, 0x573906FEL, 0xA1E2CE9BL, 0x4FCD7F52L, + 0x50115E01L, 0xA70683FAL, 0xA002B5C4L, 0x0DE6D027L, + 0x9AF88C27L, 0x773F8641L, 0xC3604C06L, 0x61A806B5L, + 0xF0177A28L, 0xC0F586E0L, 0x006058AAL, 0x30DC7D62L, + 0x11E69ED7L, 0x2338EA63L, 0x53C2DD94L, 0xC2C21634L, + 0xBBCBEE56L, 0x90BCB6DEL, 0xEBFC7DA1L, 0xCE591D76L, + 0x6F05E409L, 0x4B7C0188L, 0x39720A3DL, 0x7C927C24L, + 0x86E3725FL, 0x724D9DB9L, 0x1AC15BB4L, 0xD39EB8FCL, + 0xED545578L, 0x08FCA5B5L, 0xD83D7CD3L, 0x4DAD0FC4L, + 0x1E50EF5EL, 0xB161E6F8L, 0xA28514D9L, 0x6C51133CL, + 0x6FD5C7E7L, 0x56E14EC4L, 0x362ABFCEL, 0xDDC6C837L, + 0xD79A3234L, 0x92638212L, 0x670EFA8EL, 0x406000E0L}, + +{ 0x3A39CE37L, 0xD3FAF5CFL, 0xABC27737L, 0x5AC52D1BL, + 0x5CB0679EL, 0x4FA33742L, 0xD3822740L, 0x99BC9BBEL, + 0xD5118E9DL, 0xBF0F7315L, 0xD62D1C7EL, 0xC700C47BL, + 0xB78C1B6BL, 0x21A19045L, 0xB26EB1BEL, 0x6A366EB4L, + 0x5748AB2FL, 0xBC946E79L, 0xC6A376D2L, 0x6549C2C8L, + 0x530FF8EEL, 0x468DDE7DL, 0xD5730A1DL, 0x4CD04DC6L, + 0x2939BBDBL, 0xA9BA4650L, 0xAC9526E8L, 0xBE5EE304L, + 0xA1FAD5F0L, 0x6A2D519AL, 0x63EF8CE2L, 0x9A86EE22L, + 0xC089C2B8L, 0x43242EF6L, 0xA51E03AAL, 0x9CF2D0A4L, + 0x83C061BAL, 0x9BE96A4DL, 0x8FE51550L, 0xBA645BD6L, + 0x2826A2F9L, 0xA73A3AE1L, 0x4BA99586L, 0xEF5562E9L, + 0xC72FEFD3L, 0xF752F7DAL, 0x3F046F69L, 0x77FA0A59L, + 0x80E4A915L, 0x87B08601L, 0x9B09E6ADL, 0x3B3EE593L, + 0xE990FD5AL, 0x9E34D797L, 0x2CF0B7D9L, 0x022B8B51L, + 0x96D5AC3AL, 0x017DA67DL, 0xD1CF3ED6L, 0x7C7D2D28L, + 0x1F9F25CFL, 0xADF2B89BL, 0x5AD6B472L, 0x5A88F54CL, + 0xE029AC71L, 0xE019A5E6L, 0x47B0ACFDL, 0xED93FA9BL, + 0xE8D3C48DL, 0x283B57CCL, 0xF8D56629L, 0x79132E28L, + 0x785F0191L, 0xED756055L, 0xF7960E44L, 0xE3D35E8CL, + 0x15056DD4L, 0x88F46DBAL, 0x03A16125L, 0x0564F0BDL, + 0xC3EB9E15L, 0x3C9057A2L, 0x97271AECL, 0xA93A072AL, + 0x1B3F6D9BL, 0x1E6321F5L, 0xF59C66FBL, 0x26DCF319L, + 0x7533D928L, 0xB155FDF5L, 0x03563482L, 0x8ABA3CBBL, + 0x28517711L, 0xC20AD9F8L, 0xABCC5167L, 0xCCAD925FL, + 0x4DE81751L, 0x3830DC8EL, 0x379D5862L, 0x9320F991L, + 0xEA7A90C2L, 0xFB3E7BCEL, 0x5121CE64L, 0x774FBE32L, + 0xA8B6E37EL, 0xC3293D46L, 0x48DE5369L, 0x6413E680L, + 0xA2AE0810L, 0xDD6DB224L, 0x69852DFDL, 0x09072166L, + 0xB39A460AL, 0x6445C0DDL, 0x586CDECFL, 0x1C20C8AEL, + 0x5BBEF7DDL, 0x1B588D40L, 0xCCD2017FL, 0x6BB4E3BBL, + 0xDDA26A7EL, 0x3A59FF45L, 0x3E350A44L, 0xBCB4CDD5L, + 0x72EACEA8L, 0xFA6484BBL, 0x8D6612AEL, 0xBF3C6F47L, + 0xD29BE463L, 0x542F5D9EL, 0xAEC2771BL, 0xF64E6370L, + 0x740E0D8DL, 0xE75B1357L, 0xF8721671L, 0xAF537D5DL, + 0x4040CB08L, 0x4EB4E2CCL, 0x34D2466AL, 0x0115AF84L, + 0xE1B00428L, 0x95983A1DL, 0x06B89FB4L, 0xCE6EA048L, + 0x6F3F3B82L, 0x3520AB82L, 0x011A1D4BL, 0x277227F8L, + 0x611560B1L, 0xE7933FDCL, 0xBB3A792BL, 0x344525BDL, + 0xA08839E1L, 0x51CE794BL, 0x2F32C9B7L, 0xA01FBAC9L, + 0xE01CC87EL, 0xBCC7D1F6L, 0xCF0111C3L, 0xA1E8AAC7L, + 0x1A908749L, 0xD44FBD9AL, 0xD0DADECBL, 0xD50ADA38L, + 0x0339C32AL, 0xC6913667L, 0x8DF9317CL, 0xE0B12B4FL, + 0xF79E59B7L, 0x43F5BB3AL, 0xF2D519FFL, 0x27D9459CL, + 0xBF97222CL, 0x15E6FC2AL, 0x0F91FC71L, 0x9B941525L, + 0xFAE59361L, 0xCEB69CEBL, 0xC2A86459L, 0x12BAA8D1L, + 0xB6C1075EL, 0xE3056A0CL, 0x10D25065L, 0xCB03A442L, + 0xE0EC6E0EL, 0x1698DB3BL, 0x4C98A0BEL, 0x3278E964L, + 0x9F1F9532L, 0xE0D392DFL, 0xD3A0342BL, 0x8971F21EL, + 0x1B0A7441L, 0x4BA3348CL, 0xC5BE7120L, 0xC37632D8L, + 0xDF359F8DL, 0x9B992F2EL, 0xE60B6F47L, 0x0FE3F11DL, + 0xE54CDA54L, 0x1EDAD891L, 0xCE6279CFL, 0xCD3E7E6FL, + 0x1618B166L, 0xFD2C1D05L, 0x848FD2C5L, 0xF6FB2299L, + 0xF523F357L, 0xA6327623L, 0x93A83531L, 0x56CCCD02L, + 0xACF08162L, 0x5A75EBB5L, 0x6E163697L, 0x88D273CCL, + 0xDE966292L, 0x81B949D0L, 0x4C50901BL, 0x71C65614L, + 0xE6C6C7BDL, 0x327A140AL, 0x45E1D006L, 0xC3F27B9AL, + 0xC9AA53FDL, 0x62A80F00L, 0xBB25BFE2L, 0x35BDD2F6L, + 0x71126905L, 0xB2040222L, 0xB6CBCF7CL, 0xCD769C2BL, + 0x53113EC0L, 0x1640E3D3L, 0x38ABBD60L, 0x2547ADF0L, + 0xBA38209CL, 0xF746CE76L, 0x77AFA1C5L, 0x20756060L, + 0x85CBFE4EL, 0x8AE88DD8L, 0x7AAAF9B0L, 0x4CF9AA7EL, + 0x1948C25CL, 0x02FB8A8CL, 0x01C36AE4L, 0xD6EBE1F9L, + 0x90D4F869L, 0xA65CDEA0L, 0x3F09252DL, 0xC208E69FL, + 0xB74E6132L, 0xCE77E25BL, 0x578FDFE3L, 0x3AC372E6L} +}; +//----------------------------------------------------------------------------- +uint32_t F(BLOWFISH_CTX *ctx, uint32_t x) +{ +unsigned short a, b, c, d; +uint32_t y = 0; +//uint32_t y1, y2; + +d = x & 0x00FF; +x >>= 8; +c = x & 0x00FF; +x >>= 8; +b = x & 0x00FF; +x >>= 8; +a = x & 0x00FF; + +/*y1 = ctx->S[0][a]; +y2 = ctx->S[1][b]; +y = y1+y2;*/ + +y = ctx->S[0][a] + ctx->S[1][b]; +y = y ^ ctx->S[2][c]; +y = y + ctx->S[3][d]; +return y; +} +//----------------------------------------------------------------------------- +void Blowfish_Encrypt(BLOWFISH_CTX *ctx, uint32_t *xl, uint32_t *xr) +{ +uint32_t Xl; +uint32_t Xr; +uint32_t temp; +short i; + +Xl = *xl; +Xr = *xr; + +for (i = 0; i < N; ++i) + { + Xl = Xl ^ ctx->P[i]; + Xr = F(ctx, Xl) ^ Xr; + temp = Xl; + Xl = Xr; + Xr = temp; + } + +temp = Xl; +Xl = Xr; +Xr = temp; +Xr = Xr ^ ctx->P[N]; +Xl = Xl ^ ctx->P[N + 1]; +*xl = Xl; +*xr = Xr; +} +//----------------------------------------------------------------------------- +void Blowfish_Decrypt(BLOWFISH_CTX *ctx, uint32_t *xl, uint32_t *xr) +{ +uint32_t Xl; +uint32_t Xr; +uint32_t temp; +short i; + +Xl = *xl; +Xr = *xr; + +for (i = N + 1; i > 1; --i) + { + Xl = Xl ^ ctx->P[i]; + Xr = F(ctx, Xl) ^ Xr; + /* Exchange Xl and Xr */ + temp = Xl; + Xl = Xr; + Xr = temp; + } + +/* Exchange Xl and Xr */ +temp = Xl; +Xl = Xr; +Xr = temp; +Xr = Xr ^ ctx->P[1]; +Xl = Xl ^ ctx->P[0]; +*xl = Xl; +*xr = Xr; +} +//----------------------------------------------------------------------------- +void Blowfish_Init(BLOWFISH_CTX *ctx, unsigned char *key, int keyLen) +{ +int i, j, k; +uint32_t data, datal, datar; + +memset(ctx->S, 0, sizeof(ctx->S)); + +for (i = 0; i < 4; i++) + { + + for (j = 0; j < 256; j++) + ctx->S[i][j] = ORIG_S[i][j]; + } + +j = 0; + +for (i = 0; i < N + 2; ++i) + { + data = 0x00000000; + + for (k = 0; k < 4; ++k) + { + data = (data << 8) | key[j]; + j = j + 1; + if (j >= keyLen) + j = 0; + } + + ctx->P[i] = ORIG_P[i] ^ data; + } + +datal = 0x00000000; +datar = 0x00000000; + +for (i = 0; i < N + 2; i += 2) + { + Blowfish_Encrypt(ctx, &datal, &datar); + ctx->P[i] = datal; + ctx->P[i + 1] = datar; + } + +for (i = 0; i < 4; ++i) + { + + for (j = 0; j < 256; j += 2) + { + Blowfish_Encrypt(ctx, &datal, &datar); + ctx->S[i][j] = datal; + ctx->S[i][j + 1] = datar; + } + } +} +//----------------------------------------------------------------------------- +void EnDecodeInit(const char * passwd, int, BLOWFISH_CTX *ctx) +{ +unsigned char * keyL = NULL;//[PASSWD_LEN]; // ðÁÒÏÌØ ÄÌÑ ÛÉÆÒÏ×ËÉ + +keyL = new unsigned char[PASSWD_LEN]; + +memset(keyL, 0, PASSWD_LEN); + +strncpy((char *)keyL, passwd, PASSWD_LEN); + +Blowfish_Init(ctx, keyL, PASSWD_LEN); + +delete[] keyL; +} +//----------------------------------------------------------------------------- +// Note: swap bytes order for compatibility with OpenSSL +uint32_t bytes2block(const char * c) +{ + uint32_t t = static_cast(*c++); + t += static_cast(*c++) << 8; + t += static_cast(*c++) << 16; + t += static_cast(*c) << 24; + return t; +} +//----------------------------------------------------------------------------- +// Note: swap bytes order for compatibility with OpenSSL +void block2bytes(uint32_t t, char * c) +{ + *c++ = t & 0x000000FF; + *c++ = t >> 8 & 0x000000FF; + *c++ = t >> 16 & 0x000000FF; + *c = t >> 24 & 0x000000FF; +} +//----------------------------------------------------------------------------- +void DecodeString(char * d, const char * s, BLOWFISH_CTX *ctx) +{ +uint32_t a = bytes2block(s); +uint32_t b = bytes2block(s + 4); + +Blowfish_Decrypt(ctx, &a, &b); + +block2bytes(a, d); +block2bytes(b, d + 4); +} +//----------------------------------------------------------------------------- +void EncodeString(char * d, const char * s, BLOWFISH_CTX *ctx) +{ +uint32_t a = bytes2block(s); +uint32_t b = bytes2block(s + 4); + +Blowfish_Encrypt(ctx, &a, &b); + +block2bytes(a, d); +block2bytes(b, d + 4); +} +//----------------------------------------------------------------------------- diff --git a/stglibs/crypto.lib/blowfish.h b/stglibs/crypto.lib/blowfish.h new file mode 100644 index 00000000..e0cf2068 --- /dev/null +++ b/stglibs/crypto.lib/blowfish.h @@ -0,0 +1,29 @@ +/* + * Author : Paul Kocher + * E-mail : pck@netcom.com + * Date : 1997 + * Description: C implementation of the Blowfish algorithm. + */ + +#ifndef BLOWFISH_H +#define BLOWFISH_H + +#include "os_int.h" + +#define MAXKEYBYTES 56 /* 448 bits */ + +typedef struct { + uint32_t P[16 + 2]; + uint32_t S[4][256]; +} BLOWFISH_CTX; + +void Blowfish_Init(BLOWFISH_CTX *ctx, unsigned char *key, int keyLen); +void Blowfish_Encrypt(BLOWFISH_CTX *ctx, uint32_t *xl, uint32_t *xr); +void Blowfish_Decrypt(BLOWFISH_CTX *ctx, uint32_t *xl, uint32_t *xr); + +void EnDecodeInit(const char * key, int passwdLen, BLOWFISH_CTX *ctx); +void DecodeString(char * d, const char * s, BLOWFISH_CTX *ctx); +void EncodeString(char * d, const char * s, BLOWFISH_CTX *ctx); + +#endif + diff --git a/stglibs/crypto.lib/crypto.bpf b/stglibs/crypto.lib/crypto.bpf new file mode 100644 index 00000000..f5907baa --- /dev/null +++ b/stglibs/crypto.lib/crypto.bpf @@ -0,0 +1,9 @@ +//--------------------------------------------------------------------------- + +#include +#pragma hdrstop +#define Library + +// To add a file to the library use the Project menu 'Add to Project'. + + \ No newline at end of file diff --git a/stglibs/crypto.lib/crypto.bpr b/stglibs/crypto.lib/crypto.bpr new file mode 100644 index 00000000..78be7185 --- /dev/null +++ b/stglibs/crypto.lib/crypto.bpr @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Version Info] +IncludeVerInfo=0 +AutoIncBuild=0 +MajorVer=1 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=1058 +CodePage=1251 + +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=1.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=1.0.0.0 +Comments= + +[HistoryLists\hlIncludePath] +Count=2 +Item0=$(BCB)\include;$(BCB)\include\vcl;..\..\include +Item1=$(BCB)\include;$(BCB)\include\vcl + +[HistoryLists\hlLibraryPath] +Count=1 +Item0=$(BCB)\lib\obj;$(BCB)\lib + +[HistoryLists\hlDebugSourcePath] +Count=1 +Item0=$(BCB)\source\vcl + +[HistoryLists\hlConditionals] +Count=2 +Item0=_DEBUG;WIN32 +Item1=_DEBUG + +[HistoryLists\hlIntOutputDir] +Count=1 +Item0=obj + +[HistoryLists\hlFinalOutputDir] +Count=2 +Item0=..\..\lib\ +Item1=..\..\lib + +[HistoryLists\hlTlibPageSize] +Count=1 +Item0=0x0010 + +[Debugging] +DebugSourceDirs=$(BCB)\source\vcl + +[Parameters] +RunParams= +Launcher= +UseLauncher=0 +DebugCWD= +HostApplication= +RemoteHost= +RemotePath= +RemoteLauncher= +RemoteCWD= +RemoteDebug=0 + +[Compiler] +ShowInfoMsgs=0 +LinkDebugVcl=0 +LinkCGLIB=0 + +[CORBA] +AddServerUnit=1 +AddClientUnit=1 +PrecompiledHeaders=1 + +[Language] +ActiveLang= +ProjectLang= +RootDir= + + \ No newline at end of file diff --git a/stglibs/dotconfpp.lib/Makefile b/stglibs/dotconfpp.lib/Makefile new file mode 100644 index 00000000..85e34a7f --- /dev/null +++ b/stglibs/dotconfpp.lib/Makefile @@ -0,0 +1,14 @@ +############################################################################### +# $Id: Makefile,v 1.3 2007/05/08 14:29:19 faust Exp $ +############################################################################### + +LIB_NAME = dotconfpp +PROG = lib$(LIB_NAME) + +SRCS = dotconfpp.cpp \ + mempool.cpp + +INCS = dotconfpp.h \ + mempool.h + +include ../Makefile.in diff --git a/stglibs/dotconfpp.lib/dotconfpp.cpp b/stglibs/dotconfpp.lib/dotconfpp.cpp new file mode 100644 index 00000000..b82ae818 --- /dev/null +++ b/stglibs/dotconfpp.lib/dotconfpp.cpp @@ -0,0 +1,661 @@ +/* Copyright (C) 2003 Aleksey Krivoshey +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include // dirname +#include // glob +#include + +#include "dotconfpp.h" + +DOTCONFDocumentNode::DOTCONFDocumentNode():previousNode(NULL), nextNode(NULL), parentNode(NULL), childNode(NULL), + values(NULL), valuesCount(0), + name(NULL), lineNum(0), fileName(NULL), closed(true) +{ +} + +DOTCONFDocumentNode::~DOTCONFDocumentNode() +{ + free(name); + if(values != NULL){ + for(int i = 0 ; i < valuesCount; i++){ + free(values[i]); + } + free(values); + } +} + +void DOTCONFDocumentNode::pushValue(char * _value) +{ + valuesCount++; + values = (char**)realloc(values, valuesCount*sizeof(char*)); + values[valuesCount-1] = strdup(_value); +} + +const char* DOTCONFDocumentNode::getValue(int index) const +{ + if(index >= valuesCount){ + return NULL; + } + return values[index]; +} + +DOTCONFDocument::DOTCONFDocument(DOTCONFDocument::CaseSensitive caseSensitivity): + mempool(NULL), + curParent(NULL), curPrev(NULL), errorCallback(NULL), errorCallbackData(NULL), + curLine(0), file(NULL), fileName(NULL) +{ + if(caseSensitivity == CASESENSITIVE){ + cmp_func = strcmp; + } else { + cmp_func = strcasecmp; + } + + mempool = new AsyncDNSMemPool(1024); + mempool->initialize(); +} + +DOTCONFDocument::~DOTCONFDocument() +{ + for(std::list::iterator i = nodeTree.begin(); i != nodeTree.end(); i++){ + delete(*i); + } + for(std::list::iterator i = requiredOptions.begin(); i != requiredOptions.end(); i++){ + free(*i); + } + for(std::list::iterator i = processedFiles.begin(); i != processedFiles.end(); i++){ + free(*i); + } + free(fileName); + delete mempool; +} + +int DOTCONFDocument::cleanupLine(char * line) +{ + char * start = line; + char * bg = line; + bool multiline = false; + bool concat = false; + char * word = NULL; + + if(!words.empty() && quoted) + concat = true; + + while(*line){ + if((*line == '#' || *line == ';') && !quoted){ + *bg = 0; + if(strlen(start)){ + //printf("2start='%s'\n", start); + if(concat){ + word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1); + strcpy(word, words.back()); + strcat(word, start); + words.pop_back(); + concat = false; + } else { + word = mempool->strdup(start); + } + words.push_back(word); + } + break; + } + if(*line == '=' && !quoted){ // 'parameter = value' is the same as 'parameter value' but do not replace with ' ' when used in quoted value + *line = ' ';continue; + } + if(*line == '\\' && (*(line+1) == '"' || *(line+1) == '\'')){ + *bg++ = *(line+1); + line+=2; continue; + } + if(*line == '\\' && *(line+1) == 'n'){ + *bg++ = '\n'; + line+=2; continue; + } + if(*line == '\\' && *(line+1) == 'r'){ + *bg++ = '\r'; + line+=2; continue; + } + if(*line == '\\' && (*(line+1) == '\n' || *(line+1) == '\r')){ //multiline + *bg = 0; + if(strlen(start)){ + //printf("3start='%s'\n", start); + if(concat){ + word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1); + strcpy(word, words.back()); + strcat(word, start); + words.pop_back(); + concat = false; + } else { + word = mempool->strdup(start); + } + words.push_back(word); + } + multiline = true; + break; + } + if(*line == '"' || *line == '\''){ //need to handle quotes because of spaces or = that may be between + quoted = !quoted; + line++; continue; + } + if(isspace(*line) && !quoted){ + *bg++ = 0; + if(strlen(start)){ + //printf("start='%s'\n", start); + if(concat){ + word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1); + strcpy(word, words.back()); + strcat(word, start); + words.pop_back(); + concat = false; + } else { + word = mempool->strdup(start); + } + words.push_back(word); + } + start = bg; + while(isspace(*++line)) {}; + continue; + } + *bg++ = *line++; + } + + if(quoted && !multiline){ + error(curLine, fileName, "unterminated quote"); + return -1; + } + + return multiline?1:0; +} + +int DOTCONFDocument::parseLine() +{ + char * word = NULL; + char * nodeName = NULL; + char * nodeValue = NULL; + DOTCONFDocumentNode * tagNode = NULL; + bool newNode = false; + + for(std::list::iterator i = words.begin(); i != words.end(); i++) { + word = *i; + + if(*word == '<'){ + newNode = true; + } + + if(newNode){ + nodeValue = NULL; + nodeName = NULL; + newNode = false; + } + + size_t wordLen = strlen(word); + if(word[wordLen-1] == '>'){ + word[wordLen-1] = 0; + newNode = true; + } + + if(nodeName == NULL){ + nodeName = word; + bool closed = true; //if this not <> node then it is closed by default + if(*nodeName == '<'){ + if(*(nodeName+1) != '/'){ //opening tag + nodeName++; + closed = false; + } else { //closing tag + nodeName+=2; + std::list::reverse_iterator i=nodeTree.rbegin(); + for(; i!=nodeTree.rend(); i++){ + if(!cmp_func(nodeName, (*i)->name) && !(*i)->closed){ + (*i)->closed = true; + curParent = (*i)->parentNode; + curPrev = *i; + break; + } + } + if(i==nodeTree.rend()){ + error(curLine, fileName, "not matched closing tag ", nodeName); + return -1; + } + continue; + } + } + tagNode = new DOTCONFDocumentNode; + tagNode->name = strdup(nodeName); + tagNode->document = this; + tagNode->fileName = processedFiles.back(); + tagNode->lineNum = curLine; + tagNode->closed = closed; + if(!nodeTree.empty()){ + DOTCONFDocumentNode * prev = nodeTree.back(); + if(prev->closed){ + + curPrev->nextNode = tagNode; + tagNode->previousNode = curPrev; + tagNode->parentNode = curParent; + + } else { + prev->childNode = tagNode; + tagNode->parentNode = prev; + curParent = prev; + } + } + nodeTree.push_back(tagNode); + curPrev = tagNode; + } else { + nodeValue = word; + tagNode->pushValue(nodeValue); + } + } + + return 0; +} +int DOTCONFDocument::parseFile(DOTCONFDocumentNode * _parent) +{ + char str[512]; + int ret = 0; + curLine = 0; + curParent = _parent; + + quoted = false; + size_t slen = 0; + + while(fgets(str, 511, file)){ + curLine++; + slen = strlen(str); + if( slen >= 510 ){ + error(curLine, fileName, "warning: line too long"); + } + if(str[slen-1] != '\n'){ + str[slen] = '\n'; + str[slen+1] = 0; + } + if((ret = cleanupLine(str)) == -1){ + break; + } + if(ret == 0){ + if(!words.empty()){ + ret = parseLine(); + mempool->free(); + words.clear(); + if(ret == -1){ + break; + } + } + } + } + + return ret; +} + +int DOTCONFDocument::checkConfig(const std::list::iterator & from) +{ + int ret = 0; + + DOTCONFDocumentNode * tagNode = NULL; + int vi = 0; + for(std::list::iterator i = from; i != nodeTree.end(); i++){ + tagNode = *i; + if(!tagNode->closed){ + error(tagNode->lineNum, tagNode->fileName, "unclosed tag %s", tagNode->name); + ret = -1; + break; + } + vi = 0; + while( vi < tagNode->valuesCount ){ + //if((tagNode->values[vi])[0] == '$' && (tagNode->values[vi])[1] == '{' && strchr(tagNode->values[vi], '}') ){ + if(strstr(tagNode->values[vi], "${") && strchr(tagNode->values[vi], '}') ){ + ret = macroSubstitute(tagNode, vi ); + mempool->free(); + if(ret == -1){ + break; + } + } + vi++; + } + if(ret == -1){ + break; + } + } + + return ret; +} + +int DOTCONFDocument::setContent(const char * _fileName) +{ + int ret = 0; + char realpathBuf[PATH_MAX]; + + if(realpath(_fileName, realpathBuf) == NULL){ + error(0, NULL, "realpath(%s) failed: %s", _fileName, strerror(errno)); + return -1; + } + + fileName = strdup(realpathBuf); + + char * forPathName = strdup(realpathBuf); + + if (forPathName == NULL) { + error(0, NULL, "Not enought memory to duplicate realpath"); + return -1; + } + + char * _pathName = dirname(forPathName); + + std::string pathName(_pathName); + + free(forPathName); // From strdup + + processedFiles.push_back(strdup(realpathBuf)); + + if(( file = fopen(fileName, "r")) == NULL){ + error(0, NULL, "failed to open file '%s': %s", fileName, strerror(errno)); + return -1; + } + + ret = parseFile(); + + (void) fclose(file); + + if(!ret){ + + if( (ret = checkConfig(nodeTree.begin())) == -1){ + return -1; + } + + std::list::iterator from; + DOTCONFDocumentNode * tagNode = NULL; + int vi = 0; + for(std::list::iterator i = nodeTree.begin(); i!=nodeTree.end(); i++){ + tagNode = *i; + if(!cmp_func("IncludeFile", tagNode->name)){ + vi = 0; + while( vi < tagNode->valuesCount ){ + glob_t globBuf; + std::string nodeFilePath; + if (*tagNode->values[vi] != '/') { + // Relative path + nodeFilePath = pathName + "/" + tagNode->values[vi]; + } else { + // Absolute path + nodeFilePath = tagNode->values[vi]; + } + int res = glob(nodeFilePath.c_str(), 0, NULL, &globBuf); + if (res) { + switch (res) { + case GLOB_NOSPACE: + error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': no free space", nodeFilePath.c_str()); + return -1; + case GLOB_ABORTED: + // printf("Read error\n"); + // Ignore that error + break; + case GLOB_NOMATCH: + // printf("No match\n"); + // Ignore that error + break; + default: + error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': unknown error", nodeFilePath.c_str()); + return -1; + } + } + if (!res) { + for (size_t i = 0; i < globBuf.gl_pathc; ++i) { + std::string nodeFilePath(globBuf.gl_pathv[i]); + if(access(nodeFilePath.c_str(), R_OK) == -1){ + error(tagNode->lineNum, tagNode->fileName, "%s: %s", nodeFilePath.c_str(), strerror(errno)); + continue; + } + if(realpath(nodeFilePath.c_str(), realpathBuf) == NULL){ + error(tagNode->lineNum, tagNode->fileName, "realpath(%s) failed: %s", nodeFilePath.c_str(), strerror(errno)); + continue; + } + + bool processed = false; + for(std::list::const_iterator itInode = processedFiles.begin(); itInode != processedFiles.end(); itInode++){ + if(!strcmp(*itInode, realpathBuf)){ + processed = true; + break; + } + } + if(processed){ + break; + } + + processedFiles.push_back(strdup(realpathBuf)); + + file = fopen(nodeFilePath.c_str(), "r"); + if(file == NULL){ + error(tagNode->lineNum, fileName, "failed to open file '%s': %s", nodeFilePath.c_str(), strerror(errno)); + continue; + } + //free(fileName); + fileName = strdup(realpathBuf); + from = nodeTree.end(); from--; + + if(tagNode->parentNode){ + DOTCONFDocumentNode * nd = tagNode->parentNode->childNode; + while(nd){ + if(!nd->nextNode) + break; + nd = nd->nextNode; + } + + curPrev = nd; + } + ret = parseFile(tagNode->parentNode); + + //ret = parseFile(tagNode->parentNode); + (void) fclose(file); + if(ret == -1) + continue; + if(checkConfig(++from) == -1){ + continue; + } + } + } + globfree(&globBuf); + vi++; + } + } + } + /* + if( (ret = checkConfig(nodeTree.begin())) == -1){ + return -1; + } + */ + + if(!requiredOptions.empty()) + ret = checkRequiredOptions(); + } + + return ret; +} + +int DOTCONFDocument::checkRequiredOptions() +{ + for(std::list::const_iterator ci = requiredOptions.begin(); ci != requiredOptions.end(); ci++){ + bool matched = false; + for(std::list::iterator i = nodeTree.begin(); i!=nodeTree.end(); i++){ + if(!cmp_func((*i)->name, *ci)){ + matched = true; + break; + } + } + if(!matched){ + error(0, NULL, "required option '%s' not specified", *ci); + return -1; + } + } + return 0; +} + +void DOTCONFDocument::error(int lineNum, const char * fileName, const char * fmt, ...) +{ + va_list args; + va_start(args, fmt); + + size_t len = (lineNum!=0?strlen(fileName):0) + strlen(fmt) + 50; + char * buf = (char*)mempool->alloc(len); + + if(lineNum) + (void) snprintf(buf, len, "DOTCONF++: file '%s', line %d: %s\n", fileName, lineNum, fmt); + else + (void) snprintf(buf, len, "DOTCONF++: %s\n", fmt); + + if (errorCallback) { + errorCallback(errorCallbackData, buf); + } else { + (void) vfprintf(stderr, buf, args); + } + + va_end(args); +} + +char * DOTCONFDocument::getSubstitution(char * macro, int lineNum) +{ + char * buf = NULL; + char * variable = macro+2; + + char * endBr = strchr(macro, '}'); + + if(!endBr){ + error(lineNum, fileName, "unterminated '{'"); + return NULL; + } + *endBr = 0; + + char * defaultValue = strchr(variable, ':'); + + if(defaultValue){ + *defaultValue++ = 0; + if(*defaultValue != '-'){ + error(lineNum, fileName, "incorrect macro substitution syntax"); + return NULL; + } + defaultValue++; + if(*defaultValue == '"' || *defaultValue == '\''){ + defaultValue++; + defaultValue[strlen(defaultValue)-1] = 0; + } + } else { + defaultValue = NULL; + } + + char * subs = getenv(variable); + if( subs ){ + buf = mempool->strdup(subs); + } else { + std::list::iterator i = nodeTree.begin(); + DOTCONFDocumentNode * tagNode = NULL; + for(; i!=nodeTree.end(); i++){ + tagNode = *i; + if(!cmp_func(tagNode->name, variable)){ + if(tagNode->valuesCount != 0){ + buf = mempool->strdup(tagNode->values[0]); + break; + } + } + } + if( i == nodeTree.end() ){ + if( defaultValue ){ + buf = mempool->strdup(defaultValue); + } else { + error(lineNum, fileName, "substitution not found and default value not given"); + return NULL; + } + } + } + return buf; +} + +int DOTCONFDocument::macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex) +{ + int ret = 0; + char * macro = tagNode->values[valueIndex]; + size_t valueLen = strlen(tagNode->values[valueIndex])+1; + char * value = (char*)mempool->alloc(valueLen); + char * v = value; + char * subs = NULL; + + while(*macro){ + if(*macro == '$' && *(macro+1) == '{'){ + char * m = strchr(macro, '}'); + subs = getSubstitution(macro, tagNode->lineNum); + if(subs == NULL){ + ret = -1; + break; + } + macro = m + 1; + *v = 0; + v = (char*)mempool->alloc(strlen(value)+strlen(subs)+valueLen); + strcpy(v, value); + value = strcat(v, subs); + v = value + strlen(value); + continue; + } + *v++ = *macro++; + } + *v = 0; + + free(tagNode->values[valueIndex]); + tagNode->values[valueIndex] = strdup(value); + return ret; +} + +const DOTCONFDocumentNode * DOTCONFDocument::getFirstNode() const +{ + if ( !nodeTree.empty() ) { + return *nodeTree.begin(); + } else { + return NULL; + } +} + +const DOTCONFDocumentNode * DOTCONFDocument::findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode, const DOTCONFDocumentNode * startNode) const +{ + //printf("nodeName=%s, cont=%s, start=%s\n", nodeName, containingNode!=NULL?containingNode->name:"NULL", startNode!=NULL?startNode->name:"NULL"); + + std::list::const_iterator i = nodeTree.begin(); + + if(startNode == NULL) + startNode = parentNode; + + if(startNode != NULL){ + while( i != nodeTree.end() && (*i) != startNode ){ + i++; + } + if( i != nodeTree.end() ) i++; + } + + for(; i!=nodeTree.end(); i++){ + //if(parentNode != NULL && (*i)->parentNode != parentNode){ + if((*i)->parentNode != parentNode){ + continue; + } + if(!cmp_func(nodeName, (*i)->name)){ + return *i; + } + } + return NULL; +} + +void DOTCONFDocument::setRequiredOptionNames(const char ** requiredOptionNames) +{ + while(*requiredOptionNames){ + requiredOptions.push_back(strdup( *requiredOptionNames )); + requiredOptionNames++; + } +} + diff --git a/stglibs/dotconfpp.lib/dotconfpp.h b/stglibs/dotconfpp.lib/dotconfpp.h new file mode 100644 index 00000000..c068640f --- /dev/null +++ b/stglibs/dotconfpp.lib/dotconfpp.h @@ -0,0 +1,122 @@ +/* Copyright (C) 2003 Aleksey Krivoshey +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef DOTCONFPP_H +#define DOTCONFPP_H + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "os_int.h" +#include "mempool.h" + +typedef void (* DOTCONFCallback) (void * data, const char * buf); + +class DOTCONFDocument; + +class DOTCONFDocumentNode +{ +friend class DOTCONFDocument; +private: + DOTCONFDocumentNode * previousNode; + DOTCONFDocumentNode * nextNode; + DOTCONFDocumentNode * parentNode; + DOTCONFDocumentNode * childNode; + char ** values; + int valuesCount; + char * name; + const DOTCONFDocument * document; + int lineNum; + char * fileName; + bool closed; + + void pushValue(char * _value); + +public: + DOTCONFDocumentNode(); + ~DOTCONFDocumentNode(); + + const char * getConfigurationFileName() const { return fileName; } + int getConfigurationLineNumber() const { return lineNum; } + + const DOTCONFDocumentNode * getNextNode() const { return nextNode; } + const DOTCONFDocumentNode * getPreviuosNode() const { return previousNode; } + const DOTCONFDocumentNode * getParentNode() const { return parentNode; } + const DOTCONFDocumentNode * getChildNode() const { return childNode; } + const char * getValue(int index = 0) const; + const char * getName() const { return name; } + const DOTCONFDocument * getDocument() const { return document; } +}; + +class DOTCONFDocument +{ +public: + enum CaseSensitive { CASESENSITIVE, CASEINSENSITIVE }; +protected: + AsyncDNSMemPool * mempool; +private: + DOTCONFDocumentNode * curParent; + DOTCONFDocumentNode * curPrev; + DOTCONFCallback errorCallback; + void * errorCallbackData; + int curLine; + bool quoted; + std::list nodeTree; + std::list requiredOptions; + std::list processedFiles; + FILE * file; + char * fileName; + std::list words; + int (* cmp_func)(const char *, const char *); + + int checkRequiredOptions(); + int parseLine(); + int parseFile(DOTCONFDocumentNode * _parent = NULL); + int checkConfig(const std::list::iterator & from); + int cleanupLine(char * line); + char * getSubstitution(char * macro, int lineNum); + int macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex); + +protected: + virtual void error(int lineNum, const char * fileName, const char * fmt, ...); + +public: + DOTCONFDocument(CaseSensitive caseSensitivity = CASESENSITIVE); + virtual ~DOTCONFDocument(); + + void setErrorCallback(DOTCONFCallback _callback, void * _data) { errorCallback = _callback; errorCallbackData = _data; }; + + int setContent(const char * _fileName); + + void setRequiredOptionNames(const char ** requiredOptionNames); // !TERMINATE ARRAY WITH NULL + const DOTCONFDocumentNode * getFirstNode() const; + const DOTCONFDocumentNode * findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode = NULL, const DOTCONFDocumentNode * startNode = NULL) const; +}; + +#endif diff --git a/stglibs/dotconfpp.lib/mempool.cpp b/stglibs/dotconfpp.lib/mempool.cpp new file mode 100644 index 00000000..3469418c --- /dev/null +++ b/stglibs/dotconfpp.lib/mempool.cpp @@ -0,0 +1,116 @@ +/* Copyright (C) 2003 Aleksey Krivoshey +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "mempool.h" + +AsyncDNSMemPool::PoolChunk::PoolChunk(size_t _size): + pool(NULL), pos(0), size(_size) +{ + pool = ::malloc(size); +} + +AsyncDNSMemPool::PoolChunk::~PoolChunk() +{ + ::free(pool); +} + +AsyncDNSMemPool::AsyncDNSMemPool(size_t _defaultSize): + chunks(NULL), chunksCount(0), defaultSize(_defaultSize), + poolUsage(0), poolUsageCounter(0) +{ +} + +AsyncDNSMemPool::~AsyncDNSMemPool() +{ + for(size_t i = 0; isize - chunk->pos) >= size){ + chunk->pos += size; + return ((u_int8_t*)chunk->pool) + chunk->pos - size; + } + } + addNewChunk(size); + chunks[chunksCount-1]->pos = size; + return chunks[chunksCount-1]->pool; +} + +void AsyncDNSMemPool::free() +{ + size_t pu = 0; + size_t psz = 0; + poolUsageCounter++; + + for(size_t i = 0; ipos; + psz += chunks[i]->size; + chunks[i]->pos = 0; + } + poolUsage=(poolUsage>pu)?poolUsage:pu; + + if(poolUsageCounter >= 10 && chunksCount > 1){ + psz -= chunks[chunksCount-1]->size; + if(poolUsage < psz){ + chunksCount--; + delete chunks[chunksCount]; + } + poolUsage = 0; + poolUsageCounter = 0; + } +} + +void * AsyncDNSMemPool::calloc(size_t size) +{ + return ::memset(this->alloc(size), 0, size); +} + +char * AsyncDNSMemPool::strdup(const char *str) +{ + return ::strcpy((char*)this->alloc(strlen(str)+1), str); +} + diff --git a/stglibs/dotconfpp.lib/mempool.h b/stglibs/dotconfpp.lib/mempool.h new file mode 100644 index 00000000..73b3c4c3 --- /dev/null +++ b/stglibs/dotconfpp.lib/mempool.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2003 Aleksey Krivoshey +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef ASYNC_DNS_MEMPOOL_H +#define ASYNC_DNS_MEMPOOL_H + +#include "os_int.h" + +#include +#include +#include + +class AsyncDNSMemPool +{ +private: + struct PoolChunk { + void * pool; + size_t pos; + size_t size; + + PoolChunk(size_t _size); + ~PoolChunk(); + }; + PoolChunk ** chunks; + size_t chunksCount; + size_t defaultSize; + + size_t poolUsage; + size_t poolUsageCounter; + + void addNewChunk(size_t size); + +public: + AsyncDNSMemPool(size_t _defaultSize = 4096); + virtual ~AsyncDNSMemPool(); + + int initialize(); + void free(); + void * alloc(size_t size); + void * calloc(size_t size); + char * strdup(const char *str); +}; + +#endif + diff --git a/stglibs/hostallow.lib/Makefile b/stglibs/hostallow.lib/Makefile new file mode 100644 index 00000000..97f9c0c0 --- /dev/null +++ b/stglibs/hostallow.lib/Makefile @@ -0,0 +1,12 @@ +############################################################################### +# $Id: Makefile,v 1.3 2007/05/08 14:29:20 faust Exp $ +############################################################################### + +LIB_NAME = hostallow +PROG = lib$(LIB_NAME) + +SRCS = hostallow.cpp + +INCS = hostallow.h + +include ../Makefile.in diff --git a/stglibs/hostallow.lib/hostallow.cpp b/stglibs/hostallow.lib/hostallow.cpp new file mode 100644 index 00000000..9fedbbfc --- /dev/null +++ b/stglibs/hostallow.lib/hostallow.cpp @@ -0,0 +1,294 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hostallow.h" +//----------------------------------------------------------------------------- +HOSTALLOW::HOSTALLOW() +{ + +} +//----------------------------------------------------------------------------- +int HOSTALLOW::ParseHosts(const char * str, int hostsType) +{ +/* +ðÒÏÉÚ×ÏÄÉÍ ÒÁÚÂÏÒ ÓÔÒÏËÉ ×ÉÄÁ host host host ... +ÇÄÅ host ÍÏÖÅÔ ÉÍÅÔØ ×ÉÄ a.b.c.d ÉÌÉ a.b.c.d/e +ÉÌÉ all. +ÐÒÉÞÅÍ × ÓÌÕÞÁÅ ÓÅÔÉ ÍÁÓËÁ É ÁÄÒÅÓ ÄÏÌÖÎÙ ÂÙÔØ +ÓÏÏÔ×ÅÔÓÔ×ÕÀÝÉÍÉ ÄÒÕÇ ÄÒÕÇÕ. + +òÅÚÕÌØÔÁÔÙ ÚÁÎÏÓÉÍ × ÓÏÏÔ×ÅÔÓÔ×ÕÀÝÉÊ ÓÐÉÓÏË + * */ + +int len; +char *s; +char * tok; +uint32_t ip; +uint32_t mask; +//INETADDR inetAddr; + +if (strcasecmp(str, "all") == 0) + { + if (hostsType == hostsAllow) + hostAllowList.push_back(INETADDR()); + else + hostDenyList.push_back(INETADDR()); + return 0; + } +else + { + len = strlen(str); + + s = new char[len + 1]; + + strcpy(s, str); + + tok = strtok(s, " "); + + while (tok) + { + if (ParseIPMask(tok, &ip, &mask) != 0) + { + return -1; + delete[] s; + } + //printfd(__FILE__, "ParseHosts tok %s\n", tok); + tok = strtok(NULL, " "); + if (hostsType == hostsAllow) + { + //printfd(__FILE__, "ParseHosts APPEND allow %X %X\n", ip, mask); + hostAllowList.push_back(INETADDR(ip, mask)); + } + else + { + //printfd(__FILE__, "ParseHosts APPEND deny %X %X\n", ip, mask); + hostDenyList.push_back(INETADDR(ip, mask)); + } + } + } + +delete[] s; +return 0; +} +//----------------------------------------------------------------------------- +int HOSTALLOW::ParseIPMask(const char * s, uint32_t * ip, uint32_t * mask) +{ +/* +òÁÚÂÏÒ ÓÔÒÏËÉ ×ÉÄÁ a.b.c.d/e ÉÌÉ a.b.c.d + +123.123.123.123/30 + * */ +int len; +char * host; + +int i = 0, msk; + +len = strlen(s); +host = new char[len + 1]; + +while (s[i] != 0 && s[i] != '/') + { + host[i] = s[i]; + i++; + } + +host[i] = 0; + +if (inet_addr(host) == INADDR_NONE) + { + delete[] host; + sprintf(errMsg, "Icorrect IP address %s", host); + return -1; + } + +*ip = inet_addr(host); + +char *res; + +if (s[i] == '/') + { + msk = strtol(&s[i+1], &res, 10); + if (*res != 0) + { + sprintf(errMsg, "Icorrect mask %s", &s[i+1]); + delete[] host; + return -1; + } + + if (msk < 0 || msk > 32) + { + sprintf(errMsg, "Icorrect mask %s", &s[i+1]); + delete[] host; + *mask = 0; + return 0; + } + + uint32_t m = 0; + m = htonl(0xFFffFFff<<(32 - msk)); + + *mask = m; + } +else + { + *mask = 0xFFffFFff; + } + +if ((*ip & *mask) != *ip) + { + sprintf(errMsg, "Address does'n match mask.\n"); + delete[] host; + return -1; + } + +delete[] host; +return 0; +} +//----------------------------------------------------------------------------- +int HOSTALLOW::ParseOrder(const char * str) +{ +/* +ÐÒÏÉÚ×ÏÄÉÍ ÒÁÚÂÏÒ ÓÔÒÏËÉ ×ÉÄÁ allow deny ÉÌÉ deny allow + */ + +if (strcasecmp(str, "allow,deny") == 0) + { + order = orderAllow; + return 0; + } + +if (strcasecmp(str, "deny,allow") == 0) + { + order = orderDeny; + return 0; + } + +sprintf(errMsg, "Parameter \'order\' must be \'allow,deny\' or \'deny,allow\'"); +return -1; +} +//----------------------------------------------------------------------------- +int HOSTALLOW::GetError() +{ +/* +÷ÏÚ×ÒÁÝÁÅÍ ËÏÄ ÏÛÉÂËÉ É ÓÂÒÁÓÙ×ÁÅÍ ÅÅ. + * */ +return 0; +} +//----------------------------------------------------------------------------- +bool HOSTALLOW::HostAllowed(uint32_t ip) +{ +/* +ðÒÏ×ÅÒÑÅÍ Ñ×ÌÑÅÔÓÑ ÌÉ éð ÒÁÚÒÅÛÅÎÎÙÍ ÉÌÉ ÎÅÔ + * */ + +if (order == orderDeny) + { + if (IsHostInDeniedList(ip)) + { + return false; + } + + if (IsHostInAllowedList(ip)) + { + return true; + } + } +else + { + if (IsHostInAllowedList(ip)) + { + return true; + } + + if (IsHostInDeniedList(ip)) + { + return false; + } + } + +return false; +} +//----------------------------------------------------------------------------- +int HOSTALLOW::IsIPInSubnet(uint32_t checkedIP, INETADDR &ia) +{ +//uint32_t checkedIP; +if ((ia.mask & checkedIP) == (ia.ip)) + return true; +return false; +} +//----------------------------------------------------------------------------- +bool HOSTALLOW::IsHostInAllowedList(uint32_t ip) +{ +/* +îÁÈÏÄÉÔÓÑ ÌÉ éð × ÓÐÉÓËÅ ÒÁÚÒÅÛÅÎÎÙÈ + * */ +list::iterator li; + +li = hostAllowList.begin(); + +while(li != hostAllowList.end()) + { + if (IsIPInSubnet(ip, *li)) + return true; + } + +return false; +} +//----------------------------------------------------------------------------- +bool HOSTALLOW::IsHostInDeniedList(uint32_t ip) +{ +/* +îÁÈÏÄÉÔÓÑ ÌÉ éð × ÓÐÉÓËÅ ÚÁÐÒÅÝÅÎÎÙÈ + * */ +list::iterator li; + +li = hostDenyList.begin(); + +while(li != hostDenyList.end()) + { + if (IsIPInSubnet(ip, *li)) + return true; + } + +return false; +} +//----------------------------------------------------------------------------- +const char * HOSTALLOW::GetStrError() +{ +/* +÷ÏÚ×ÒÁÝÁÅÍ ÔÅËÓÔÏ×ÏÅ ÏÐÉÓÁÎÉÅ ÏÛÉÂËÉ. + * */ +return errMsg; +} +//----------------------------------------------------------------------------- + diff --git a/stglibs/hostallow.lib/hostallow.h b/stglibs/hostallow.lib/hostallow.h new file mode 100644 index 00000000..034cdb3f --- /dev/null +++ b/stglibs/hostallow.lib/hostallow.h @@ -0,0 +1,83 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + + +#ifndef HOSTALLOW_H +#define HOSTALLOW_H + +#include "os_int.h" + +#include + +using namespace std; + +#define HA_ERR_MSG_LEN (255) +//----------------------------------------------------------------------------- +enum ORDER +{ + orderAllow = 0, + orderDeny +}; +//----------------------------------------------------------------------------- +enum +{ + hostsAllow = 0, + hostsDeny +}; +//----------------------------------------------------------------------------- +struct INETADDR +{ + INETADDR(uint32_t i, uint8_t m) {ip = i; mask = m;}; + INETADDR() {ip = 0; mask = 0;}; + uint32_t ip; + uint8_t mask; +}; +//----------------------------------------------------------------------------- +class HOSTALLOW +{ +public: + HOSTALLOW(); + int ParseHosts(const char *, int hostsType); + int ParseOrder(const char *); + int GetError(); + bool HostAllowed(uint32_t ip); + const char * GetStrError(); + +private: + int ParseIPMask(const char * s, uint32_t * ip, uint32_t * mask); + bool IsHostInDeniedList(uint32_t ip); + bool IsHostInAllowedList(uint32_t ip); + //int IsIPInSubnet(INETADDR &ia); + int IsIPInSubnet(uint32_t checkedIP, INETADDR &ia); + list hostAllowList; + list hostDenyList; + char errMsg[HA_ERR_MSG_LEN]; + int order; + char src[16 + 1]; + char dst[16 + 1]; + +}; +//----------------------------------------------------------------------------- + +#endif diff --git a/stglibs/ia_auth_c.lib/Makefile b/stglibs/ia_auth_c.lib/Makefile new file mode 100644 index 00000000..fb49f948 --- /dev/null +++ b/stglibs/ia_auth_c.lib/Makefile @@ -0,0 +1,16 @@ +############################################################################### +# $Id: Makefile,v 1.11 2010/08/18 07:47:03 faust Exp $ +############################################################################### + +LIB_NAME = ia_auth_c +PROG = lib$(LIB_NAME) + +SRCS = ia_auth_c.cpp + +INCS = ia_auth_c.h + +STGLIBS = -lstg_common \ + -lstg_crypto +LIBS = $(LIB_THREAD) + +include ../Makefile.in diff --git a/stglibs/ia_auth_c.lib/ia_auth_c.bpf b/stglibs/ia_auth_c.lib/ia_auth_c.bpf new file mode 100644 index 00000000..e3c42ce0 --- /dev/null +++ b/stglibs/ia_auth_c.lib/ia_auth_c.bpf @@ -0,0 +1,9 @@ +//--------------------------------------------------------------------------- + +#include +#pragma hdrstop +#define Library + +// To add a file to the library use the Project menu 'Add to Project'. + + \ No newline at end of file diff --git a/stglibs/ia_auth_c.lib/ia_auth_c.bpr b/stglibs/ia_auth_c.lib/ia_auth_c.bpr new file mode 100644 index 00000000..c80bf51c --- /dev/null +++ b/stglibs/ia_auth_c.lib/ia_auth_c.bpr @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Version Info] +IncludeVerInfo=0 +AutoIncBuild=0 +MajorVer=1 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=1049 +CodePage=1251 + +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=1.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=1.0.0.0 +Comments= + +[HistoryLists\hlIncludePath] +Count=2 +Item0=$(BCB)\include;$(BCB)\include\vcl;..\..\include +Item1=$(BCB)\include;$(BCB)\include\vcl + +[HistoryLists\hlLibraryPath] +Count=1 +Item0=$(BCB)\lib\obj;$(BCB)\lib + +[HistoryLists\hlDebugSourcePath] +Count=1 +Item0=$(BCB)\source\vcl + +[HistoryLists\hlConditionals] +Count=2 +Item0=_DEBUG;WIN32 +Item1=_DEBUG + +[HistoryLists\hlFinalOutputDir] +Count=1 +Item0=..\..\lib + +[Debugging] +DebugSourceDirs=$(BCB)\source\vcl + +[Parameters] +RunParams= +Launcher= +UseLauncher=0 +DebugCWD= +HostApplication= +RemoteHost= +RemotePath= +RemoteLauncher= +RemoteCWD= +RemoteDebug=0 + +[Compiler] +ShowInfoMsgs=0 +LinkDebugVcl=0 +LinkCGLIB=0 + +[CORBA] +AddServerUnit=1 +AddClientUnit=1 +PrecompiledHeaders=1 + + \ No newline at end of file diff --git a/stglibs/ia_auth_c.lib/ia_auth_c.cpp b/stglibs/ia_auth_c.lib/ia_auth_c.cpp new file mode 100644 index 00000000..d97692ea --- /dev/null +++ b/stglibs/ia_auth_c.lib/ia_auth_c.cpp @@ -0,0 +1,868 @@ +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 1, or (at your option) +** any later version. + +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. + +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + $Author: faust $ + $Revision: 1.15 $ + $Date: 2010/04/16 11:28:03 $ +*/ + +/* +* Author : +* Boris Mikhailenko +* Andrey Rakhmanov - èñïðàâëåíèå äâóõ áàãîâ. +*/ + +//--------------------------------------------------------------------------- + +#include +#include + +#include + +#ifdef WIN32 + #include + #include + #include + #include +#else + #include + #include + #include + #include + #include + #include + #include +#endif + +#include "common.h" +#include "ia_auth_c.h" + +#define IA_NONE (0) +#define IA_CONNECT (1) +#define IA_DISCONNECT (2) + +#define IA_DEBUGPROTO 1 + +#define IA_ID "00100" +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +#ifndef WIN32 +#include +void * RunL(void * data) +{ + +IA_CLIENT_PROT * c = (IA_CLIENT_PROT *)data; +static int a = 0; + +if (a == 0) + { + usleep(50000); + a = 1; + } + +while (c->GetNonstop()) + { + c->Run(); + } +return NULL; +} +//--------------------------------------------------------------------------- +void Sleep(int ms) +{ +usleep(ms * 1000); +} +//--------------------------------------------------------------------------- +long GetTickCount() +{ +struct timeval tv; +gettimeofday(&tv, NULL); +return tv.tv_sec*1000 + tv.tv_usec/1000; +} +#else +//--------------------------------------------------------------------------- +unsigned long WINAPI RunW(void * data) +{ +IA_CLIENT_PROT * c = (IA_CLIENT_PROT *)data; +while (c->GetNonstop()) + c->Run(); +return 0; +} +//--------------------------------------------------------------------------- +#endif +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +IA_CLIENT_PROT::IA_CLIENT_PROT(const string & sn, unsigned short p, uint16_t localPort) + : stat(), + action(IA_NONE), + phase(1), + phaseTime(0), + messageText(), + infoText(), + strError(), + codeError(0), + nonstop(true), + isNetPrepared(false), + proxyMode(false), + password(), + login(), + serverName(sn), + port(p), + ip(0), + localPort(localPort), + firstConnect(true), + reconnect(0), + sockr(0), + protNum(0), + userTimeout(60), + aliveTimeout(5), + rnd(0), + pStatusChangedCb(NULL), + pStatChangedCb(NULL), + pInfoCb(NULL), + pErrorCb(NULL), + pDirNameCb(NULL), + statusChangedCbData(NULL), + statChangedCbData(NULL), + infoCbData(NULL), + errorCbData(NULL), + dirNameCbData(NULL), + packetTypes(), + connSyn8(NULL), + connSynAck8(NULL), + connAck8(NULL), + aliveSyn8(NULL), + aliveAck8(NULL), + disconnSyn8(NULL), + disconnSynAck8(NULL), + disconnAck8(NULL), + err(), + info(NULL) +{ +memset(&stat, 0, sizeof(stat)); + +#ifdef WIN32 +WSAStartup(MAKEWORD(2, 0), &wsaData); +#endif + +packetTypes["CONN_SYN"] = CONN_SYN_N; +packetTypes["CONN_SYN_ACK"] = CONN_SYN_ACK_N; +packetTypes["CONN_ACK"] = CONN_ACK_N; +packetTypes["ALIVE_SYN"] = ALIVE_SYN_N; +packetTypes["ALIVE_ACK"] = ALIVE_ACK_N; +packetTypes["DISCONN_SYN"] = DISCONN_SYN_N; +packetTypes["DISCONN_SYN_ACK"] = DISCONN_SYN_ACK_N; +packetTypes["DISCONN_ACK"] = DISCONN_ACK_N; +packetTypes["FIN"] = FIN_N; +packetTypes["ERR"] = ERROR_N; +packetTypes["INFO"] = INFO_N; +packetTypes["INFO_7"] = INFO_7_N; +packetTypes["INFO_8"] = INFO_8_N; + +unsigned char key[IA_PASSWD_LEN]; +memset(key, 0, IA_LOGIN_LEN); +strncpy((char *)key, "pr7Hhen", 8); +Blowfish_Init(&ctxHdr, key, IA_PASSWD_LEN); +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::PrepareNet() +{ +struct hostent * phe; +unsigned long ip; + +ip = inet_addr(serverName.c_str()); +if (ip == INADDR_NONE) + { + phe = gethostbyname(serverName.c_str()); + if (phe) + { + ip = *((unsigned long*)phe->h_addr_list[0]); + } + else + { + strError = string("Unknown host ") + "\'" + serverName + "\'"; + codeError = IA_GETHOSTBYNAME_ERROR; + if (pErrorCb != NULL) + pErrorCb(messageText, IA_GETHOSTBYNAME_ERROR, errorCbData); + } + } + +#ifndef WIN32 +close(sockr); +#else +closesocket(sockr); +#endif + +sockr = socket(AF_INET, SOCK_DGRAM, 0); // Cîêåò ÷åðåç êîòîðûé øëåì è ïðèíèìàåì + +localAddrS.sin_family = AF_INET; +localAddrS.sin_port = htons(port); +localAddrS.sin_addr.s_addr = inet_addr("0.0.0.0"); + +localAddrR.sin_family = AF_INET; + +if (localPort) + localAddrR.sin_port = htons(localPort); +else + localAddrR.sin_port = htons(port); +localAddrR.sin_addr.s_addr = inet_addr("0.0.0.0"); + +servAddr.sin_family = AF_INET; +servAddr.sin_port = htons(port); +servAddr.sin_addr.s_addr = ip; + +int res = bind(sockr, (struct sockaddr*)&localAddrR, sizeof(localAddrR)); +if (res == -1) + { + strError = "bind error"; + codeError = IA_BIND_ERROR; + if (pErrorCb != NULL) + pErrorCb(messageText, IA_BIND_ERROR, errorCbData); + return; + } + +#ifdef WIN32 +unsigned long arg = 1; +res = ioctlsocket(sockr, FIONBIO, &arg); +#else +if (0 != fcntl(sockr, F_SETFL, O_NONBLOCK)) + { + strError = "fcntl error"; + codeError = IA_FCNTL_ERROR; + if (pErrorCb != NULL) + pErrorCb(messageText, IA_FCNTL_ERROR, errorCbData); + } +#endif + +} +//--------------------------------------------------------------------------- +IA_CLIENT_PROT::~IA_CLIENT_PROT() +{ +#ifndef WIN32 +close(sockr); +#else +closesocket(sockr); +WSACleanup(); +#endif +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::DeterminatePacketType(const char * buffer) +{ +map::iterator pi; +pi = packetTypes.find(buffer); +if (pi == packetTypes.end()) + { + return -1; + } +else + { + return pi->second; + } +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::FillHdr8(char * buffer, unsigned long) +{ +strncpy(buffer, IA_ID, 6); +buffer[IA_MAGIC_LEN] = 0; +buffer[IA_MAGIC_LEN + 1] = IA_PROTO_VER; +strncpy(buffer + sizeof(HDR_8), login.c_str(), IA_LOGIN_LEN); +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Send(char * buffer, int len) +{ +if (!isNetPrepared) + { + PrepareNet(); + isNetPrepared = true; + } + +// Øèôðóåì LoginS +int db = sizeof(HDR_8); +for (int i = 0; i < IA_LOGIN_LEN/8; i++) + { + Blowfish_Encrypt(&ctxHdr, (uint32_t*)(buffer + db + i*8), (uint32_t*)(buffer + db + i*8 + 4)); + } + +// Øèôðóåì âñ¸ îñòàëüíîå +db += IA_LOGIN_LEN; +int encLen = (len - sizeof(HDR_8) - IA_LOGIN_LEN)/8; +for (int i = 0; i < encLen; i++) + { + Blowfish_Encrypt(&ctxPass, (uint32_t*)(buffer + db), (uint32_t*)(buffer + db + 4)); + db += 8; + } + +return sendto(sockr, buffer, len, 0, (struct sockaddr*)&servAddr, sizeof(servAddr)); +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Recv(char * buffer, int len) +{ +#ifdef WIN32 +int fromLen; +#else +socklen_t fromLen; +#endif + +struct sockaddr_in addr; +fromLen = sizeof(addr); +int res = recvfrom(sockr, buffer, len, 0, (struct sockaddr*)&addr, &fromLen); + +if (res == -1) + return res; + +if (strcmp(buffer + 4 + sizeof(HDR_8), "ERR")) + { + for (int i = 0; i < len/8; i++) + Blowfish_Decrypt(&ctxPass, (uint32_t*)(buffer + i*8), (uint32_t*)(buffer + i*8 + 4)); + } + +return 0; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::NetSend(int n) +{ +char buffer[2048]; +int msgLen; + +memset(buffer, 0, 2048); + +switch (n) + { + case CONN_SYN_N: + msgLen = Prepare_CONN_SYN_8(buffer); + break; + + case CONN_ACK_N: + msgLen = Prepare_CONN_ACK_8(buffer); + break; + + case ALIVE_ACK_N: + msgLen = Prepare_ALIVE_ACK_8(buffer); + break; + + case DISCONN_SYN_N: + msgLen = Prepare_DISCONN_SYN_8(buffer); + break; + + case DISCONN_ACK_N: + msgLen = Prepare_DISCONN_ACK_8(buffer); + break; + + default: + return -1; + } + +FillHdr8(buffer, 0); +Send(buffer, msgLen); + +return 0; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::NetRecv() +{ +char buffer[2048]; + +if (Recv(buffer, sizeof(buffer)) < 0) + return -1; + +char packetName[20]; +strncpy(packetName, buffer + 12, sizeof(packetName)); +packetName[sizeof(packetName) - 1] = 0; +int pn = DeterminatePacketType(buffer + 12); + +int ret; +switch (pn) + { + case CONN_SYN_ACK_N: + ret = Process_CONN_SYN_ACK_8(buffer); + break; + + case ALIVE_SYN_N: + ret = Process_ALIVE_SYN_8(buffer); + break; + + case DISCONN_SYN_ACK_N: + ret = Process_DISCONN_SYN_ACK_8(buffer); + break; + + case FIN_N: + ret = Process_FIN_8(buffer); + break; + + case INFO_8_N: + ret = Process_INFO_8(buffer); + break; + + case ERROR_N: + ret = Process_ERROR(buffer); + break; + + default: + ret = -1; + } +return ret; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::Start() +{ +#ifdef WIN32 +unsigned long pt; +CreateThread(NULL, 16384, RunW, this, 0, &pt); +#else +pthread_create(&thread, NULL, RunL, this); +#endif +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::Stop() +{ +nonstop = false; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::Run() +{ +NetRecv(); + +switch (phase) + { + case 1: + if (action == IA_CONNECT) + { + action = IA_NONE; + NetSend(CONN_SYN_N); + phase = 2; + phaseTime = GetTickCount(); + } + if (reconnect && !firstConnect) + { + action = IA_CONNECT; + } + break; + + case 2: + if ((int)(GetTickCount() - phaseTime)/1000 > aliveTimeout) + { + phase = 1; + phaseTime = GetTickCount(); + if (pStatusChangedCb != NULL) + pStatusChangedCb(0, statusChangedCbData); + } + + if (action == IA_DISCONNECT) + { + action = IA_NONE; + NetSend(DISCONN_SYN_N); + phase = 4; + phaseTime = GetTickCount(); + } + + break; + + case 3: + if ((int)(GetTickCount() - phaseTime)/1000 > userTimeout) + { + phase = 1; + phaseTime = GetTickCount(); + if (pStatusChangedCb != NULL) + pStatusChangedCb(0, statusChangedCbData); + firstConnect = false; + } + + if (action == IA_DISCONNECT) + { + action = IA_NONE; + NetSend(DISCONN_SYN_N); + phase = 4; + phaseTime = GetTickCount(); + } + + break; + + case 4: + if ((int)(GetTickCount() - phaseTime)/1000 > aliveTimeout) + { + phase=1; + phaseTime = GetTickCount(); + if (pStatusChangedCb != NULL) + pStatusChangedCb(0, statusChangedCbData); + } + + if (action == IA_CONNECT) + { + action = IA_NONE; + NetSend(CONN_SYN_N); + phase = 2; + phaseTime = GetTickCount(); + } + + break; + + case 5: + if ((int)(GetTickCount() - phaseTime)/1000 > aliveTimeout) + { + phase = 1; + phaseTime = GetTickCount(); + if (pStatusChangedCb != NULL) + pStatusChangedCb(0, statusChangedCbData); + } + + if (action == IA_CONNECT) + { + action = IA_NONE; + NetSend(CONN_SYN_N); + phase = 2; + phaseTime = GetTickCount(); + } + + break; + } +Sleep(20); +return; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::GetStat(LOADSTAT * ls) +{ +memcpy(ls, &stat, sizeof(stat)); +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetServer(const string & sn, unsigned short p) +{ +serverName = sn; +port = p; +PrepareNet(); +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetLogin(const string & l) +{ +login = l; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetPassword(const string & p) +{ +password = p; + +unsigned char keyL[IA_PASSWD_LEN]; +memset(keyL, 0, IA_PASSWD_LEN); +strncpy((char *)keyL, password.c_str(), IA_PASSWD_LEN); +Blowfish_Init(&ctxPass, keyL, IA_PASSWD_LEN); +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetEnabledDirs(const bool * selectedDirs) +{ +memcpy(IA_CLIENT_PROT::selectedDirs, selectedDirs, sizeof(bool) * DIR_NUM); +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Connect() +{ +action = IA_CONNECT; +return 0; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Disconnect() +{ +firstConnect = true; +action = IA_DISCONNECT; +return 0; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::GetStrError(string * error) const +{ +int ret = codeError; +*error = strError; +strError = ""; +codeError = 0; +return ret; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Process_CONN_SYN_ACK_8(const char * buffer) +{ +vector dirNames; +connSynAck8 = (CONN_SYN_ACK_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(connSynAck8->len); +SwapBytes(connSynAck8->rnd); +SwapBytes(connSynAck8->userTimeOut); +SwapBytes(connSynAck8->aliveDelay); +#endif + +rnd = connSynAck8->rnd; +userTimeout = connSynAck8->userTimeOut; +aliveTimeout = connSynAck8->aliveDelay; + +for (int i = 0; i < DIR_NUM; i++) + { + dirNames.push_back((const char*)connSynAck8->dirName[i]); + } + +if (pDirNameCb != NULL) + pDirNameCb(dirNames, dirNameCbData); + +NetSend(CONN_ACK_N); +phase = 3; +phaseTime = GetTickCount(); + +return CONN_SYN_ACK_N; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Process_ALIVE_SYN_8(const char * buffer) +{ +aliveSyn8 = (ALIVE_SYN_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(aliveSyn8->len); +SwapBytes(aliveSyn8->rnd); +SwapBytes(aliveSyn8->cash); +SwapBytes(aliveSyn8->status); +for (int i = 0; i < DIR_NUM; ++i) + { + SwapBytes(aliveSyn8->mu[i]); + SwapBytes(aliveSyn8->md[i]); + SwapBytes(aliveSyn8->su[i]); + SwapBytes(aliveSyn8->sd[i]); + } +#endif + +rnd = aliveSyn8->rnd; +memcpy(&stat, (char*)aliveSyn8->mu, sizeof(stat)); + +if (pStatChangedCb != NULL) + pStatChangedCb(stat, statChangedCbData); + +if (pStatusChangedCb != NULL) + pStatusChangedCb(1, statusChangedCbData); +NetSend(ALIVE_ACK_N); +phaseTime = GetTickCount(); + +return ALIVE_SYN_N; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Process_DISCONN_SYN_ACK_8(const char * buffer) +{ +disconnSynAck8 = (DISCONN_SYN_ACK_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(disconnSynAck8->len); +SwapBytes(disconnSynAck8->rnd); +#endif + +rnd = disconnSynAck8->rnd; + +NetSend(DISCONN_ACK_N); +phase = 5; +phaseTime = GetTickCount(); + +return DISCONN_SYN_ACK_N; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Process_FIN_8(const char *) +{ +phase = 1; +phaseTime = GetTickCount(); +if (pStatusChangedCb != NULL) + pStatusChangedCb(0, statusChangedCbData); + +return FIN_N; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Process_INFO_8(const char * buffer) +{ +info = (INFO_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(info->len); +SwapBytes(info->sendTime); +#endif + +if (pInfoCb != NULL) + pInfoCb((char*)info->text, info->infoType, info->showTime, info->sendTime, infoCbData); +return INFO_8_N; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Process_ERROR(const char * buffer) +{ +memcpy(&err, buffer, sizeof(err)); + +#ifdef ARCH_BE +SwapBytes(err.len); +#endif + +KOIToWin((const char*)err.text, &messageText); +if (pErrorCb != NULL) + pErrorCb(messageText, IA_SERVER_ERROR, errorCbData); +phase = 1; +phaseTime = GetTickCount(); +codeError = IA_SERVER_ERROR; + +return ERROR_N; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Prepare_CONN_SYN_8(char * buffer) +{ +connSyn8 = (CONN_SYN_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(connSyn8->len); +#endif + +connSyn8->len = sizeof(CONN_SYN_8); +#ifdef IA_DEBUGPROTO +if (sizeof(CONN_SYN_8) != Min8(sizeof(CONN_SYN_8))) + { + int * a = NULL; + *a = 0; + } +#endif + +strncpy((char*)connSyn8->type, "CONN_SYN", IA_MAX_TYPE_LEN); +strncpy((char*)connSyn8->login, login.c_str(), IA_LOGIN_LEN); +connSyn8->dirs = 0; +for (int i = 0; i < DIR_NUM; i++) + { + connSyn8->dirs |= (selectedDirs[i] << i); + } +return connSyn8->len; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Prepare_CONN_ACK_8(char * buffer) +{ +connAck8 = (CONN_ACK_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(connAck8->len); +SwapBytes(connAck8->rnd); +#endif + +#ifdef IA_DEBUGPROTO +if (sizeof(CONN_ACK_8) != Min8(sizeof(CONN_ACK_8))) + { + int * a = NULL; + *a = 0; + } +#endif + +connAck8->len = sizeof(CONN_ACK_8); +strncpy((char*)connAck8->loginS, login.c_str(), IA_LOGIN_LEN); +strncpy((char*)connAck8->type, "CONN_ACK", IA_MAX_TYPE_LEN); +rnd++; +connAck8->rnd = rnd; + +return connAck8->len; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Prepare_ALIVE_ACK_8(char * buffer) +{ +aliveAck8 = (ALIVE_ACK_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(aliveAck8->len); +SwapBytes(aliveAck8->rnd); +#endif + +#ifdef IA_DEBUGPROTO +if (Min8(sizeof(ALIVE_ACK_8)) != sizeof(ALIVE_ACK_8)) + { + int * a = NULL; + *a = 0; + } +#endif + +aliveAck8 = (ALIVE_ACK_8*)buffer; +aliveAck8->len = sizeof(ALIVE_ACK_8); +strncpy((char*)aliveAck8->loginS, login.c_str(), IA_LOGIN_LEN); +strncpy((char*)aliveAck8->type, "ALIVE_ACK", IA_MAX_TYPE_LEN); +aliveAck8->rnd = ++rnd; +return aliveAck8->len; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Prepare_DISCONN_SYN_8(char * buffer) +{ +disconnSyn8 = (DISCONN_SYN_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(disconnSyn8->len); +#endif + +#ifdef IA_DEBUGPROTO +if (Min8(sizeof(DISCONN_SYN_8)) != sizeof(DISCONN_SYN_8)) + { + int * a = NULL; + *a = 0; + } +#endif + +disconnSyn8->len = sizeof(DISCONN_SYN_8); +strncpy((char*)disconnSyn8->loginS, login.c_str(), IA_LOGIN_LEN); +strncpy((char*)disconnSyn8->type, "DISCONN_SYN", IA_MAX_TYPE_LEN); +strncpy((char*)disconnSyn8->login, login.c_str(), IA_LOGIN_LEN); +return disconnSyn8->len; +} +//--------------------------------------------------------------------------- +int IA_CLIENT_PROT::Prepare_DISCONN_ACK_8(char * buffer) +{ +disconnAck8 = (DISCONN_ACK_8*)buffer; + +#ifdef ARCH_BE +SwapBytes(disconnAck8->len); +SwapBytes(disconnAck8->rnd); +#endif + +#ifdef IA_DEBUGPROTO +if (Min8(sizeof(DISCONN_ACK_8)) != sizeof(DISCONN_ACK_8)) + { + int * a = NULL; + *a = 0; + } +#endif + +disconnAck8->len = Min8(sizeof(DISCONN_ACK_8)); +disconnAck8->rnd = rnd + 1; +strncpy((char*)disconnAck8->loginS, login.c_str(), IA_LOGIN_LEN); +strncpy((char*)disconnAck8->type, "DISCONN_ACK", IA_MAX_TYPE_LEN); +return disconnAck8->len; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetStatusChangedCb(tpStatusChangedCb p, void * data) +{ +pStatusChangedCb = p; +statusChangedCbData = data; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetStatChangedCb(tpStatChangedCb p, void * data) +{ +pStatChangedCb = p; +statChangedCbData = data; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetInfoCb(tpCallBackInfoFn p, void * data) +{ +pInfoCb = p; +infoCbData = data; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetDirNameCb(tpCallBackDirNameFn p, void * data) +{ +pDirNameCb = p; +dirNameCbData = data; +} +//--------------------------------------------------------------------------- +void IA_CLIENT_PROT::SetErrorCb(tpCallBackErrorFn p, void * data) +{ +pErrorCb = p; +errorCbData = data; +} +//--------------------------------------------------------------------------- diff --git a/stglibs/ia_auth_c.lib/ia_auth_c.h b/stglibs/ia_auth_c.lib/ia_auth_c.h new file mode 100644 index 00000000..2e88cf76 --- /dev/null +++ b/stglibs/ia_auth_c.lib/ia_auth_c.h @@ -0,0 +1,209 @@ +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 1, or (at your option) +** any later version. + +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. + +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + $Author: faust $ + $Revision: 1.10 $ + $Date: 2010/03/15 12:57:24 $ +*/ + +/* +* Author : Boris Mikhailenko +*/ +//--------------------------------------------------------------------------- +#ifndef IA_AUTH_C_H +#define IA_AUTH_C_H + +#ifndef WIN32 +#include +#include +#else +#include +#endif + +#include +#include +#include + +#include "blowfish.h" +#include "ia_packets.h" + +#define IA_BIND_ERROR (1) +#define IA_SERVER_ERROR (2) +#define IA_FCNTL_ERROR (3) +#define IA_GETHOSTBYNAME_ERROR (4) + +#define IA_PROTO_VER (8) +#define IA_PROTO_PROXY_VER (101) + +using namespace std; + +typedef void (*tpStatusChangedCb)(int status, void * data); +typedef void (*tpStatChangedCb)(const LOADSTAT & stat, void * data); +typedef void (*tpCallBackInfoFn)(const string & message, int infoType, int showTime, int sendTime, void * data); +typedef void (*tpCallBackErrorFn)(const string & message, int netError, void * data); +typedef void (*tpCallBackDirNameFn)(const vector & dirName, void * data); + +//--------------------------------------------------------------------------- +class IA_CLIENT_PROT +{ +#ifdef WIN32 +friend unsigned long WINAPI RunW(void * data); +#else +friend void * RunL(void * data); +#endif + +public: + IA_CLIENT_PROT(const string & sn, uint16_t p, uint16_t localPort = 0); + ~IA_CLIENT_PROT(); + + void Start(); + void Stop(); + void GetStat(LOADSTAT * ls); + + void SetServer(const string & sn, unsigned short port); + void SetLogin(const string & login); + void SetPassword(const string & password); + void SetEnabledDirs(const bool * selectedDirs); + + void SetStatusChangedCb(tpStatusChangedCb p, void * data); + void SetStatChangedCb(tpStatChangedCb p, void * data); + void SetInfoCb(tpCallBackInfoFn p, void * data); + void SetErrorCb(tpCallBackErrorFn p, void * data); + void SetDirNameCb(tpCallBackDirNameFn p, void * data); + + int Connect(); + int Disconnect(); + int GetAuthorized() const { return phase == 3 || phase == 4; }; // Ìû ïîäêëþ÷åíû èëè íåò? + int GetPhase() const { return phase; }; + int GetStatus() const; + int GetReconnect() const { return reconnect; }; + void SetReconnect(int r) { reconnect = r; }; + char GetProtoVer() const { return proxyMode ? IA_PROTO_PROXY_VER : IA_PROTO_VER; }; + void GetMessageText(string * text) const { *text = messageText; }; + void GetInfoText(string * text) const { *text = infoText; }; + int GetStrError(string * error) const; + + void SetProxyMode(bool on) { proxyMode = on; }; + bool GetProxyMode() const { return proxyMode; }; + + void SetIP(uint32_t ip) { IA_CLIENT_PROT::ip = ip; }; + uint32_t GetIP() const { return ip; }; + +private: + void Run(); + int NetRecv(); + int NetSend(int n); + bool GetNonstop() const { return nonstop; }; + void PrepareNet(); + int DeterminatePacketType(const char * buffer); + + int Process_CONN_SYN_ACK_8(const char * buffer); + int Process_ALIVE_SYN_8(const char * buffer); + int Process_DISCONN_SYN_ACK_8(const char * buffer); + int Process_FIN_8(const char * buffer); + int Process_INFO_8(const char * buffer); + int Process_ERROR(const char * buffer); + + int Prepare_CONN_SYN_8(char * buffer); + int Prepare_CONN_ACK_8(char * buffer); + int Prepare_ALIVE_ACK_8(char * buffer); + int Prepare_DISCONN_SYN_8(char * buffer); + int Prepare_DISCONN_ACK_8(char * buffer); + + void FillHdr8(char * buffer, unsigned long ip); + int Send(char * buffer, int len); + int Recv(char * buffer, int len); + + LOADSTAT stat; + int action; + int phase; + int phaseTime; // Âðåìÿ âõîäà â ôàçó + string messageText; // Ñîîáùåíèå îá îøèáêå + string infoText; + mutable string strError; + mutable int codeError; + bool nonstop; + bool isNetPrepared; + bool proxyMode; + + BLOWFISH_CTX ctxPass; + BLOWFISH_CTX ctxHdr; + + bool selectedDirs[DIR_NUM]; + + string password; + string login; + + #ifdef WIN32 + WSADATA wsaData; + #else + pthread_t thread; + #endif + + string serverName; // Èìÿ ñåðâåðà + uint16_t port; // Ïîðò ñåðâåðà + uint32_t ip; // Proxy IP + uint32_t localPort; + + struct sockaddr_in localAddrS; // Íàø àäðåñ + struct sockaddr_in localAddrR; // Íàø àäðåñ + struct sockaddr_in servAddr; // àäðåñ ñåðâåðà + + bool firstConnect; + int reconnect; + int sockr; + int protNum; // ×èñëî, êîòîðîå ó÷àñòâóåò â îáìåíå ñîîáùåíèÿìèa + int userTimeout; + int aliveTimeout; + unsigned int rnd; + + tpStatusChangedCb pStatusChangedCb; + tpStatChangedCb pStatChangedCb; + tpCallBackInfoFn pInfoCb; + tpCallBackErrorFn pErrorCb; + tpCallBackDirNameFn pDirNameCb; + + void * statusChangedCbData; + void * statChangedCbData; + void * infoCbData; + void * errorCbData; + void * dirNameCbData; + + map packetTypes; + + CONN_SYN_8 * connSyn8; + CONN_SYN_ACK_8 * connSynAck8; + CONN_ACK_8 * connAck8; + ALIVE_SYN_8 * aliveSyn8; + ALIVE_ACK_8 * aliveAck8; + DISCONN_SYN_8 * disconnSyn8; + DISCONN_SYN_ACK_8 * disconnSynAck8; + DISCONN_ACK_8 * disconnAck8; + ERR_8 err; + INFO_8 * info; +}; +//--------------------------------------------------------------------------- +#ifdef WIN32 +unsigned long WINAPI RunW(void *); +#else +void * RunW(void *); +#endif + +//--------------------------------------------------------------------------- +#endif //IA_AUTH_C_H + + diff --git a/stglibs/ibpp.lib/Makefile b/stglibs/ibpp.lib/Makefile new file mode 100644 index 00000000..0c605edc --- /dev/null +++ b/stglibs/ibpp.lib/Makefile @@ -0,0 +1,37 @@ +############################################################################### +# $Id: Makefile,v 1.6 2009/03/03 15:50:15 faust Exp $ +############################################################################### + +LIB_NAME = ibpp +PROG = lib$(LIB_NAME) + +SRCS = array.cpp \ + blob.cpp \ + database.cpp \ + date.cpp \ + dbkey.cpp \ + _dpb.cpp \ + events.cpp \ + exception.cpp \ + _ibpp.cpp \ + _ibs.cpp \ + _rb.cpp \ + row.cpp \ + service.cpp \ + _spb.cpp \ + statement.cpp \ + time.cpp \ + _tpb.cpp \ + transaction.cpp \ + user.cpp + +INCS = ibpp.h + +ADD_CXXFLAGS_1 = -DIBPP_LINUX + +LIBS = -lfbclient + +include ../Makefile.in + + + diff --git a/stglibs/ibpp.lib/_dpb.cpp b/stglibs/ibpp.lib/_dpb.cpp new file mode 100644 index 00000000..4729cd3c --- /dev/null +++ b/stglibs/ibpp.lib/_dpb.cpp @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: _dpb.cpp,v 1.2 2009/03/19 20:00:27 faust Exp $ +// Subject : IBPP, internal DPB class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * DPB == Database Parameter Block/Buffer, see Interbase 6.0 C-API +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +const int DPB::BUFFERINCR = 128; + +void DPB::Grow(int needed) +{ + if (mBuffer == 0) ++needed; // Initial alloc will require one more byte + if ((mSize + needed) > mAlloc) + { + // We need to grow the buffer. We use increments of BUFFERINCR bytes. + needed = (needed / BUFFERINCR + 1) * BUFFERINCR; + char* newbuffer = new char[mAlloc + needed]; + if (mBuffer == 0) + { + // Initial allocation, initialize the version tag + newbuffer[0] = isc_dpb_version1; + mSize = 1; + } + else + { + // Move the old buffer content to the new one + memcpy(newbuffer, mBuffer, mSize); + delete [] mBuffer; + } + mBuffer = newbuffer; + mAlloc += needed; + } +} + +void DPB::Insert(char type, const char* data) +{ + int len = (int)strlen(data); + Grow(len + 2); + mBuffer[mSize++] = type; + mBuffer[mSize++] = char(len); + strncpy(&mBuffer[mSize], data, len); + mSize += len; +} + +void DPB::Insert(char type, int16_t data) +{ + Grow(2 + 2); + mBuffer[mSize++] = type; + mBuffer[mSize++] = char(2); + *(int16_t*)&mBuffer[mSize] = int16_t((*gds.Call()->m_vax_integer)((char*)&data, 2)); + mSize += 2; +} + +void DPB::Insert(char type, bool data) +{ + Grow(2 + 1); + mBuffer[mSize++] = type; + mBuffer[mSize++] = char(1); + mBuffer[mSize++] = char(data ? 1 : 0); +} + +void DPB::Insert(char type, char data) +{ + Grow(2 + 1); + mBuffer[mSize++] = type; + mBuffer[mSize++] = char(1); + mBuffer[mSize++] = data; +} + +void DPB::Reset() +{ + if (mAlloc != 0) + { + delete [] mBuffer; + mBuffer = 0; + mSize = 0; + mAlloc = 0; + } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/_ibpp.cpp b/stglibs/ibpp.lib/_ibpp.cpp new file mode 100644 index 00000000..3374b966 --- /dev/null +++ b/stglibs/ibpp.lib/_ibpp.cpp @@ -0,0 +1,367 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: _ibpp.cpp,v 1.3 2009/03/19 20:00:27 faust Exp $ +// Subject : IBPP, Initialization of the library +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +#ifdef IBPP_WINDOWS +// New (optional) Registry Keys introduced by Firebird Server 1.5 +#define REG_KEY_ROOT_INSTANCES "SOFTWARE\\Firebird Project\\Firebird Server\\Instances" +#define FB_DEFAULT_INSTANCE "DefaultInstance" +#endif + +namespace ibpp_internals +{ + const double consts::dscales[19] = { + 1, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, + 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, + 1E16, 1E17, 1E18 }; + + const int consts::Dec31_1899 = 693595; + +// Many compilers confuses those following min/max with macros min and max ! +#undef min +#undef max + +#ifdef __DMC__ // Needs to break-down the declaration else compiler crash (!) + const std::numeric_limits i16_limits; + const std::numeric_limits i32_limits; + const int16_t consts::min16 = i16_limits.min(); + const int16_t consts::max16 = i16_limits.max(); + const int32_t consts::min32 = i32_limits.min(); + const int32_t consts::max32 = i32_limits.max(); +#else + const int16_t consts::min16 = std::numeric_limits::min(); + const int16_t consts::max16 = std::numeric_limits::max(); + const int32_t consts::min32 = std::numeric_limits::min(); + const int32_t consts::max32 = std::numeric_limits::max(); +#endif + + GDS gds; // Global unique GDS instance + +#ifdef IBPP_WINDOWS + std::string AppPath; // Used by GDS::Call() below +#endif + +#ifdef _DEBUG + std::ostream& operator<< (std::ostream& a, flush_debug_stream_type) + { + if (std::stringstream* p = dynamic_cast(&a)) + { +#ifdef IBPP_WINDOWS + ::OutputDebugString(("IBPP: " + p->str() + "\n").c_str()); +#endif + p->str(""); + } + return a; + } +#endif // _DEBUG + +} + +using namespace ibpp_internals; + +GDS* GDS::Call() +{ + // Let's load the CLIENT library, if it is not already loaded. + // The load is guaranteed to be done only once per application. + + if (! mReady) + { +#ifdef IBPP_WINDOWS + + // Let's load the FBCLIENT.DLL or GDS32.DLL, we will never release it. + // Windows will do that for us when the executable will terminate. + + char fbdll[MAX_PATH]; + HKEY hkey_instances; + + // Try to load FBCLIENT.DLL from each of the additional optional paths + // that may have been specified through ClientLibSearchPaths(). + // We also want to actually update the environment PATH so that it references + // the specific path from where we attempt the load. This is useful because + // it directs the system to attempt finding dependencies (like the C/C++ + // runtime libraries) from the same location where FBCLIENT is found. + + mHandle = 0; + + std::string SysPath(getenv("PATH")); + std::string::size_type pos = 0; + while (pos < mSearchPaths.size()) + { + std::string::size_type newpos = mSearchPaths.find(';', pos); + + std::string path; + if (newpos == std::string::npos) path = mSearchPaths.substr(pos); + else path = mSearchPaths.substr(pos, newpos-pos); + + if (path.size() >= 1) + { + if (path[path.size()-1] != '\\') path += '\\'; + + AppPath.assign("PATH="); + AppPath.append(path).append(";").append(SysPath); + putenv(AppPath.c_str()); + + path.append("fbclient.dll"); + mHandle = LoadLibrary(path.c_str()); + if (mHandle != 0 || newpos == std::string::npos) break; + } + pos = newpos + 1; + } + + if (mHandle == 0) + { + // Try to load FBCLIENT.DLL from the current application location. This + // is a usefull step for applications using the embedded version of FB + // or a local copy (for whatever reasons) of the dll. + + if (! AppPath.empty()) + { + // Restores the original system path + AppPath.assign("PATH="); + AppPath.append(SysPath); + putenv(AppPath.c_str()); + } + + int len = GetModuleFileName(NULL, fbdll, sizeof(fbdll)); + if (len != 0) + { + // Get to the last '\' (this one precedes the filename part). + // There is always one after a success call to GetModuleFileName(). + char* p = fbdll + len; + do {--p;} while (*p != '\\'); + *p = '\0'; + lstrcat(fbdll, "\\fbembed.dll");// Local copy could be named fbembed.dll + mHandle = LoadLibrary(fbdll); + if (mHandle == 0) + { + *p = '\0'; + lstrcat(fbdll, "\\fbclient.dll"); // Or possibly renamed fbclient.dll + mHandle = LoadLibrary(fbdll); + } + } + } + + if (mHandle == 0) + { + // Try to locate FBCLIENT.DLL through the optional FB registry key. + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_KEY_ROOT_INSTANCES, 0, + KEY_READ, &hkey_instances) == ERROR_SUCCESS) + { + DWORD keytype; + DWORD buflen = sizeof(fbdll); + if (RegQueryValueEx(hkey_instances, FB_DEFAULT_INSTANCE, 0, + &keytype, reinterpret_cast(fbdll), + &buflen) == ERROR_SUCCESS && keytype == REG_SZ) + { + lstrcat(fbdll, "bin\\fbclient.dll"); + mHandle = LoadLibrary(fbdll); + } + RegCloseKey(hkey_instances); + } + } + + if (mHandle == 0) + { + // Let's try from the PATH and System directories + mHandle = LoadLibrary("fbclient.dll"); + if (mHandle == 0) + { + // Not found. Last try : attemps loading gds32.dll from PATH and + // System directories + mHandle = LoadLibrary("gds32.dll"); + if (mHandle == 0) + throw LogicExceptionImpl("GDS::Call()", + _("Can't find or load FBCLIENT.DLL or GDS32.DLL")); + } + } +#endif + + mGDSVersion = 60; + + // Get the entry points that we need + +#ifdef IBPP_WINDOWS +#define IB_ENTRYPOINT(X) \ + if ((m_##X = (proto_##X*)GetProcAddress(mHandle, "isc_"#X)) == 0) \ + throw LogicExceptionImpl("GDS:gds()", _("Entry-point isc_"#X" not found")) +#endif +#ifdef IBPP_UNIX +/* TODO : perform a late-bind on unix --- not so important, well I think (OM) */ +#define IB_ENTRYPOINT(X) m_##X = (proto_##X*)isc_##X +#endif + + IB_ENTRYPOINT(create_database); + IB_ENTRYPOINT(attach_database); + IB_ENTRYPOINT(detach_database); + IB_ENTRYPOINT(drop_database); + IB_ENTRYPOINT(database_info); + IB_ENTRYPOINT(open_blob2); + IB_ENTRYPOINT(create_blob2); + IB_ENTRYPOINT(close_blob); + IB_ENTRYPOINT(cancel_blob); + IB_ENTRYPOINT(get_segment); + IB_ENTRYPOINT(put_segment); + IB_ENTRYPOINT(blob_info); + IB_ENTRYPOINT(array_lookup_bounds); + IB_ENTRYPOINT(array_get_slice); + IB_ENTRYPOINT(array_put_slice); + IB_ENTRYPOINT(vax_integer); + IB_ENTRYPOINT(sqlcode); + IB_ENTRYPOINT(sql_interprete); + IB_ENTRYPOINT(interprete); + IB_ENTRYPOINT(que_events); + IB_ENTRYPOINT(cancel_events); + IB_ENTRYPOINT(start_multiple); + IB_ENTRYPOINT(commit_transaction); + IB_ENTRYPOINT(commit_retaining); + IB_ENTRYPOINT(rollback_transaction); + IB_ENTRYPOINT(rollback_retaining); + IB_ENTRYPOINT(dsql_execute_immediate); + IB_ENTRYPOINT(dsql_allocate_statement); + IB_ENTRYPOINT(dsql_describe); + IB_ENTRYPOINT(dsql_describe_bind); + IB_ENTRYPOINT(dsql_prepare); + IB_ENTRYPOINT(dsql_execute); + IB_ENTRYPOINT(dsql_execute2); + IB_ENTRYPOINT(dsql_fetch); + IB_ENTRYPOINT(dsql_free_statement); + IB_ENTRYPOINT(dsql_set_cursor_name); + IB_ENTRYPOINT(dsql_sql_info); + + IB_ENTRYPOINT(service_attach); + IB_ENTRYPOINT(service_detach); + IB_ENTRYPOINT(service_start); + IB_ENTRYPOINT(service_query); + + mReady = true; + } + + return this; +} + +namespace IBPP +{ + + bool CheckVersion(uint32_t AppVersion) + { + //(void)gds.Call(); // Just call it to trigger the initialization + return (AppVersion & 0xFFFFFF00) == + (IBPP::Version & 0xFFFFFF00) ? true : false; + } + + int GDSVersion() + { + return gds.Call()->mGDSVersion; + } + +#ifdef IBPP_WINDOWS + void ClientLibSearchPaths(const std::string& paths) + { + gds.mSearchPaths.assign(paths); + } +#else + void ClientLibSearchPaths(const std::string&) + { + } +#endif + + // Factories for our Interface objects + + Service ServiceFactory(const std::string& ServerName, + const std::string& UserName, const std::string& UserPassword) + { + (void)gds.Call(); // Triggers the initialization, if needed + return new ServiceImpl(ServerName, UserName, UserPassword); + } + + Database DatabaseFactory(const std::string& ServerName, + const std::string& DatabaseName, const std::string& UserName, + const std::string& UserPassword, const std::string& RoleName, + const std::string& CharSet, const std::string& CreateParams) + { + (void)gds.Call(); // Triggers the initialization, if needed + return new DatabaseImpl(ServerName, DatabaseName, UserName, + UserPassword, RoleName, CharSet, CreateParams); + } + + Transaction TransactionFactory(Database db, TAM am, + TIL il, TLR lr, TFF flags) + { + (void)gds.Call(); // Triggers the initialization, if needed + return new TransactionImpl( dynamic_cast(db.intf()), + am, il, lr, flags); + } + + Statement StatementFactory(Database db, Transaction tr, + const std::string& sql) + { + (void)gds.Call(); // Triggers the initialization, if needed + return new StatementImpl( dynamic_cast(db.intf()), + dynamic_cast(tr.intf()), + sql); + } + + Blob BlobFactory(Database db, Transaction tr) + { + (void)gds.Call(); // Triggers the initialization, if needed + return new BlobImpl(dynamic_cast(db.intf()), + dynamic_cast(tr.intf())); + } + + Array ArrayFactory(Database db, Transaction tr) + { + (void)gds.Call(); // Triggers the initialization, if needed + return new ArrayImpl(dynamic_cast(db.intf()), + dynamic_cast(tr.intf())); + } + + Events EventsFactory(Database db) + { + (void)gds.Call(); // Triggers the initialization, if needed + return new EventsImpl(dynamic_cast(db.intf())); + } + +} + +// +// EOF +// + diff --git a/stglibs/ibpp.lib/_ibpp.h b/stglibs/ibpp.lib/_ibpp.h new file mode 100644 index 00000000..e7af2eaf --- /dev/null +++ b/stglibs/ibpp.lib/_ibpp.h @@ -0,0 +1,1414 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: _ibpp.h,v 1.2 2007/05/17 08:37:05 faust Exp $ +// Subject : IBPP internal declarations +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// +// * 'Internal declarations' means everything used to implement ibpp. This +// file and its contents is NOT needed by users of the library. All those +// declarations are wrapped in a namespace : 'ibpp_internals'. +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __INTERNAL_IBPP_H__ +#define __INTERNAL_IBPP_H__ + +#include "ibpp.h" + +#if defined(__BCPLUSPLUS__) || defined(_MSC_VER) || defined(__DMC__) +#define HAS_HDRSTOP +#endif + +#if (defined(__GNUC__) && defined(IBPP_WINDOWS)) +// Setting flags for ibase.h -- using GCC/Cygwin/MinGW on Win32 +#ifndef _MSC_VER +#define _MSC_VER 1299 +#endif +#ifndef _WIN32 +#define _WIN32 1 +#endif +#endif + +#include "ibase.h" // From Firebird 1.x or InterBase 6.x installation + +#if (defined(__GNUC__) && defined(IBPP_WINDOWS)) +// UNSETTING flags used above for ibase.h -- Huge conflicts with libstdc++ ! +#undef _MSC_VER +#undef _WIN32 +#endif + +#ifdef IBPP_WINDOWS +#include +#endif + +//#include +#include +#include +#include +#include + +#ifdef _DEBUG +#define ASSERTION(x) {if (!(x)) {throw LogicExceptionImpl("ASSERTION", \ + "'"#x"' is not verified at %s, line %d", \ + __FILE__, __LINE__);}} +#else +#define ASSERTION(x) /* x */ +#endif + +// Fix to famous MSVC 6 variable scope bug +#if defined(_MSC_VER) && (_MSC_VER < 1300) // MSVC 6 should be < 1300 +#define for if(true)for +#endif + +namespace ibpp_internals +{ + +enum flush_debug_stream_type {fds}; + +#ifdef _DEBUG + +struct DebugStream : public std::stringstream +{ + // next two operators fix some g++ and vc++ related problems + std::ostream& operator<< (const char* p) + { static_cast(*this)<< p; return *this; } + + std::ostream& operator<< (const std::string& p) + { static_cast(*this)<< p; return *this; } + + DebugStream& operator=(const DebugStream&) {return *this;} + DebugStream(const DebugStream&) {} + DebugStream() {} +}; +std::ostream& operator<< (std::ostream& a, flush_debug_stream_type); + +#else + +struct DebugStream +{ + template DebugStream& operator<< (const T&) { return *this; } + // for manipulators + DebugStream& operator<< (std::ostream&(*)(std::ostream&)) { return *this; } +}; + +#endif // _DEBUG + +class DatabaseImpl; +class TransactionImpl; +class StatementImpl; +class BlobImpl; +class ArrayImpl; +class EventsImpl; + +// Native data types +typedef enum {ivArray, ivBlob, ivDate, ivTime, ivTimestamp, ivString, + ivInt16, ivInt32, ivInt64, ivFloat, ivDouble, + ivBool, ivDBKey, ivByte} IITYPE; + +// +// Those are the Interbase C API prototypes that we use +// Taken 'asis' from IBASE.H, prefix 'isc_' replaced with 'proto_', +// and 'typedef' preprended... +// + +typedef ISC_STATUS ISC_EXPORT proto_create_database (ISC_STATUS *, + short, + char *, + isc_db_handle *, + short, + char *, + short); + +typedef ISC_STATUS ISC_EXPORT proto_attach_database (ISC_STATUS *, + short, + char *, + isc_db_handle *, + short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_detach_database (ISC_STATUS *, + isc_db_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_drop_database (ISC_STATUS *, + isc_db_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_database_info (ISC_STATUS *, + isc_db_handle *, + short, + char *, + short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_execute_immediate (ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned short, + char *, + unsigned short, + XSQLDA *); + +typedef ISC_STATUS ISC_EXPORT proto_open_blob2 (ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + isc_blob_handle *, + ISC_QUAD *, + short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_create_blob2 (ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + isc_blob_handle *, + ISC_QUAD *, + short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_close_blob (ISC_STATUS *, + isc_blob_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_cancel_blob (ISC_STATUS *, + isc_blob_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_get_segment (ISC_STATUS *, + isc_blob_handle *, + unsigned short *, + unsigned short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_put_segment (ISC_STATUS *, + isc_blob_handle *, + unsigned short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_blob_info (ISC_STATUS *, + isc_blob_handle *, + short, + char *, + short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_array_lookup_bounds (ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + char *, + char *, + ISC_ARRAY_DESC *); + +typedef ISC_STATUS ISC_EXPORT proto_array_get_slice (ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + ISC_QUAD *, + ISC_ARRAY_DESC *, + void *, + ISC_LONG *); + +typedef ISC_STATUS ISC_EXPORT proto_array_put_slice (ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + ISC_QUAD *, + ISC_ARRAY_DESC *, + void *, + ISC_LONG *); + +typedef ISC_LONG ISC_EXPORT proto_vax_integer (char *, + short); + +typedef ISC_LONG ISC_EXPORT proto_sqlcode (ISC_STATUS *); + +typedef void ISC_EXPORT proto_sql_interprete (short, + char *, + short); + +typedef ISC_STATUS ISC_EXPORT proto_interprete (char *, + ISC_STATUS * *); + +typedef ISC_STATUS ISC_EXPORT proto_que_events (ISC_STATUS *, + isc_db_handle *, + ISC_LONG *, + short, + char *, + isc_callback, + void *); + +typedef ISC_STATUS ISC_EXPORT proto_cancel_events (ISC_STATUS *, + isc_db_handle *, + ISC_LONG *); + +typedef ISC_STATUS ISC_EXPORT proto_start_multiple (ISC_STATUS *, + isc_tr_handle *, + short, + void *); + +typedef ISC_STATUS ISC_EXPORT proto_commit_transaction (ISC_STATUS *, + isc_tr_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_commit_retaining (ISC_STATUS *, + isc_tr_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_rollback_transaction (ISC_STATUS *, + isc_tr_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_rollback_retaining (ISC_STATUS *, + isc_tr_handle *); + +/////////// +typedef ISC_STATUS ISC_EXPORT proto_dsql_allocate_statement (ISC_STATUS *, + isc_db_handle *, + isc_stmt_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_describe (ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_describe_bind (ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_execute (ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_execute2 (ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + XSQLDA *, + XSQLDA *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_fetch (ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_free_statement (ISC_STATUS *, + isc_stmt_handle *, + unsigned short); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_prepare (ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + char *, + unsigned short, + XSQLDA *); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_set_cursor_name (ISC_STATUS *, + isc_stmt_handle *, + char *, + unsigned short); + +typedef ISC_STATUS ISC_EXPORT proto_dsql_sql_info (ISC_STATUS *, + isc_stmt_handle *, + short, + char *, + short, + char *); + +typedef void ISC_EXPORT proto_decode_date (ISC_QUAD *, + void *); + +typedef void ISC_EXPORT proto_encode_date (void *, + ISC_QUAD *); + +typedef int ISC_EXPORT proto_add_user (ISC_STATUS *, USER_SEC_DATA *); +typedef int ISC_EXPORT proto_delete_user (ISC_STATUS *, USER_SEC_DATA *); +typedef int ISC_EXPORT proto_modify_user (ISC_STATUS *, USER_SEC_DATA *); + +// +// Those API are only available in versions 6.x of the GDS32.DLL +// + +typedef ISC_STATUS ISC_EXPORT proto_service_attach (ISC_STATUS *, + unsigned short, + char *, + isc_svc_handle *, + unsigned short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_service_detach (ISC_STATUS *, + isc_svc_handle *); + +typedef ISC_STATUS ISC_EXPORT proto_service_query (ISC_STATUS *, + isc_svc_handle *, + isc_resv_handle *, + unsigned short, + char *, + unsigned short, + char *, + unsigned short, + char *); + +typedef ISC_STATUS ISC_EXPORT proto_service_start (ISC_STATUS *, + isc_svc_handle *, + isc_resv_handle *, + unsigned short, + char*); + +typedef void ISC_EXPORT proto_decode_sql_date (ISC_DATE *, + void *); + +typedef void ISC_EXPORT proto_decode_sql_time (ISC_TIME *, + void *); + +typedef void ISC_EXPORT proto_decode_timestamp (ISC_TIMESTAMP *, + void *); + +typedef void ISC_EXPORT proto_encode_sql_date (void *, + ISC_DATE *); + +typedef void ISC_EXPORT proto_encode_sql_time (void *, + ISC_TIME *); + +typedef void ISC_EXPORT proto_encode_timestamp (void *, + ISC_TIMESTAMP *); + +// +// Internal binding structure to the GDS32 DLL +// + +struct GDS +{ + // Attributes + bool mReady; + int mGDSVersion; // Version of the GDS32.DLL (50 for 5.0, 60 for 6.0) + +#ifdef IBPP_WINDOWS + HMODULE mHandle; // The GDS32.DLL HMODULE + std::string mSearchPaths; // Optional additional search paths +#endif + + GDS* Call(); + + // GDS32 Entry Points + proto_create_database* m_create_database; + proto_attach_database* m_attach_database; + proto_detach_database* m_detach_database; + proto_drop_database* m_drop_database; + proto_database_info* m_database_info; + proto_dsql_execute_immediate* m_dsql_execute_immediate; + proto_open_blob2* m_open_blob2; + proto_create_blob2* m_create_blob2; + proto_close_blob* m_close_blob; + proto_cancel_blob* m_cancel_blob; + proto_get_segment* m_get_segment; + proto_put_segment* m_put_segment; + proto_blob_info* m_blob_info; + proto_array_lookup_bounds* m_array_lookup_bounds; + proto_array_get_slice* m_array_get_slice; + proto_array_put_slice* m_array_put_slice; + + proto_vax_integer* m_vax_integer; + proto_sqlcode* m_sqlcode; + proto_sql_interprete* m_sql_interprete; + proto_interprete* m_interprete; + proto_que_events* m_que_events; + proto_cancel_events* m_cancel_events; + proto_start_multiple* m_start_multiple; + proto_commit_transaction* m_commit_transaction; + proto_commit_retaining* m_commit_retaining; + proto_rollback_transaction* m_rollback_transaction; + proto_rollback_retaining* m_rollback_retaining; + proto_dsql_allocate_statement* m_dsql_allocate_statement; + proto_dsql_describe* m_dsql_describe; + proto_dsql_describe_bind* m_dsql_describe_bind; + proto_dsql_prepare* m_dsql_prepare; + proto_dsql_execute* m_dsql_execute; + proto_dsql_execute2* m_dsql_execute2; + proto_dsql_fetch* m_dsql_fetch; + proto_dsql_free_statement* m_dsql_free_statement; + proto_dsql_set_cursor_name* m_dsql_set_cursor_name; + proto_dsql_sql_info* m_dsql_sql_info; + //proto_decode_date* m_decode_date; + //proto_encode_date* m_encode_date; + //proto_add_user* m_add_user; + //proto_delete_user* m_delete_user; + //proto_modify_user* m_modify_user; + + proto_service_attach* m_service_attach; + proto_service_detach* m_service_detach; + proto_service_start* m_service_start; + proto_service_query* m_service_query; + //proto_decode_sql_date* m_decode_sql_date; + //proto_decode_sql_time* m_decode_sql_time; + //proto_decode_timestamp* m_decode_timestamp; + //proto_encode_sql_date* m_encode_sql_date; + //proto_encode_sql_time* m_encode_sql_time; + //proto_encode_timestamp* m_encode_timestamp; + + // Constructor (No need for a specific destructor) + GDS() + { + mReady = false; + mGDSVersion = 0; +#ifdef IBPP_WINDOWS + mHandle = 0; +#endif + }; +}; + +extern GDS gds; + +// +// Service Parameter Block (used to define a service) +// + +class SPB +{ + static const int BUFFERINCR; + + char* mBuffer; // Dynamically allocated SPB structure + int mSize; // Its used size in bytes + int mAlloc; // Its allocated size in bytes + + void Grow(int needed); // Alloc or grow the mBuffer + +public: + void Insert(char); // Insert a single byte code + void InsertString(char, int, const char*); // Insert a string, len can be defined as 1 or 2 bytes + void InsertByte(char type, char data); + void InsertQuad(char type, int32_t data); + void Reset(); // Clears the SPB + char* Self() { return mBuffer; } + short Size() { return (short)mSize; } + + SPB() : mBuffer(0), mSize(0), mAlloc(0) { } + ~SPB() { Reset(); } +}; + +// +// Database Parameter Block (used to define a database) +// + +class DPB +{ + static const int BUFFERINCR; + + char* mBuffer; // Dynamically allocated DPB structure + int mSize; // Its used size in bytes + int mAlloc; // Its allocated size in bytes + + void Grow(int needed); // Allocate or grow the mBuffer, so that + // 'needed' bytes can be written (at least) + +public: + void Insert(char, const char*); // Insert a new char* 'cluster' + void Insert(char, int16_t); // Insert a new int16_t 'cluster' + void Insert(char, bool); // Insert a new bool 'cluster' + void Insert(char, char); // Insert a new byte 'cluster' + void Reset(); // Clears the DPB + char* Self() { return mBuffer; } + short Size() { return (short)mSize; } + + DPB() : mBuffer(0), mSize(0), mAlloc(0) { } + ~DPB() { Reset(); } +}; + +// +// Transaction Parameter Block (used to define a transaction) +// + +class TPB +{ + static const int BUFFERINCR; + + char* mBuffer; // Dynamically allocated TPB structure + int mSize; // Its used size in bytes + int mAlloc; // Its allocated size + + void Grow(int needed); // Alloc or re-alloc the mBuffer + +public: + void Insert(char); // Insert a flag item + void Insert(const std::string& data); // Insert a string (typically table name) + void Reset(); // Clears the TPB + char* Self() { return mBuffer; } + int Size() { return mSize; } + + TPB() : mBuffer(0), mSize(0), mAlloc(0) { } + ~TPB() { Reset(); } +}; + +// +// Used to receive (and process) a results buffer in various API calls +// + +class RB +{ + char* mBuffer; + int mSize; + + char* FindToken(char token); + char* FindToken(char token, char subtoken); + +public: + void Reset(); + int GetValue(char token); + int GetCountValue(char token); + int GetValue(char token, char subtoken); + bool GetBool(char token); + int GetString(char token, std::string& data); + + char* Self() { return mBuffer; } + short Size() { return (short)mSize; } + + RB(); + RB(int Size); + ~RB(); +}; + +// +// Used to receive status info from API calls +// + +class IBS +{ + mutable ISC_STATUS mVector[20]; + mutable std::string mMessage; + +public: + ISC_STATUS* Self() { return mVector; } + bool Errors() { return (mVector[0] == 1 && mVector[1] > 0) ? true : false; } + const char* ErrorMessage() const; + int SqlCode() const; + int EngineCode() const { return (mVector[0] == 1) ? (int)mVector[1] : 0; } + void Reset(); + + IBS(); + IBS(IBS&); // Copy Constructor + ~IBS(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// +// Implementation of the "hidden" classes associated with their public +// counterparts. Their private data and methods can freely change without +// breaking the compatibility of the DLL. If they receive new public methods, +// and those methods are reflected in the public class, then the compatibility +// is broken. +// +/////////////////////////////////////////////////////////////////////////////// + +// +// Hidden implementation of Exception classes. +// + +/* + std::exception + | + IBPP::Exception + / \ + / \ + IBPP::LogicException ExceptionBase IBPP::SQLException + | \ / | \ / + | LogicExceptionImpl | SQLExceptionImpl + | | + IBPP::WrongType | + \ | + IBPP::WrongTypeImpl +*/ + +class ExceptionBase +{ + // (((((((( OBJECT INTERNALS )))))))) + +protected: + std::string mContext; // Exception context ("IDatabase::Drop") + std::string mWhat; // Full formatted message + + void buildErrorMessage(const char* message); + void raise(const std::string& context, const char* message, va_list argptr); + +public: + // The following constructors are small and could be inlined, but for object + // code compacity of the library it is much better to have them non-inlined. + // The amount of code generated by compilers for a throw is well-enough. + + ExceptionBase() throw(); + ExceptionBase(const ExceptionBase& copied) throw(); + ExceptionBase& operator=(const ExceptionBase& copied) throw(); + ExceptionBase(const std::string& context, const char* message = 0, ...) throw(); + + virtual ~ExceptionBase() throw(); + + // (((((((( OBJECT INTERFACE )))))))) + + virtual const char* Origin() const throw(); + virtual const char* ErrorMessage() const throw(); + virtual const char* what() const throw(); +}; + +class LogicExceptionImpl : public IBPP::LogicException, public ExceptionBase +{ + // (((((((( OBJECT INTERNALS )))))))) + +public: + // The following constructors are small and could be inlined, but for object + // code compacity of the library it is much better to have them non-inlined. + // The amount of code generated by compilers for a throw is well-enough. + + LogicExceptionImpl() throw(); + LogicExceptionImpl(const LogicExceptionImpl& copied) throw(); + LogicExceptionImpl& operator=(const LogicExceptionImpl& copied) throw(); + LogicExceptionImpl(const std::string& context, const char* message = 0, ...) throw(); + + virtual ~LogicExceptionImpl() throw (); + + // (((((((( OBJECT INTERFACE )))))))) + // + // The object public interface is partly implemented by inheriting from + // the ExceptionBase class. + +public: + virtual const char* Origin() const throw(); + virtual const char* ErrorMessage() const throw(); + virtual const char* what() const throw(); +}; + +class SQLExceptionImpl : public IBPP::SQLException, public ExceptionBase +{ + // (((((((( OBJECT INTERNALS )))))))) + +private: + int mSqlCode; + int mEngineCode; + +public: + // The following constructors are small and could be inlined, but for object + // code compacity of the library it is much better to have them non-inlined. + // The amount of code generated by compilers for a throw is well-enough. + + SQLExceptionImpl() throw(); + SQLExceptionImpl(const SQLExceptionImpl& copied) throw(); + SQLExceptionImpl& operator=(const SQLExceptionImpl& copied) throw(); + SQLExceptionImpl(const IBS& status, const std::string& context, + const char* message = 0, ...) throw(); + + virtual ~SQLExceptionImpl() throw (); + + // (((((((( OBJECT INTERFACE )))))))) + // + // The object public interface is partly implemented by inheriting from + // the ExceptionBase class. + +public: + virtual const char* Origin() const throw(); + virtual const char* ErrorMessage() const throw(); + virtual const char* what() const throw(); + virtual int SqlCode() const throw(); + virtual int EngineCode() const throw(); +}; + +class WrongTypeImpl : public IBPP::WrongType, public ExceptionBase +{ + // (((((((( OBJECT INTERNALS )))))))) + +public: + // The following constructors are small and could be inlined, but for object + // code compacity of the library it is much better to have them non-inlined. + // The amount of code generated by compilers for a throw is well-enough. + + WrongTypeImpl() throw(); + WrongTypeImpl(const WrongTypeImpl& copied) throw(); + WrongTypeImpl& operator=(const WrongTypeImpl& copied) throw(); + WrongTypeImpl(const std::string& context, int sqlType, IITYPE varType, + const char* message = 0, ...) throw(); + + virtual ~WrongTypeImpl() throw (); + + // (((((((( OBJECT INTERFACE )))))))) + // + // The object public interface is partly implemented by inheriting from + // the ExceptionBase class. + +public: + virtual const char* Origin() const throw(); + virtual const char* ErrorMessage() const throw(); + virtual const char* what() const throw(); +}; + +class ServiceImpl : public IBPP::IService +{ + // (((((((( OBJECT INTERNALS )))))))) + +private: + int mRefCount; // Reference counter + isc_svc_handle mHandle; // InterBase API Service Handle + std::string mServerName; // Nom du serveur + std::string mUserName; // Nom de l'utilisateur + std::string mUserPassword; // Mot de passe de l'utilisateur + std::string mWaitMessage; // Progress message returned by WaitMsg() + + isc_svc_handle* GetHandlePtr() { return &mHandle; } + void SetServerName(const char*); + void SetUserName(const char*); + void SetUserPassword(const char*); + +public: + isc_svc_handle GetHandle() { return mHandle; } + + ServiceImpl(const std::string& ServerName, const std::string& UserName, + const std::string& UserPassword); + ~ServiceImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + void Connect(); + bool Connected() { return mHandle == 0 ? false : true; } + void Disconnect(); + + void GetVersion(std::string& version); + + void AddUser(const IBPP::User&); + void GetUser(IBPP::User&); + void GetUsers(std::vector&); + void ModifyUser(const IBPP::User&); + void RemoveUser(const std::string& username); + + void SetPageBuffers(const std::string& dbfile, int buffers); + void SetSweepInterval(const std::string& dbfile, int sweep); + void SetSyncWrite(const std::string& dbfile, bool); + void SetReadOnly(const std::string& dbfile, bool); + void SetReserveSpace(const std::string& dbfile, bool); + + void Shutdown(const std::string& dbfile, IBPP::DSM mode, int sectimeout); + void Restart(const std::string& dbfile); + void Sweep(const std::string& dbfile); + void Repair(const std::string& dbfile, IBPP::RPF flags); + + void StartBackup(const std::string& dbfile, const std::string& bkfile, + IBPP::BRF flags = IBPP::BRF(0)); + void StartRestore(const std::string& bkfile, const std::string& dbfile, + int pagesize, IBPP::BRF flags = IBPP::BRF(0)); + + const char* WaitMsg(); + void Wait(); + + IBPP::IService* AddRef(); + void Release(); +}; + +class DatabaseImpl : public IBPP::IDatabase +{ + // (((((((( OBJECT INTERNALS )))))))) + + int mRefCount; // Reference counter + isc_db_handle mHandle; // InterBase API Session Handle + std::string mServerName; // Server name + std::string mDatabaseName; // Database name (path/file) + std::string mUserName; // User name + std::string mUserPassword; // User password + std::string mRoleName; // Role used for the duration of the connection + std::string mCharSet; // Character Set used for the connection + std::string mCreateParams; // Other parameters (creation only) + + int mDialect; // 1 if IB5, 1 or 3 if IB6/FB1 + std::vector mTransactions;// Table of Transaction* + std::vector mStatements;// Table of Statement* + std::vector mBlobs; // Table of Blob* + std::vector mArrays; // Table of Array* + std::vector mEvents; // Table of Events* + +public: + isc_db_handle* GetHandlePtr() { return &mHandle; } + isc_db_handle GetHandle() { return mHandle; } + + void AttachTransactionImpl(TransactionImpl*); + void DetachTransactionImpl(TransactionImpl*); + void AttachStatementImpl(StatementImpl*); + void DetachStatementImpl(StatementImpl*); + void AttachBlobImpl(BlobImpl*); + void DetachBlobImpl(BlobImpl*); + void AttachArrayImpl(ArrayImpl*); + void DetachArrayImpl(ArrayImpl*); + void AttachEventsImpl(EventsImpl*); + void DetachEventsImpl(EventsImpl*); + + DatabaseImpl(const std::string& ServerName, const std::string& DatabaseName, + const std::string& UserName, const std::string& UserPassword, + const std::string& RoleName, const std::string& CharSet, + const std::string& CreateParams); + ~DatabaseImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + const char* ServerName() const { return mServerName.c_str(); } + const char* DatabaseName() const { return mDatabaseName.c_str(); } + const char* Username() const { return mUserName.c_str(); } + const char* UserPassword() const { return mUserPassword.c_str(); } + const char* RoleName() const { return mRoleName.c_str(); } + const char* CharSet() const { return mCharSet.c_str(); } + const char* CreateParams() const { return mCreateParams.c_str(); } + + void Info(int* ODSMajor, int* ODSMinor, + int* PageSize, int* Pages, int* Buffers, int* Sweep, + bool* SyncWrites, bool* Reserve); + void Statistics(int* Fetches, int* Marks, int* Reads, int* Writes); + void Counts(int* Insert, int* Update, int* Delete, + int* ReadIdx, int* ReadSeq); + void Users(std::vector& users); + int Dialect() { return mDialect; } + + void Create(int dialect); + void Connect(); + bool Connected() { return mHandle == 0 ? false : true; } + void Inactivate(); + void Disconnect(); + void Drop(); + + IBPP::IDatabase* AddRef(); + void Release(); +}; + +class TransactionImpl : public IBPP::ITransaction +{ + // (((((((( OBJECT INTERNALS )))))))) + +private: + int mRefCount; // Reference counter + isc_tr_handle mHandle; // Transaction InterBase + + std::vector mDatabases; // Tableau de IDatabase* + std::vector mStatements; // Tableau de IStatement* + std::vector mBlobs; // Tableau de IBlob* + std::vector mArrays; // Tableau de Array* + std::vector mTPBs; // Tableau de TPB + + void Init(); // A usage exclusif des constructeurs + +public: + isc_tr_handle* GetHandlePtr() { return &mHandle; } + isc_tr_handle GetHandle() { return mHandle; } + + void AttachStatementImpl(StatementImpl*); + void DetachStatementImpl(StatementImpl*); + void AttachBlobImpl(BlobImpl*); + void DetachBlobImpl(BlobImpl*); + void AttachArrayImpl(ArrayImpl*); + void DetachArrayImpl(ArrayImpl*); + void AttachDatabaseImpl(DatabaseImpl* dbi, IBPP::TAM am = IBPP::amWrite, + IBPP::TIL il = IBPP::ilConcurrency, + IBPP::TLR lr = IBPP::lrWait, IBPP::TFF flags = IBPP::TFF(0)); + void DetachDatabaseImpl(DatabaseImpl* dbi); + + TransactionImpl(DatabaseImpl* db, IBPP::TAM am = IBPP::amWrite, + IBPP::TIL il = IBPP::ilConcurrency, + IBPP::TLR lr = IBPP::lrWait, IBPP::TFF flags = IBPP::TFF(0)); + ~TransactionImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + void AttachDatabase(IBPP::Database db, IBPP::TAM am = IBPP::amWrite, + IBPP::TIL il = IBPP::ilConcurrency, + IBPP::TLR lr = IBPP::lrWait, IBPP::TFF flags = IBPP::TFF(0)); + void DetachDatabase(IBPP::Database db); + void AddReservation(IBPP::Database db, + const std::string& table, IBPP::TTR tr); + + void Start(); + bool Started() { return mHandle == 0 ? false : true; } + void Commit(); + void Rollback(); + void CommitRetain(); + void RollbackRetain(); + + IBPP::ITransaction* AddRef(); + void Release(); +}; + +class RowImpl : public IBPP::IRow +{ + // (((((((( OBJECT INTERNALS )))))))) + +private: + int mRefCount; // Reference counter + + XSQLDA* mDescrArea; // XSQLDA descriptor itself + std::vector mNumerics; // Temporary storage for Numerics + std::vector mFloats; // Temporary storage for Floats + std::vector mInt64s; // Temporary storage for 64 bits + std::vector mInt32s; // Temporary storage for 32 bits + std::vector mInt16s; // Temporary storage for 16 bits + std::vector mBools; // Temporary storage for Bools + std::vector mStrings; // Temporary storage for Strings + std::vector mUpdated; // Which columns where updated (Set()) ? + + int mDialect; // Related database dialect + DatabaseImpl* mDatabase; // Related Database (important for Blobs, ...) + TransactionImpl* mTransaction; // Related Transaction (same remark) + + void SetValue(int, IITYPE, const void* value, int = 0); + void* GetValue(int, IITYPE, void* = 0); + +public: + void Free(); + short AllocatedSize() { return mDescrArea->sqln; } + void Resize(int n); + void AllocVariables(); + bool MissingValues(); // Returns wether one of the mMissing[] is true + XSQLDA* Self() { return mDescrArea; } + + RowImpl& operator=(const RowImpl& copied); + RowImpl(const RowImpl& copied); + RowImpl(int dialect, int size, DatabaseImpl* db, TransactionImpl* tr); + ~RowImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + void SetNull(int); + void Set(int, bool); + void Set(int, const char*); // c-strings + void Set(int, const void*, int); // byte buffers + void Set(int, const std::string&); + void Set(int, int16_t); + void Set(int, int32_t); + void Set(int, int64_t); + void Set(int, float); + void Set(int, double); + void Set(int, const IBPP::Timestamp&); + void Set(int, const IBPP::Date&); + void Set(int, const IBPP::Time&); + void Set(int, const IBPP::DBKey&); + void Set(int, const IBPP::Blob&); + void Set(int, const IBPP::Array&); + + bool IsNull(int); + bool Get(int, bool&); + bool Get(int, char*); // c-strings, len unchecked + bool Get(int, void*, int&); // byte buffers + bool Get(int, std::string&); + bool Get(int, int16_t&); + bool Get(int, int32_t&); + bool Get(int, int64_t&); + bool Get(int, float&); + bool Get(int, double&); + bool Get(int, IBPP::Timestamp&); + bool Get(int, IBPP::Date&); + bool Get(int, IBPP::Time&); + bool Get(int, IBPP::DBKey&); + bool Get(int, IBPP::Blob&); + bool Get(int, IBPP::Array&); + + bool IsNull(const std::string&); + bool Get(const std::string&, bool&); + bool Get(const std::string&, char*); // c-strings, len unchecked + bool Get(const std::string&, void*, int&); // byte buffers + bool Get(const std::string&, std::string&); + bool Get(const std::string&, int16_t&); + bool Get(const std::string&, int32_t&); + bool Get(const std::string&, int64_t&); + bool Get(const std::string&, float&); + bool Get(const std::string&, double&); + bool Get(const std::string&, IBPP::Timestamp&); + bool Get(const std::string&, IBPP::Date&); + bool Get(const std::string&, IBPP::Time&); + bool Get(const std::string&, IBPP::DBKey&); + bool Get(const std::string&, IBPP::Blob&); + bool Get(const std::string&, IBPP::Array&); + + int ColumnNum(const std::string&); + const char* ColumnName(int); + const char* ColumnAlias(int); + const char* ColumnTable(int); + IBPP::SDT ColumnType(int); + int ColumnSubtype(int); + int ColumnSize(int); + int ColumnScale(int); + int Columns(); + + bool ColumnUpdated(int); + bool Updated(); + + IBPP::Database DatabasePtr() const; + IBPP::Transaction TransactionPtr() const; + + IBPP::IRow* Clone(); + IBPP::IRow* AddRef(); + void Release(); +}; + +class StatementImpl : public IBPP::IStatement +{ + // (((((((( OBJECT INTERNALS )))))))) + +private: + friend class TransactionImpl; + + int mRefCount; // Reference counter + isc_stmt_handle mHandle; // Statement Handle + + DatabaseImpl* mDatabase; // Attached database + TransactionImpl* mTransaction; // Attached transaction + RowImpl* mInRow; + //bool* mInMissing; // Quels paramètres n'ont pas été spécifiés + RowImpl* mOutRow; + bool mResultSetAvailable; // Executed and result set is available + bool mCursorOpened; // dsql_set_cursor_name was called + IBPP::STT mType; // Type de requète + std::string mSql; // Last SQL statement prepared or executed + + // Internal Methods + void CursorFree(); + +public: + // Properties and Attributes Access Methods + isc_stmt_handle GetHandle() { return mHandle; } + + void AttachDatabaseImpl(DatabaseImpl*); + void DetachDatabaseImpl(); + void AttachTransactionImpl(TransactionImpl*); + void DetachTransactionImpl(); + + StatementImpl(DatabaseImpl*, TransactionImpl*, const std::string&); + ~StatementImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + void Prepare(const std::string& sql); + void Execute(const std::string& sql); + inline void Execute() { Execute(std::string()); } + void ExecuteImmediate(const std::string&); + void CursorExecute(const std::string& cursor, const std::string& sql); + inline void CursorExecute(const std::string& cursor) { CursorExecute(cursor, std::string()); } + bool Fetch(); + bool Fetch(IBPP::Row&); + int AffectedRows(); + void Close(); // Free resources, attachments maintained + std::string& Sql() { return mSql; } + IBPP::STT Type() { return mType; } + + void SetNull(int); + void Set(int, bool); + void Set(int, const char*); // c-strings + void Set(int, const void*, int); // byte buffers + void Set(int, const std::string&); + void Set(int, int16_t); + void Set(int, int32_t); + void Set(int, int64_t); + void Set(int, float); + void Set(int, double); + void Set(int, const IBPP::Timestamp&); + void Set(int, const IBPP::Date&); + void Set(int, const IBPP::Time&); + void Set(int, const IBPP::DBKey&); + void Set(int, const IBPP::Blob&); + void Set(int, const IBPP::Array&); + + bool IsNull(int); + bool Get(int, bool*); + bool Get(int, bool&); + bool Get(int, char*); // c-strings, len unchecked + bool Get(int, void*, int&); // byte buffers + bool Get(int, std::string&); + bool Get(int, int16_t*); + bool Get(int, int16_t&); + bool Get(int, int32_t*); + bool Get(int, int32_t&); + bool Get(int, int64_t*); + bool Get(int, int64_t&); + bool Get(int, float*); + bool Get(int, float&); + bool Get(int, double*); + bool Get(int, double&); + bool Get(int, IBPP::Timestamp&); + bool Get(int, IBPP::Date&); + bool Get(int, IBPP::Time&); + bool Get(int, IBPP::DBKey&); + bool Get(int, IBPP::Blob&); + bool Get(int, IBPP::Array&); + + bool IsNull(const std::string&); + bool Get(const std::string&, bool*); + bool Get(const std::string&, bool&); + bool Get(const std::string&, char*); // c-strings, len unchecked + bool Get(const std::string&, void*, int&); // byte buffers + bool Get(const std::string&, std::string&); + bool Get(const std::string&, int16_t*); + bool Get(const std::string&, int16_t&); + bool Get(const std::string&, int32_t*); + bool Get(const std::string&, int32_t&); + bool Get(const std::string&, int64_t*); + bool Get(const std::string&, int64_t&); + bool Get(const std::string&, float*); + bool Get(const std::string&, float&); + bool Get(const std::string&, double*); + bool Get(const std::string&, double&); + bool Get(const std::string&, IBPP::Timestamp&); + bool Get(const std::string&, IBPP::Date&); + bool Get(const std::string&, IBPP::Time&); + bool Get(const std::string&, IBPP::DBKey&); + bool Get(const std::string&, IBPP::Blob&); + bool Get(const std::string&, IBPP::Array&); + + int ColumnNum(const std::string&); + int ColumnNumAlias(const std::string&); + const char* ColumnName(int); + const char* ColumnAlias(int); + const char* ColumnTable(int); + IBPP::SDT ColumnType(int); + int ColumnSubtype(int); + int ColumnSize(int); + int ColumnScale(int); + int Columns(); + + IBPP::SDT ParameterType(int); + int ParameterSubtype(int); + int ParameterSize(int); + int ParameterScale(int); + int Parameters(); + + void Plan(std::string&); + + IBPP::Database DatabasePtr() const; + IBPP::Transaction TransactionPtr() const; + + IBPP::IStatement* AddRef(); + void Release(); +}; + +class BlobImpl : public IBPP::IBlob +{ + // (((((((( OBJECT INTERNALS )))))))) + +private: + friend class RowImpl; + + int mRefCount; + bool mIdAssigned; + ISC_QUAD mId; + isc_blob_handle mHandle; + bool mWriteMode; + DatabaseImpl* mDatabase; // Belongs to this database + TransactionImpl* mTransaction; // Belongs to this transaction + + void Init(); + void SetId(ISC_QUAD*); + void GetId(ISC_QUAD*); + +public: + void AttachDatabaseImpl(DatabaseImpl*); + void DetachDatabaseImpl(); + void AttachTransactionImpl(TransactionImpl*); + void DetachTransactionImpl(); + + BlobImpl(const BlobImpl&); + BlobImpl(DatabaseImpl*, TransactionImpl* = 0); + ~BlobImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + void Create(); + void Open(); + void Close(); + void Cancel(); + int Read(void*, int size); + void Write(const void*, int size); + void Info(int* Size, int* Largest, int* Segments); + + void Save(const std::string& data); + void Load(std::string& data); + + IBPP::Database DatabasePtr() const; + IBPP::Transaction TransactionPtr() const; + + IBPP::IBlob* AddRef(); + void Release(); +}; + +class ArrayImpl : public IBPP::IArray +{ + // (((((((( OBJECT INTERNALS )))))))) + +private: + friend class RowImpl; + + int mRefCount; // Reference counter + bool mIdAssigned; + ISC_QUAD mId; + bool mDescribed; + ISC_ARRAY_DESC mDesc; + DatabaseImpl* mDatabase; // Database attachée + TransactionImpl* mTransaction; // Transaction attachée + void* mBuffer; // Buffer for native data + int mBufferSize; // Size of this buffer in bytes + int mElemCount; // Count of elements in this array + int mElemSize; // Size of an element in the buffer + + void Init(); + void SetId(ISC_QUAD*); + void GetId(ISC_QUAD*); + void ResetId(); + void AllocArrayBuffer(); + +public: + void AttachDatabaseImpl(DatabaseImpl*); + void DetachDatabaseImpl(); + void AttachTransactionImpl(TransactionImpl*); + void DetachTransactionImpl(); + + ArrayImpl(const ArrayImpl&); + ArrayImpl(DatabaseImpl*, TransactionImpl* = 0); + ~ArrayImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + void Describe(const std::string& table, const std::string& column); + void ReadTo(IBPP::ADT, void*, int); + void WriteFrom(IBPP::ADT, const void*, int); + IBPP::SDT ElementType(); + int ElementSize(); + int ElementScale(); + int Dimensions(); + void Bounds(int dim, int* low, int* high); + void SetBounds(int dim, int low, int high); + + IBPP::Database DatabasePtr() const; + IBPP::Transaction TransactionPtr() const; + + IBPP::IArray* AddRef(); + void Release(); +}; + +// +// EventBufferIterator: used in EventsImpl implementation. +// + +template +struct EventBufferIterator +{ + It mIt; + +public: + EventBufferIterator& operator++() + { mIt += 1 + static_cast(*mIt) + 4; return *this; } + + bool operator == (const EventBufferIterator& i) const { return i.mIt == mIt; } + bool operator != (const EventBufferIterator& i) const { return i.mIt != mIt; } + +#ifdef __BCPLUSPLUS__ +#pragma warn -8027 +#endif + std::string get_name() const + { + return std::string(mIt + 1, mIt + 1 + static_cast(*mIt)); + } +#ifdef __BCPLUSPLUS__ +#pragma warn .8027 +#endif + + uint32_t get_count() const + { + return (*gds.Call()->m_vax_integer) + (const_cast(&*(mIt + 1 + static_cast(*mIt))), 4); + } + + // Those container like begin() and end() allow access to the underlying type + It begin() { return mIt; } + It end() { return mIt + 1 + static_cast(*mIt) + 4; } + + EventBufferIterator() {} + EventBufferIterator(It it) : mIt(it) {} +}; + +class EventsImpl : public IBPP::IEvents +{ + static const size_t MAXEVENTNAMELEN; + static void EventHandler(const char*, short, const char*); + + typedef std::vector ObjRefs; + ObjRefs mObjectReferences; + + typedef std::vector Buffer; + Buffer mEventBuffer; + Buffer mResultsBuffer; + + int mRefCount; // Reference counter + + DatabaseImpl* mDatabase; + ISC_LONG mId; // Firebird internal Id of these events + bool mQueued; // Has isc_que_events() been called? + bool mTrapped; // EventHandled() was called since last que_events() + + void FireActions(); + void Queue(); + void Cancel(); + + EventsImpl& operator=(const EventsImpl&); + EventsImpl(const EventsImpl&); + +public: + void AttachDatabaseImpl(DatabaseImpl*); + void DetachDatabaseImpl(); + + EventsImpl(DatabaseImpl* dbi); + ~EventsImpl(); + + // (((((((( OBJECT INTERFACE )))))))) + +public: + void Add(const std::string&, IBPP::EventInterface*); + void Drop(const std::string&); + void List(std::vector&); + void Clear(); // Drop all events + void Dispatch(); // Dispatch NON async events + + IBPP::Database DatabasePtr() const; + + IBPP::IEvents* AddRef(); + void Release(); +}; + +void encodeDate(ISC_DATE& isc_dt, const IBPP::Date& dt); +void decodeDate(IBPP::Date& dt, const ISC_DATE& isc_dt); + +void encodeTime(ISC_TIME& isc_tm, const IBPP::Time& tm); +void decodeTime(IBPP::Time& tm, const ISC_TIME& isc_tm); + +void encodeTimestamp(ISC_TIMESTAMP& isc_ts, const IBPP::Timestamp& ts); +void decodeTimestamp(IBPP::Timestamp& ts, const ISC_TIMESTAMP& isc_ts); + +struct consts // See _ibpp.cpp for initializations of these constants +{ + static const double dscales[19]; + static const int Dec31_1899; + static const int16_t min16; + static const int16_t max16; + static const int32_t min32; + static const int32_t max32; +}; + +} // namespace ibpp_internal + +#endif // __INTERNAL_IBPP_H__ + +// +// Eof +// diff --git a/stglibs/ibpp.lib/_ibs.cpp b/stglibs/ibpp.lib/_ibs.cpp new file mode 100644 index 00000000..25e66bc9 --- /dev/null +++ b/stglibs/ibpp.lib/_ibs.cpp @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: _ibs.cpp,v 1.2 2009/03/19 20:00:27 faust Exp $ +// Subject : IBPP, internal Status class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +int IBS::SqlCode() const +{ + return (int)(*gds.Call()->m_sqlcode)(&mVector[0]); +} + +const char* IBS::ErrorMessage() const +{ + char msg[1024]; + ISC_LONG sqlcode; + + if (! mMessage.empty()) return mMessage.c_str(); // If message compiled, returns it + + // Compiles the message (SQL part) + std::ostringstream message; + sqlcode = (*gds.Call()->m_sqlcode)(mVector); + if (sqlcode != -999) + { + (*gds.Call()->m_sql_interprete)((short)sqlcode, msg, sizeof(msg)); + message<< _("SQL Message : ")<< sqlcode<< "\n"<< msg<< "\n\n"; + } + + message<< _("Engine Code : ")<< EngineCode()<< "\n"; + + // Compiles the message (Engine part) + ISC_STATUS* error = &mVector[0]; + try { (*gds.Call()->m_interprete)(msg, &error); } + catch(...) { msg[0] = '\0'; } + message<< _("Engine Message :")<< "\n"<< msg; + try + { + while ((*gds.Call()->m_interprete)(msg, &error)) + message<< "\n"<< msg; + } + catch (...) { } + + message<< "\n"; + mMessage = message.str(); + return mMessage.c_str(); +} + +void IBS::Reset() +{ + for (int i = 0; i < 20; i++) mVector[i] = 0; + mMessage.erase(); +} + +IBS::IBS() +{ + Reset(); +} + +IBS::~IBS() +{ +} + +/** Copy Constructor +*/ + +IBS::IBS(IBS& copied) +{ + memcpy(mVector, copied.mVector, sizeof(mVector)); +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/_rb.cpp b/stglibs/ibpp.lib/_rb.cpp new file mode 100644 index 00000000..d3dfbc8e --- /dev/null +++ b/stglibs/ibpp.lib/_rb.cpp @@ -0,0 +1,205 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: _rb.cpp,v 1.2 2009/03/19 20:00:27 faust Exp $ +// Subject : IBPP, internal RB class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * RB == Result Block/Buffer, see Interbase 6.0 C-API +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +char* RB::FindToken(char token) +{ + char* p = mBuffer; + + while (*p != isc_info_end) + { + int len; + + if (*p == token) return p; + len = (*gds.Call()->m_vax_integer)(p+1, 2); + p += (len + 3); + } + + return 0; +} + +char* RB::FindToken(char token, char subtoken) +{ + char* p = mBuffer; + + while (*p != isc_info_end) + { + int len; + + if (*p == token) + { + // Found token, now find subtoken + int inlen = (*gds.Call()->m_vax_integer)(p+1, 2); + p += 3; + while (inlen > 0) + { + if (*p == subtoken) return p; + len = (*gds.Call()->m_vax_integer)(p+1, 2); + p += (len + 3); + inlen -= (len + 3); + } + return 0; + } + len = (*gds.Call()->m_vax_integer)(p+1, 2); + p += (len + 3); + } + + return 0; +} + +int RB::GetValue(char token) +{ + int value; + int len; + char* p = FindToken(token); + + if (p == 0) + throw LogicExceptionImpl("RB::GetValue", _("Token not found.")); + + len = (*gds.Call()->m_vax_integer)(p+1, 2); + if (len == 0) value = 0; + else value = (*gds.Call()->m_vax_integer)(p+3, (short)len); + + return value; +} + +int RB::GetCountValue(char token) +{ + // Specifically used on tokens like isc_info_insert_count and the like + // which return detailed counts per relation. We sum up the values. + int value; + int len; + char* p = FindToken(token); + + if (p == 0) + throw LogicExceptionImpl("RB::GetCountValue", _("Token not found.")); + + // len is the number of bytes in the following array + len = (*gds.Call()->m_vax_integer)(p+1, 2); + p += 3; + value = 0; + while (len > 0) + { + // Each array item is 6 bytes : 2 bytes for the relation_id which + // we skip, and 4 bytes for the count value which we sum up accross + // all tables. + value += (*gds.Call()->m_vax_integer)(p+2, 4); + p += 6; + len -= 6; + } + + return value; +} + +int RB::GetValue(char token, char subtoken) +{ + int value; + int len; + char* p = FindToken(token, subtoken); + + if (p == 0) + throw LogicExceptionImpl("RB::GetValue", _("Token/Subtoken not found.")); + + len = (*gds.Call()->m_vax_integer)(p+1, 2); + if (len == 0) value = 0; + else value = (*gds.Call()->m_vax_integer)(p+3, (short)len); + + return value; +} + +bool RB::GetBool(char token) +{ + int value; + char* p = FindToken(token); + + if (p == 0) + throw LogicExceptionImpl("RB::GetBool", _("Token not found.")); + + value = (*gds.Call()->m_vax_integer)(p+1, 4); + + return value == 0 ? false : true; +} + +int RB::GetString(char token, std::string& data) +{ + int len; + char* p = FindToken(token); + + if (p == 0) + throw LogicExceptionImpl("RB::GetString", _("Token not found.")); + + len = (*gds.Call()->m_vax_integer)(p+1, 2); + data = std::string(p+3, len); + return len; +} + +void RB::Reset() +{ + delete [] mBuffer; + mBuffer = new char [mSize]; + memset(mBuffer, 255, mSize); +} + +RB::RB() +{ + mSize = 1024; + mBuffer = new char [1024]; + memset(mBuffer, 255, mSize); +} + +RB::RB(int Size) +{ + mSize = Size; + mBuffer = new char [Size]; + memset(mBuffer, 255, mSize); +} + +RB::~RB() +{ + try { delete [] mBuffer; } + catch (...) { } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/_spb.cpp b/stglibs/ibpp.lib/_spb.cpp new file mode 100644 index 00000000..632a0209 --- /dev/null +++ b/stglibs/ibpp.lib/_spb.cpp @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: _spb.cpp,v 1.2 2009/03/19 20:00:27 faust Exp $ +// Subject : IBPP, internal SPB class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * SPB == Service Parameter Block/Buffer, see Interbase 6.0 C-API +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +const int SPB::BUFFERINCR = 128; + +void SPB::Grow(int needed) +{ + if ((mSize + needed) > mAlloc) + { + // We need to grow the buffer. We use increments of BUFFERINCR bytes. + needed = (needed / BUFFERINCR + 1) * BUFFERINCR; + char* newbuffer = new char[mAlloc + needed]; + if (mBuffer != 0) + { + // Move the old buffer content to the new one + memcpy(newbuffer, mBuffer, mSize); + delete [] mBuffer; + } + mBuffer = newbuffer; + mAlloc += needed; + } +} + +void SPB::Insert(char opcode) +{ + Grow(1); + mBuffer[mSize++] = opcode; +} + +void SPB::InsertString(char type, int lenwidth, const char* data) +{ + int16_t len = (int16_t)strlen(data); + + Grow(1 + lenwidth + len); + mBuffer[mSize++] = type; + switch (lenwidth) + { + case 1 : mBuffer[mSize] = char(len); mSize++; break; + case 2 : *(int16_t*)&mBuffer[mSize] = int16_t((*gds.Call()->m_vax_integer)((char*)&len, 2)); + mSize += 2; break; + default : throw LogicExceptionImpl("IISPB::IISPB", _("Invalid length parameter")); + } + strncpy(&mBuffer[mSize], data, len); + mSize += len; +} + +void SPB::InsertByte(char type, char data) +{ + Grow(1 + 1); + mBuffer[mSize++] = type; + mBuffer[mSize++] = data; +} + +void SPB::InsertQuad(char type, int32_t data) +{ + Grow(1 + 4); + mBuffer[mSize++] = type; + *(int32_t*)&mBuffer[mSize] = int32_t((*gds.Call()->m_vax_integer)((char*)&data, 4)); + mSize += 4; +} + +void SPB::Reset() +{ + if (mBuffer != 0) + { + delete [] mBuffer; + mBuffer = 0; + mSize = 0; + mAlloc = 0; + } +} + +/* +void SPB::Insert(char type, short data) +{ + Grow(1 + 3); + mBuffer[mSize++] = type; + mBuffer[mSize++] = char(2); + *(short*)&mBuffer[mSize] = data; + mSize += 2; +} + +void SPB::Insert(char type, bool data) +{ + Grow(1 + 2); + mBuffer[mSize++] = type; + mBuffer[mSize++] = char(1); + mBuffer[mSize++] = char(data ? 1 : 0); +} +*/ + +// +// EOF +// diff --git a/stglibs/ibpp.lib/_tpb.cpp b/stglibs/ibpp.lib/_tpb.cpp new file mode 100644 index 00000000..d27c2d55 --- /dev/null +++ b/stglibs/ibpp.lib/_tpb.cpp @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: _tpb.cpp,v 1.2 2009/03/19 20:00:28 faust Exp $ +// Subject : IBPP, internal TPB class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * TPB == Transaction Parameter Block/Buffer, see Interbase 6.0 C-API +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +const int TPB::BUFFERINCR = 128; + +void TPB::Grow(int needed) +{ + if (mBuffer == 0) ++needed; // Initial alloc will require one more byte + if ((mSize + needed) > mAlloc) + { + // We need to grow the buffer. We use increments of BUFFERINCR bytes. + needed = (needed / BUFFERINCR + 1) * BUFFERINCR; + char* newbuffer = new char[mAlloc + needed]; + if (mBuffer == 0) + { + // Initial allocation, initialize the version tag + newbuffer[0] = isc_tpb_version3; + mSize = 1; + } + else + { + // Move the old buffer content to the new one + memcpy(newbuffer, mBuffer, mSize); + delete [] mBuffer; + } + mBuffer = newbuffer; + mAlloc += needed; + } +} + +void TPB::Insert(char item) +{ + Grow(1); + mBuffer[mSize++] = item; +} + +void TPB::Insert(const std::string& data) +{ + int len = (int)data.length(); + Grow(1 + len); + mBuffer[mSize++] = (char)len; + strncpy(&mBuffer[mSize], data.c_str(), len); + mSize += len; +} + +void TPB::Reset() +{ + if (mSize != 0) + { + delete [] mBuffer; + mBuffer = 0; + mSize = 0; + mAlloc = 0; + } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/all_in_one.cpp b/stglibs/ibpp.lib/all_in_one.cpp new file mode 100644 index 00000000..9f74f7e0 --- /dev/null +++ b/stglibs/ibpp.lib/all_in_one.cpp @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: all_in_one.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $ +// Subject : "All In One" source code file +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// * This file is just an "all in one" holder for all the core source files +// of IBPP. When you build a project made of each individual source code +// files, please DON'T include this one. +// Though if you prefer, maybe for better compiler optimizations, you can +// compile all of IBPP at once by just compiling this single .cpp file, +// which in turn, includes all the others. Presenting such a single +// compilation unit to the compiler may help it do better optimizations. +// This is just provided for convenience and is in no case required. +// +/////////////////////////////////////////////////////////////////////////////// + +#include "_ibpp.cpp" +#include "_dpb.cpp" +#include "_ibs.cpp" +#include "_rb.cpp" +#include "_spb.cpp" +#include "_tpb.cpp" + +#include "array.cpp" +#include "blob.cpp" +#include "database.cpp" +#include "date.cpp" +#include "dbkey.cpp" +#include "events.cpp" +#include "exception.cpp" +#include "row.cpp" +#include "service.cpp" +#include "statement.cpp" +#include "time.cpp" +#include "transaction.cpp" +#include "user.cpp" + +// Eof diff --git a/stglibs/ibpp.lib/array.cpp b/stglibs/ibpp.lib/array.cpp new file mode 100644 index 00000000..2dc7ab27 --- /dev/null +++ b/stglibs/ibpp.lib/array.cpp @@ -0,0 +1,1046 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: array.cpp,v 1.2 2009/03/19 20:00:28 faust Exp $ +// Subject : IBPP, Array class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include +#include + +using namespace ibpp_internals; + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void ArrayImpl::Describe(const std::string& table, const std::string& column) +{ + //if (mIdAssigned) + // throw LogicExceptionImpl("Array::Lookup", _("Array already in use.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Array::Lookup", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Array::Lookup", _("No Transaction is attached.")); + + ResetId(); // Re-use this array object if was previously assigned + + IBS status; + (*gds.Call()->m_array_lookup_bounds)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), const_cast(table.c_str()), + const_cast(column.c_str()), &mDesc); + if (status.Errors()) + throw SQLExceptionImpl(status, "Array::Lookup", + _("isc_array_lookup_bounds failed.")); + + AllocArrayBuffer(); + + mDescribed = true; +} + +void ArrayImpl::SetBounds(int dim, int low, int high) +{ + if (! mDescribed) + throw LogicExceptionImpl("Array::SetBounds", _("Array description not set.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Array::SetBounds", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Array::SetBounds", _("No Transaction is attached.")); + if (dim < 0 || dim > mDesc.array_desc_dimensions-1) + throw LogicExceptionImpl("Array::SetBounds", _("Invalid dimension.")); + if (low > high || + low < mDesc.array_desc_bounds[dim].array_bound_lower || + low > mDesc.array_desc_bounds[dim].array_bound_upper || + high > mDesc.array_desc_bounds[dim].array_bound_upper || + high < mDesc.array_desc_bounds[dim].array_bound_lower) + throw LogicExceptionImpl("Array::SetBounds", + _("Invalid bounds. You can only narrow the bounds.")); + + mDesc.array_desc_bounds[dim].array_bound_lower = short(low); + mDesc.array_desc_bounds[dim].array_bound_upper = short(high); + + AllocArrayBuffer(); +} + +IBPP::SDT ArrayImpl::ElementType() +{ + if (! mDescribed) + throw LogicExceptionImpl("Array::ElementType", + _("Array description not set.")); + + IBPP::SDT value; + switch (mDesc.array_desc_dtype) + { + case blr_text : value = IBPP::sdString; break; + case blr_varying : value = IBPP::sdString; break; + case blr_cstring : value = IBPP::sdString; break; + case blr_short : value = IBPP::sdSmallint; break; + case blr_long : value = IBPP::sdInteger; break; + case blr_int64 : value = IBPP::sdLargeint; break; + case blr_float : value = IBPP::sdFloat; break; + case blr_double : value = IBPP::sdDouble; break; + case blr_timestamp : value = IBPP::sdTimestamp; break; + case blr_sql_date : value = IBPP::sdDate; break; + case blr_sql_time : value = IBPP::sdTime; break; + default : throw LogicExceptionImpl("Array::ElementType", + _("Found an unknown sqltype !")); + } + + return value; +} + +int ArrayImpl::ElementSize() +{ + if (! mDescribed) + throw LogicExceptionImpl("Array::ElementSize", + _("Array description not set.")); + + return mDesc.array_desc_length; +} + +int ArrayImpl::ElementScale() +{ + if (! mDescribed) + throw LogicExceptionImpl("Array::ElementScale", + _("Array description not set.")); + + return mDesc.array_desc_scale; +} + +int ArrayImpl::Dimensions() +{ + if (! mDescribed) + throw LogicExceptionImpl("Array::Dimensions", + _("Array description not set.")); + + return mDesc.array_desc_dimensions; +} + +void ArrayImpl::Bounds(int dim, int* low, int* high) +{ + if (! mDescribed) + throw LogicExceptionImpl("Array::Bounds", _("Array description not set.")); + if (dim < 0 || dim > mDesc.array_desc_dimensions-1) + throw LogicExceptionImpl("Array::Bounds", _("Invalid dimension.")); + if (low == 0 || high == 0) + throw LogicExceptionImpl("Array::Bounds", _("Null reference detected.")); + + *low = mDesc.array_desc_bounds[dim].array_bound_lower; + *high = mDesc.array_desc_bounds[dim].array_bound_upper; +} + +/* + +COMMENTS + +1) +For an array column of type CHAR(X), the internal type returned or expected is blr_text. +In such case, the byte array received or submitted to get/put_slice is formatted in +elements of X bytes, which correspond to what is reported in array_desc_length. +The elements are not '\0' terminated but are right-padded with spaces ' '. + +2) +For an array column of type VARCHAR(X), the internal type is blr_varying. +The underlying format is rather curious and different than what is used in XSQLDA. +The element size is reported in array_desc_length as X. +Yet each element of the byte array is expected to be of size X+2 (just as if we were +to stuff a short in the first 2 bytes to store the length (as is done with XSQLDA). +No. The string of X characters maximum has to be stored in the chunks of X+2 bytes as +a zero-terminated c-string. Note that the buffer is indeed one byte too large. +Internally, the API probably convert in-place in these chunks the zero-terminated string +to a variable-size string with a short in front and the string data non zero-terminated +behind. + +3) +With Interbase 6.0 and Firebird 1.0 (initial april 2002 release), the int64 support is +broken regarding the arrays. It is not possible to work on arrays using a datatype stored +in an int64, as for instance any NUMERIC(x,0) where x is equal or greater than 10. That's +a bug in the engine, not in IBPP, which has been fixed in june 2002. Engines compiled from +the current Firebird CVS code as of july 2002 are okay. As will be the 1.01 Firebird version. +We have no idea if this is fixed or not in Interbase 6.5 though. + +*/ + +void ArrayImpl::ReadTo(IBPP::ADT adtype, void* data, int datacount) +{ + if (! mIdAssigned) + throw LogicExceptionImpl("Array::ReadTo", _("Array Id not read from column.")); + if (! mDescribed) + throw LogicExceptionImpl("Array::ReadTo", _("Array description not set.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Array::ReadTo", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Array::ReadTo", _("No Transaction is attached.")); + if (datacount != mElemCount) + throw LogicExceptionImpl("Array::ReadTo", _("Wrong count of array elements")); + + IBS status; + ISC_LONG lenbuf = mBufferSize; + (*gds.Call()->m_array_get_slice)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), &mId, &mDesc, mBuffer, &lenbuf); + if (status.Errors()) + throw SQLExceptionImpl(status, "Array::ReadTo", _("isc_array_get_slice failed.")); + if (lenbuf != mBufferSize) + throw SQLExceptionImpl(status, "Array::ReadTo", _("Internal buffer size discrepancy.")); + + // Now, convert the types and copy values to the user array... + int len; + char* src = (char*)mBuffer; + char* dst = (char*)data; + + switch (mDesc.array_desc_dtype) + { + case blr_text : + if (adtype == IBPP::adString) + { + for (int i = 0; i < mElemCount; i++) + { + strncpy(dst, src, mElemSize); + dst[mElemSize] = '\0'; + src += mElemSize; + dst += (mElemSize + 1); + } + } + else if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + if (*src == 't' || *src == 'T' || *src == 'y' || *src == 'Y' || *src == '1') + *(bool*)dst = true; + else *(bool*)dst = false; + src += mElemSize; + dst += sizeof(bool); + } + } + else throw LogicExceptionImpl("Array::ReadTo", _("Incompatible types.")); + break; + + case blr_varying : + if (adtype == IBPP::adString) + { + for (int i = 0; i < mElemCount; i++) + { + len = (int)strlen(src); + if (len > mElemSize-2) len = mElemSize-2; + strncpy(dst, src, len); + dst[len] = '\0'; + src += mElemSize; + dst += (mElemSize - 2 + 1); + } + } + else if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + if (*src == 't' || *src == 'T' || *src == 'y' || *src == 'Y' || *src == '1') + *(bool*)dst = true; + else *(bool*)dst = false; + src += mElemSize; + dst += sizeof(bool); + } + } + else throw LogicExceptionImpl("Array::ReadTo", _("Incompatible types.")); + break; + + case blr_short : + if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *(bool*)dst = (*(short*)src != 0) ? true : false; + src += mElemSize; + dst += sizeof(bool); + } + } + else if (adtype == IBPP::adInt16) + { + for (int i = 0; i < mElemCount; i++) + { + *(short*)dst = *(short*)src; + src += mElemSize; + dst += sizeof(short); + } + } + else if (adtype == IBPP::adInt32) + { + for (int i = 0; i < mElemCount; i++) + { + *(int*)dst = (int)*(short*)src; + src += mElemSize; + dst += sizeof(int); + } + } + else if (adtype == IBPP::adInt64) + { + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = (int64_t)*(short*)src; + src += mElemSize; + dst += sizeof(int64_t); + } + } + else if (adtype == IBPP::adFloat) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(float*)dst = (float)(*(short*)src / divisor); + src += mElemSize; + dst += sizeof(float); + } + } + else if (adtype == IBPP::adDouble) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(double*)dst = (double)(*(short*)src / divisor); + src += mElemSize; + dst += sizeof(double); + } + } + else throw LogicExceptionImpl("Array::ReadTo", _("Incompatible types.")); + break; + + case blr_long : + if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *(bool*)dst = (*(long*)src != 0) ? true : false; + src += mElemSize; + dst += sizeof(bool); + } + } + else if (adtype == IBPP::adInt16) + { + for (int i = 0; i < mElemCount; i++) + { + if (*(long*)src < consts::min16 || *(long*)src > consts::max16) + throw LogicExceptionImpl("Array::ReadTo", + _("Out of range numeric conversion !")); + *(short*)dst = short(*(long*)src); + src += mElemSize; + dst += sizeof(short); + } + } + else if (adtype == IBPP::adInt32) + { + for (int i = 0; i < mElemCount; i++) + { + *(long*)dst = *(long*)src; + src += mElemSize; + dst += sizeof(long); + } + } + else if (adtype == IBPP::adInt64) + { + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = (int64_t)*(long*)src; + src += mElemSize; + dst += sizeof(int64_t); + } + } + else if (adtype == IBPP::adFloat) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(float*)dst = (float)(*(long*)src / divisor); + src += mElemSize; + dst += sizeof(float); + } + } + else if (adtype == IBPP::adDouble) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(double*)dst = (double)(*(long*)src / divisor); + src += mElemSize; + dst += sizeof(double); + } + } + else throw LogicExceptionImpl("Array::ReadTo", _("Incompatible types.")); + break; + + case blr_int64 : + if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *(bool*)dst = (*(int64_t*)src != 0) ? true : false; + src += mElemSize; + dst += sizeof(bool); + } + } + else if (adtype == IBPP::adInt16) + { + for (int i = 0; i < mElemCount; i++) + { + if (*(int64_t*)src < consts::min16 || *(int64_t*)src > consts::max16) + throw LogicExceptionImpl("Array::ReadTo", + _("Out of range numeric conversion !")); + *(short*)dst = short(*(int64_t*)src); + src += mElemSize; + dst += sizeof(short); + } + } + else if (adtype == IBPP::adInt32) + { + for (int i = 0; i < mElemCount; i++) + { + if (*(int64_t*)src < consts::min32 || *(int64_t*)src > consts::max32) + throw LogicExceptionImpl("Array::ReadTo", + _("Out of range numeric conversion !")); + *(long*)dst = (long)*(int64_t*)src; + src += mElemSize; + dst += sizeof(long); + } + } + else if (adtype == IBPP::adInt64) + { + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = *(int64_t*)src; + src += mElemSize; + dst += sizeof(int64_t); + } + } + else if (adtype == IBPP::adFloat) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(float*)dst = (float)(*(int64_t*)src / divisor); + src += mElemSize; + dst += sizeof(float); + } + } + else if (adtype == IBPP::adDouble) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(double*)dst = (double)(*(int64_t*)src / divisor); + src += mElemSize; + dst += sizeof(double); + } + } + else throw LogicExceptionImpl("Array::ReadTo", _("Incompatible types.")); + break; + + case blr_float : + if (adtype != IBPP::adFloat || mDesc.array_desc_scale != 0) + throw LogicExceptionImpl("Array::ReadTo", _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + *(float*)dst = *(float*)src; + src += mElemSize; + dst += sizeof(float); + } + break; + + case blr_double : + if (adtype != IBPP::adDouble) throw LogicExceptionImpl("Array::ReadTo", + _("Incompatible types.")); + if (mDesc.array_desc_scale != 0) + { + // Round to scale of NUMERIC(x,y) + double divisor = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(double*)dst = (double)(*(double*)src / divisor); + src += mElemSize; + dst += sizeof(double); + } + } + else + { + for (int i = 0; i < mElemCount; i++) + { + *(double*)dst = *(double*)src; + src += mElemSize; + dst += sizeof(double); + } + } + break; + + case blr_timestamp : + if (adtype != IBPP::adTimestamp) throw LogicExceptionImpl("Array::ReadTo", + _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + decodeTimestamp(*(IBPP::Timestamp*)dst, *(ISC_TIMESTAMP*)src); + src += mElemSize; + dst += sizeof(IBPP::Timestamp); + } + break; + + case blr_sql_date : + if (adtype != IBPP::adDate) throw LogicExceptionImpl("Array::ReadTo", + _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + decodeDate(*(IBPP::Date*)dst, *(ISC_DATE*)src); + src += mElemSize; + dst += sizeof(IBPP::Date); + } + break; + + case blr_sql_time : + if (adtype != IBPP::adTime) throw LogicExceptionImpl("Array::ReadTo", + _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + decodeTime(*(IBPP::Time*)dst, *(ISC_TIME*)src); + src += mElemSize; + dst += sizeof(IBPP::Time); + } + break; + + default : + throw LogicExceptionImpl("Array::ReadTo", _("Unknown sql type.")); + } +} + +void ArrayImpl::WriteFrom(IBPP::ADT adtype, const void* data, int datacount) +{ + if (! mDescribed) + throw LogicExceptionImpl("Array::WriteFrom", _("Array description not set.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Array::WriteFrom", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Array::WriteFrom", _("No Transaction is attached.")); + if (datacount != mElemCount) + throw LogicExceptionImpl("Array::ReadTo", _("Wrong count of array elements")); + + // Read user data and convert types to the mBuffer + int len; + char* src = (char*)data; + char* dst = (char*)mBuffer; + + switch (mDesc.array_desc_dtype) + { + case blr_text : + if (adtype == IBPP::adString) + { + for (int i = 0; i < mElemCount; i++) + { + len = (int)strlen(src); + if (len > mElemSize) len = mElemSize; + strncpy(dst, src, len); + while (len < mElemSize) dst[len++] = ' '; + src += (mElemSize + 1); + dst += mElemSize; + } + } + else if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *dst = *(bool*)src ? 'T' : 'F'; + len = 1; + while (len < mElemSize) dst[len++] = ' '; + src += sizeof(bool); + dst += mElemSize; + } + } + else throw LogicExceptionImpl("Array::WriteFrom", _("Incompatible types.")); + break; + + case blr_varying : + if (adtype == IBPP::adString) + { + for (int i = 0; i < mElemCount; i++) + { + len = (int)strlen(src); + if (len > mElemSize-2) len = mElemSize-2; + strncpy(dst, src, len); + dst[len] = '\0'; + src += (mElemSize - 2 + 1); + dst += mElemSize; + } + } + else if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *(short*)dst = (short)1; + dst[2] = *(bool*)src ? 'T' : 'F'; + src += sizeof(bool); + dst += mElemSize; + } + } + else throw LogicExceptionImpl("Array::WriteFrom", _("Incompatible types.")); + break; + + case blr_short : + if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *(short*)dst = short(*(bool*)src ? 1 : 0); + src += sizeof(bool); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt16) + { + for (int i = 0; i < mElemCount; i++) + { + *(short*)dst = *(short*)src; + src += sizeof(short); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt32) + { + for (int i = 0; i < mElemCount; i++) + { + if (*(long*)src < consts::min16 || *(long*)src > consts::max16) + throw LogicExceptionImpl("Array::WriteFrom", + _("Out of range numeric conversion !")); + *(short*)dst = (short)*(int*)src; + src += sizeof(int); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt64) + { + for (int i = 0; i < mElemCount; i++) + { + if (*(int64_t*)src < consts::min16 || *(int64_t*)src > consts::max16) + throw LogicExceptionImpl("Array::WriteFrom", + _("Out of range numeric conversion !")); + *(short*)dst = (short)*(int64_t*)src; + src += sizeof(int64_t); + dst += mElemSize; + } + } + else if (adtype == IBPP::adFloat) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(short*)dst = + (short)floor(*(float*)src * multiplier + 0.5); + src += sizeof(float); + dst += mElemSize; + } + } + else if (adtype == IBPP::adDouble) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(short*)dst = + (short)floor(*(double*)src * multiplier + 0.5); + src += sizeof(double); + dst += mElemSize; + } + } + else throw LogicExceptionImpl("Array::WriteFrom", _("Incompatible types.")); + break; + + case blr_long : + if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *(long*)dst = *(bool*)src ? 1 : 0; + src += sizeof(bool); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt16) + { + for (int i = 0; i < mElemCount; i++) + { + *(long*)dst = *(short*)src; + src += sizeof(short); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt32) + { + for (int i = 0; i < mElemCount; i++) + { + *(long*)dst = *(long*)src; + src += sizeof(long); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt64) + { + for (int i = 0; i < mElemCount; i++) + { + if (*(int64_t*)src < consts::min32 || *(int64_t*)src > consts::max32) + throw LogicExceptionImpl("Array::WriteFrom", + _("Out of range numeric conversion !")); + *(long*)dst = (long)*(int64_t*)src; + src += sizeof(int64_t); + dst += mElemSize; + } + } + else if (adtype == IBPP::adFloat) + { + // This SQL_INT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(long*)dst = + (long)floor(*(float*)src * multiplier + 0.5); + src += sizeof(float); + dst += mElemSize; + } + } + else if (adtype == IBPP::adDouble) + { + // This SQL_INT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(long*)dst = + (long)floor(*(double*)src * multiplier + 0.5); + src += sizeof(double); + dst += mElemSize; + } + } + else throw LogicExceptionImpl("Array::WriteFrom", _("Incompatible types.")); + break; + + case blr_int64 : + if (adtype == IBPP::adBool) + { + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = *(bool*)src ? 1 : 0; + src += sizeof(bool); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt16) + { + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = *(short*)src; + src += sizeof(short); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt32) + { + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = *(long*)src; + src += sizeof(long); + dst += mElemSize; + } + } + else if (adtype == IBPP::adInt64) + { + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = *(int64_t*)src; + src += sizeof(int64_t); + dst += mElemSize; + } + } + else if (adtype == IBPP::adFloat) + { + // This SQL_INT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = + (int64_t)floor(*(float*)src * multiplier + 0.5); + src += sizeof(float); + dst += mElemSize; + } + } + else if (adtype == IBPP::adDouble) + { + // This SQL_INT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(int64_t*)dst = + (int64_t)floor(*(double*)src * multiplier + 0.5); + src += sizeof(double); + dst += mElemSize; + } + } + else + throw LogicExceptionImpl("Array::WriteFrom", + _("Incompatible types (blr_int64 and ADT %d)."), (int)adtype); + break; + + case blr_float : + if (adtype != IBPP::adFloat || mDesc.array_desc_scale != 0) + throw LogicExceptionImpl("Array::WriteFrom", _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + *(float*)dst = *(float*)src; + src += sizeof(float); + dst += mElemSize; + } + break; + + case blr_double : + if (adtype != IBPP::adDouble) throw LogicExceptionImpl("Array::WriteFrom", + _("Incompatible types.")); + if (mDesc.array_desc_scale != 0) + { + // Round to scale of NUMERIC(x,y) + double multiplier = consts::dscales[-mDesc.array_desc_scale]; + for (int i = 0; i < mElemCount; i++) + { + *(double*)dst = + floor(*(double*)src * multiplier + 0.5) / multiplier; + src += sizeof(double); + dst += mElemSize; + } + } + else + { + for (int i = 0; i < mElemCount; i++) + { + *(double*)dst = *(double*)src; + src += sizeof(double); + dst += mElemSize; + } + } + break; + + case blr_timestamp : + if (adtype != IBPP::adTimestamp) throw LogicExceptionImpl("Array::ReadTo", + _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + encodeTimestamp(*(ISC_TIMESTAMP*)dst, *(IBPP::Timestamp*)src); + src += sizeof(IBPP::Timestamp); + dst += mElemSize; + } + break; + + case blr_sql_date : + if (adtype != IBPP::adDate) throw LogicExceptionImpl("Array::ReadTo", + _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + encodeDate(*(ISC_DATE*)dst, *(IBPP::Date*)src); + src += sizeof(IBPP::Date); + dst += mElemSize; + } + break; + + case blr_sql_time : + if (adtype != IBPP::adTime) throw LogicExceptionImpl("Array::ReadTo", + _("Incompatible types.")); + for (int i = 0; i < mElemCount; i++) + { + encodeTime(*(ISC_TIME*)dst, *(IBPP::Time*)src); + src += sizeof(IBPP::Time); + dst += mElemSize; + } + break; + + default : + throw LogicExceptionImpl("Array::WriteFrom", _("Unknown sql type.")); + } + + IBS status; + ISC_LONG lenbuf = mBufferSize; + (*gds.Call()->m_array_put_slice)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), &mId, &mDesc, mBuffer, &lenbuf); + if (status.Errors()) + throw SQLExceptionImpl(status, "Array::WriteFrom", _("isc_array_put_slice failed.")); + if (lenbuf != mBufferSize) + throw SQLExceptionImpl(status, "Array::WriteFrom", _("Internal buffer size discrepancy.")); +} + +IBPP::Database ArrayImpl::DatabasePtr() const +{ + if (mDatabase == 0) throw LogicExceptionImpl("Array::DatabasePtr", + _("No Database is attached.")); + return mDatabase; +} + +IBPP::Transaction ArrayImpl::TransactionPtr() const +{ + if (mTransaction == 0) throw LogicExceptionImpl("Array::TransactionPtr", + _("No Transaction is attached.")); + return mTransaction; +} + +IBPP::IArray* ArrayImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + return this; +} + +void ArrayImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void ArrayImpl::Init() +{ + ResetId(); + mDescribed = false; + mDatabase = 0; + mTransaction = 0; + mBuffer = 0; + mBufferSize = 0; +} + +void ArrayImpl::SetId(ISC_QUAD* quad) +{ + if (quad == 0) + throw LogicExceptionImpl("ArrayImpl::SetId", _("Null Id reference detected.")); + + memcpy(&mId, quad, sizeof(mId)); + mIdAssigned = true; +} + +void ArrayImpl::GetId(ISC_QUAD* quad) +{ + if (quad == 0) + throw LogicExceptionImpl("ArrayImpl::GetId", _("Null Id reference detected.")); + + memcpy(quad, &mId, sizeof(mId)); +} + +void ArrayImpl::ResetId() +{ + memset(&mId, 0, sizeof(mId)); + mIdAssigned = false; +} + +void ArrayImpl::AllocArrayBuffer() +{ + // Clean previous buffer if any + if (mBuffer != 0) delete [] (char*)mBuffer; + mBuffer = 0; + + // Computes total number of elements in the array or slice + mElemCount = 1; + for (int i = 0; i < mDesc.array_desc_dimensions; i++) + { + mElemCount = mElemCount * + (mDesc.array_desc_bounds[i].array_bound_upper - + mDesc.array_desc_bounds[i].array_bound_lower + 1); + } + + // Allocates a buffer for this count of elements + mElemSize = mDesc.array_desc_length; + if (mDesc.array_desc_dtype == blr_varying) mElemSize += 2; + else if (mDesc.array_desc_dtype == blr_cstring) mElemSize += 1; + mBufferSize = mElemSize * mElemCount; + mBuffer = (void*) new char[mBufferSize]; +} + +void ArrayImpl::AttachDatabaseImpl(DatabaseImpl* database) +{ + if (database == 0) throw LogicExceptionImpl("Array::AttachDatabase", + _("Can't attach a 0 Database object.")); + + if (mDatabase != 0) mDatabase->DetachArrayImpl(this); + mDatabase = database; + mDatabase->AttachArrayImpl(this); +} + +void ArrayImpl::AttachTransactionImpl(TransactionImpl* transaction) +{ + if (transaction == 0) throw LogicExceptionImpl("Array::AttachTransaction", + _("Can't attach a 0 Transaction object.")); + + if (mTransaction != 0) mTransaction->DetachArrayImpl(this); + mTransaction = transaction; + mTransaction->AttachArrayImpl(this); +} + +void ArrayImpl::DetachDatabaseImpl() +{ + if (mDatabase == 0) return; + + mDatabase->DetachArrayImpl(this); + mDatabase = 0; +} + +void ArrayImpl::DetachTransactionImpl() +{ + if (mTransaction == 0) return; + + mTransaction->DetachArrayImpl(this); + mTransaction = 0; +} + +ArrayImpl::ArrayImpl(DatabaseImpl* database, TransactionImpl* transaction) + : mRefCount(0) +{ + Init(); + AttachDatabaseImpl(database); + if (transaction != 0) AttachTransactionImpl(transaction); +} + +ArrayImpl::~ArrayImpl() +{ + try { if (mTransaction != 0) mTransaction->DetachArrayImpl(this); } + catch (...) {} + try { if (mDatabase != 0) mDatabase->DetachArrayImpl(this); } + catch (...) {} + try { if (mBuffer != 0) delete [] (char*)mBuffer; } + catch (...) {} +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/blob.cpp b/stglibs/ibpp.lib/blob.cpp new file mode 100644 index 00000000..cfdd61fe --- /dev/null +++ b/stglibs/ibpp.lib/blob.cpp @@ -0,0 +1,381 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: blob.cpp,v 1.2 2009/03/19 20:00:28 faust Exp $ +// Subject : IBPP, Blob class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void BlobImpl::Open() +{ + if (mHandle != 0) + throw LogicExceptionImpl("Blob::Open", _("Blob already opened.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Blob::Open", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Blob::Open", _("No Transaction is attached.")); + if (! mIdAssigned) + throw LogicExceptionImpl("Blob::Open", _("Blob Id is not assigned.")); + + IBS status; + (*gds.Call()->m_open_blob2)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), &mHandle, &mId, 0, 0); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Open", _("isc_open_blob2 failed.")); + mWriteMode = false; +} + +void BlobImpl::Create() +{ + if (mHandle != 0) + throw LogicExceptionImpl("Blob::Create", _("Blob already opened.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Blob::Create", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Blob::Create", _("No Transaction is attached.")); + + IBS status; + (*gds.Call()->m_create_blob2)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), &mHandle, &mId, 0, 0); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Create", + _("isc_create_blob failed.")); + mIdAssigned = true; + mWriteMode = true; +} + +void BlobImpl::Close() +{ + if (mHandle == 0) return; // Not opened anyway + + IBS status; + (*gds.Call()->m_close_blob)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Close", _("isc_close_blob failed.")); + mHandle = 0; +} + +void BlobImpl::Cancel() +{ + if (mHandle == 0) return; // Not opened anyway + + if (! mWriteMode) + throw LogicExceptionImpl("Blob::Cancel", _("Can't cancel a Blob opened for read")); + + IBS status; + (*gds.Call()->m_cancel_blob)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Cancel", _("isc_cancel_blob failed.")); + mHandle = 0; + mIdAssigned = false; +} + +int BlobImpl::Read(void* buffer, int size) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Blob::Read", _("The Blob is not opened")); + if (mWriteMode) + throw LogicExceptionImpl("Blob::Read", _("Can't read from Blob opened for write")); + if (size < 1 || size > (64*1024-1)) + throw LogicExceptionImpl("Blob::Read", _("Invalid segment size (max 64Kb-1)")); + + IBS status; + unsigned short bytesread; + int result = (*gds.Call()->m_get_segment)(status.Self(), &mHandle, &bytesread, + (unsigned short)size, (char*)buffer); + if (result == isc_segstr_eof) return 0; // Fin du blob + if (result != isc_segment && status.Errors()) + throw SQLExceptionImpl(status, "Blob::Read", _("isc_get_segment failed.")); + return (int)bytesread; +} + +void BlobImpl::Write(const void* buffer, int size) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Blob::Write", _("The Blob is not opened")); + if (! mWriteMode) + throw LogicExceptionImpl("Blob::Write", _("Can't write to Blob opened for read")); + if (size < 1 || size > (64*1024-1)) + throw LogicExceptionImpl("Blob::Write", _("Invalid segment size (max 64Kb-1)")); + + IBS status; + (*gds.Call()->m_put_segment)(status.Self(), &mHandle, + (unsigned short)size, (char*)buffer); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Write", _("isc_put_segment failed.")); +} + +void BlobImpl::Info(int* Size, int* Largest, int* Segments) +{ + char items[] = {isc_info_blob_total_length, + isc_info_blob_max_segment, + isc_info_blob_num_segments}; + + if (mHandle == 0) + throw LogicExceptionImpl("Blob::GetInfo", _("The Blob is not opened")); + + IBS status; + RB result(100); + (*gds.Call()->m_blob_info)(status.Self(), &mHandle, sizeof(items), items, + (short)result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::GetInfo", _("isc_blob_info failed.")); + + if (Size != 0) *Size = result.GetValue(isc_info_blob_total_length); + if (Largest != 0) *Largest = result.GetValue(isc_info_blob_max_segment); + if (Segments != 0) *Segments = result.GetValue(isc_info_blob_num_segments); +} + +void BlobImpl::Save(const std::string& data) +{ + if (mHandle != 0) + throw LogicExceptionImpl("Blob::Save", _("Blob already opened.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Blob::Save", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Blob::Save", _("No Transaction is attached.")); + + IBS status; + (*gds.Call()->m_create_blob2)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), &mHandle, &mId, 0, 0); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Save", + _("isc_create_blob failed.")); + mIdAssigned = true; + mWriteMode = true; + + size_t pos = 0; + size_t len = data.size(); + while (len != 0) + { + size_t blklen = (len < 32*1024-1) ? len : 32*1024-1; + status.Reset(); + (*gds.Call()->m_put_segment)(status.Self(), &mHandle, + (unsigned short)blklen, const_cast(data.data()+pos)); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Save", + _("isc_put_segment failed.")); + pos += blklen; + len -= blklen; + } + + status.Reset(); + (*gds.Call()->m_close_blob)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Save", _("isc_close_blob failed.")); + mHandle = 0; +} + +void BlobImpl::Load(std::string& data) +{ + if (mHandle != 0) + throw LogicExceptionImpl("Blob::Load", _("Blob already opened.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Blob::Load", _("No Database is attached.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Blob::Load", _("No Transaction is attached.")); + if (! mIdAssigned) + throw LogicExceptionImpl("Blob::Load", _("Blob Id is not assigned.")); + + IBS status; + (*gds.Call()->m_open_blob2)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), &mHandle, &mId, 0, 0); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Load", _("isc_open_blob2 failed.")); + mWriteMode = false; + + size_t blklen = 32*1024-1; + data.resize(blklen); + + size_t size = 0; + size_t pos = 0; + for (;;) + { + status.Reset(); + unsigned short bytesread; + int result = (*gds.Call()->m_get_segment)(status.Self(), &mHandle, + &bytesread, (unsigned short)blklen, + const_cast(data.data()+pos)); + if (result == isc_segstr_eof) break; // End of blob + if (result != isc_segment && status.Errors()) + throw SQLExceptionImpl(status, "Blob::Load", _("isc_get_segment failed.")); + + pos += bytesread; + size += bytesread; + data.resize(size + blklen); + } + data.resize(size); + + status.Reset(); + (*gds.Call()->m_close_blob)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Blob::Load", _("isc_close_blob failed.")); + mHandle = 0; +} + +IBPP::Database BlobImpl::DatabasePtr() const +{ + if (mDatabase == 0) throw LogicExceptionImpl("Blob::DatabasePtr", + _("No Database is attached.")); + return mDatabase; +} + +IBPP::Transaction BlobImpl::TransactionPtr() const +{ + if (mTransaction == 0) throw LogicExceptionImpl("Blob::TransactionPtr", + _("No Transaction is attached.")); + return mTransaction; +} + +IBPP::IBlob* BlobImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + return this; +} + +void BlobImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void BlobImpl::Init() +{ + mIdAssigned = false; + mWriteMode = false; + mHandle = 0; + mDatabase = 0; + mTransaction = 0; +} + +void BlobImpl::SetId(ISC_QUAD* quad) +{ + if (mHandle != 0) + throw LogicExceptionImpl("BlobImpl::SetId", _("Can't set Id on an opened BlobImpl.")); + if (quad == 0) + throw LogicExceptionImpl("BlobImpl::SetId", _("Null Id reference detected.")); + + memcpy(&mId, quad, sizeof(mId)); + mIdAssigned = true; +} + +void BlobImpl::GetId(ISC_QUAD* quad) +{ + if (mHandle != 0) + throw LogicExceptionImpl("BlobImpl::GetId", _("Can't get Id on an opened BlobImpl.")); + if (! mWriteMode) + throw LogicExceptionImpl("BlobImpl::GetId", _("Can only get Id of a newly created Blob.")); + if (quad == 0) + throw LogicExceptionImpl("BlobImpl::GetId", _("Null Id reference detected.")); + + memcpy(quad, &mId, sizeof(mId)); +} + +void BlobImpl::AttachDatabaseImpl(DatabaseImpl* database) +{ + if (database == 0) throw LogicExceptionImpl("Blob::AttachDatabase", + _("Can't attach a NULL Database object.")); + + if (mDatabase != 0) mDatabase->DetachBlobImpl(this); + mDatabase = database; + mDatabase->AttachBlobImpl(this); +} + +void BlobImpl::AttachTransactionImpl(TransactionImpl* transaction) +{ + if (transaction == 0) throw LogicExceptionImpl("Blob::AttachTransaction", + _("Can't attach a NULL Transaction object.")); + + if (mTransaction != 0) mTransaction->DetachBlobImpl(this); + mTransaction = transaction; + mTransaction->AttachBlobImpl(this); +} + +void BlobImpl::DetachDatabaseImpl() +{ + if (mDatabase == 0) return; + + mDatabase->DetachBlobImpl(this); + mDatabase = 0; +} + +void BlobImpl::DetachTransactionImpl() +{ + if (mTransaction == 0) return; + + mTransaction->DetachBlobImpl(this); + mTransaction = 0; +} + +BlobImpl::BlobImpl(DatabaseImpl* database, TransactionImpl* transaction) + : mRefCount(0) +{ + Init(); + AttachDatabaseImpl(database); + if (transaction != 0) AttachTransactionImpl(transaction); +} + +BlobImpl::~BlobImpl() +{ + try + { + if (mHandle != 0) + { + if (mWriteMode) Cancel(); + else Close(); + } + } + catch (...) { } + + try { if (mTransaction != 0) mTransaction->DetachBlobImpl(this); } + catch (...) { } + try { if (mDatabase != 0) mDatabase->DetachBlobImpl(this); } + catch (...) { } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/database.cpp b/stglibs/ibpp.lib/database.cpp new file mode 100644 index 00000000..64b705d2 --- /dev/null +++ b/stglibs/ibpp.lib/database.cpp @@ -0,0 +1,483 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: database.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $ +// Subject : IBPP, Database class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void DatabaseImpl::Create(int dialect) +{ + if (mHandle != 0) + throw LogicExceptionImpl("Database::Create", _("Database is already connected.")); + if (mDatabaseName.empty()) + throw LogicExceptionImpl("Database::Create", _("Unspecified database name.")); + if (mUserName.empty()) + throw LogicExceptionImpl("Database::Create", _("Unspecified user name.")); + if (dialect != 1 && dialect != 3) + throw LogicExceptionImpl("Database::Create", _("Only dialects 1 and 3 are supported.")); + + // Build the SQL Create Statement + std::string create; + create.assign("CREATE DATABASE '"); + if (! mServerName.empty()) create.append(mServerName).append(":"); + create.append(mDatabaseName).append("' "); + + create.append("USER '").append(mUserName).append("' "); + if (! mUserPassword.empty()) + create.append("PASSWORD '").append(mUserPassword).append("' "); + + if (! mCreateParams.empty()) create.append(mCreateParams); + + // Call ExecuteImmediate to create the database + isc_tr_handle tr_handle = 0; + IBS status; + (*gds.Call()->m_dsql_execute_immediate)(status.Self(), &mHandle, &tr_handle, + 0, const_cast(create.c_str()), short(dialect), NULL); + if (status.Errors()) + throw SQLExceptionImpl(status, "Database::Create", _("isc_dsql_execute_immediate failed")); + + Disconnect(); +} + +void DatabaseImpl::Connect() +{ + if (mHandle != 0) return; // Already connected + + if (mDatabaseName.empty()) + throw LogicExceptionImpl("Database::Connect", _("Unspecified database name.")); + if (mUserName.empty()) + throw LogicExceptionImpl("Database::Connect", _("Unspecified user name.")); + + // Build a DPB based on the properties + DPB dpb; + dpb.Insert(isc_dpb_user_name, mUserName.c_str()); + dpb.Insert(isc_dpb_password, mUserPassword.c_str()); + if (! mRoleName.empty()) dpb.Insert(isc_dpb_sql_role_name, mRoleName.c_str()); + if (! mCharSet.empty()) dpb.Insert(isc_dpb_lc_ctype, mCharSet.c_str()); + + std::string connect; + if (! mServerName.empty()) + connect.assign(mServerName).append(":"); + connect.append(mDatabaseName); + + IBS status; + (*gds.Call()->m_attach_database)(status.Self(), (short)connect.size(), + const_cast(connect.c_str()), &mHandle, dpb.Size(), dpb.Self()); + if (status.Errors()) + { + mHandle = 0; // Should be, but better be sure... + throw SQLExceptionImpl(status, "Database::Connect", _("isc_attach_database failed")); + } + + // Now, get ODS version information and dialect. + // If ODS major is lower of equal to 9, we reject the connection. + // If ODS major is 10 or higher, this is at least an InterBase 6.x Server + // OR FireBird 1.x Server. + + char items[] = {isc_info_ods_version, + isc_info_db_SQL_dialect, + isc_info_end}; + RB result(100); + + status.Reset(); + (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items, + result.Size(), result.Self()); + if (status.Errors()) + { + status.Reset(); + (*gds.Call()->m_detach_database)(status.Self(), &mHandle); + mHandle = 0; // Should be, but better be sure... + throw SQLExceptionImpl(status, "Database::Connect", _("isc_database_info failed")); + } + + int ODS = result.GetValue(isc_info_ods_version); + if (ODS <= 9) + { + status.Reset(); + (*gds.Call()->m_detach_database)(status.Self(), &mHandle); + mHandle = 0; // Should be, but better be sure... + throw LogicExceptionImpl("Database::Connect", + _("Unsupported Server : wrong ODS version (%d), at least '10' required."), ODS); + } + + mDialect = result.GetValue(isc_info_db_SQL_dialect); + if (mDialect != 1 && mDialect != 3) + { + status.Reset(); + (*gds.Call()->m_detach_database)(status.Self(), &mHandle); + mHandle = 0; // Should be, but better be sure... + throw LogicExceptionImpl("Database::Connect", _("Dialect 1 or 3 required")); + } + + // Now, verify the GDS32.DLL we are using is compatible with the server + if (ODS >= 10 && gds.Call()->mGDSVersion < 60) + { + status.Reset(); + (*gds.Call()->m_detach_database)(status.Self(), &mHandle); + mHandle = 0; // Should be, but better be sure... + throw LogicExceptionImpl("Database::Connect", _("GDS32.DLL version 5 against IBSERVER 6")); + } +} + +void DatabaseImpl::Inactivate() +{ + if (mHandle == 0) return; // Not connected anyway + + IBS status; + + // Rollback any started transaction... + for (unsigned i = 0; i < mTransactions.size(); i++) + { + if (mTransactions[i]->Started()) + mTransactions[i]->Rollback(); + } + + // Cancel all pending event traps + for (unsigned i = 0; i < mEvents.size(); i++) + mEvents[i]->Clear(); + + // Let's detach from all Blobs + while (mBlobs.size() > 0) + mBlobs.back()->DetachDatabaseImpl(); + + // Let's detach from all Arrays + while (mArrays.size() > 0) + mArrays.back()->DetachDatabaseImpl(); + + // Let's detach from all Statements + while (mStatements.size() > 0) + mStatements.back()->DetachDatabaseImpl(); + + // Let's detach from all Transactions + while (mTransactions.size() > 0) + mTransactions.back()->DetachDatabaseImpl(this); + + // Let's detach from all Events + while (mEvents.size() > 0) + mEvents.back()->DetachDatabaseImpl(); +} + +void DatabaseImpl::Disconnect() +{ + if (mHandle == 0) return; // Not connected anyway + + // Put the connection to rest + Inactivate(); + + // Detach from the server + IBS status; + (*gds.Call()->m_detach_database)(status.Self(), &mHandle); + + // Should we throw, set mHandle to 0 first, because Disconnect() may + // be called from Database destructor (keeps the object coherent). + mHandle = 0; + if (status.Errors()) + throw SQLExceptionImpl(status, "Database::Disconnect", _("isc_detach_database failed")); +} + +void DatabaseImpl::Drop() +{ + if (mHandle == 0) + throw LogicExceptionImpl("Database::Drop", _("Database must be connected.")); + + // Put the connection to a rest + Inactivate(); + + IBS vector; + (*gds.Call()->m_drop_database)(vector.Self(), &mHandle); + if (vector.Errors()) + throw SQLExceptionImpl(vector, "Database::Drop", _("isc_drop_database failed")); + + mHandle = 0; +} + +void DatabaseImpl::Info(int* ODSMajor, int* ODSMinor, + int* PageSize, int* Pages, int* Buffers, int* Sweep, + bool* Sync, bool* Reserve) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Database::Info", _("Database is not connected.")); + + char items[] = {isc_info_ods_version, + isc_info_ods_minor_version, + isc_info_page_size, + isc_info_allocation, + isc_info_num_buffers, + isc_info_sweep_interval, + isc_info_forced_writes, + isc_info_no_reserve, + isc_info_end}; + IBS status; + RB result(256); + + status.Reset(); + (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items, + result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Database::Info", _("isc_database_info failed")); + + if (ODSMajor != 0) *ODSMajor = result.GetValue(isc_info_ods_version); + if (ODSMinor != 0) *ODSMinor = result.GetValue(isc_info_ods_minor_version); + if (PageSize != 0) *PageSize = result.GetValue(isc_info_page_size); + if (Pages != 0) *Pages = result.GetValue(isc_info_allocation); + if (Buffers != 0) *Buffers = result.GetValue(isc_info_num_buffers); + if (Sweep != 0) *Sweep = result.GetValue(isc_info_sweep_interval); + if (Sync != 0) + *Sync = result.GetValue(isc_info_forced_writes) == 1 ? true : false; + if (Reserve != 0) + *Reserve = result.GetValue(isc_info_no_reserve) == 1 ? false : true; +} + +void DatabaseImpl::Statistics(int* Fetches, int* Marks, int* Reads, int* Writes) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Database::Statistics", _("Database is not connected.")); + + char items[] = {isc_info_fetches, + isc_info_marks, + isc_info_reads, + isc_info_writes, + isc_info_end}; + IBS status; + RB result(128); + + status.Reset(); + (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items, + result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Database::Statistics", _("isc_database_info failed")); + + if (Fetches != 0) *Fetches = result.GetValue(isc_info_fetches); + if (Marks != 0) *Marks = result.GetValue(isc_info_marks); + if (Reads != 0) *Reads = result.GetValue(isc_info_reads); + if (Writes != 0) *Writes = result.GetValue(isc_info_writes); +} + +void DatabaseImpl::Counts(int* Insert, int* Update, int* Delete, + int* ReadIdx, int* ReadSeq) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Database::Counts", _("Database is not connected.")); + + char items[] = {isc_info_insert_count, + isc_info_update_count, + isc_info_delete_count, + isc_info_read_idx_count, + isc_info_read_seq_count, + isc_info_end}; + IBS status; + RB result(1024); + + status.Reset(); + (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items, + result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Database::Counts", _("isc_database_info failed")); + + if (Insert != 0) *Insert = result.GetCountValue(isc_info_insert_count); + if (Update != 0) *Update = result.GetCountValue(isc_info_update_count); + if (Delete != 0) *Delete = result.GetCountValue(isc_info_delete_count); + if (ReadIdx != 0) *ReadIdx = result.GetCountValue(isc_info_read_idx_count); + if (ReadSeq != 0) *ReadSeq = result.GetCountValue(isc_info_read_seq_count); +} + +void DatabaseImpl::Users(std::vector& users) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Database::Users", _("Database is not connected.")); + + char items[] = {isc_info_user_names, + isc_info_end}; + IBS status; + RB result(8000); + + status.Reset(); + (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items, + result.Size(), result.Self()); + if (status.Errors()) + { + status.Reset(); + throw SQLExceptionImpl(status, "Database::Users", _("isc_database_info failed")); + } + + users.clear(); + char* p = result.Self(); + while (*p == isc_info_user_names) + { + p += 3; // Get to the length byte (there are two undocumented bytes which we skip) + int len = (int)(*p); + ++p; // Get to the first char of username + if (len != 0) users.push_back(std::string().append(p, len)); + p += len; // Skip username + } + return; +} + +IBPP::IDatabase* DatabaseImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + return this; +} + +void DatabaseImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void DatabaseImpl::AttachTransactionImpl(TransactionImpl* tr) +{ + if (tr == 0) + throw LogicExceptionImpl("Database::AttachTransaction", + _("Transaction object is null.")); + + mTransactions.push_back(tr); +} + +void DatabaseImpl::DetachTransactionImpl(TransactionImpl* tr) +{ + if (tr == 0) + throw LogicExceptionImpl("Database::DetachTransaction", + _("ITransaction object is null.")); + + mTransactions.erase(std::find(mTransactions.begin(), mTransactions.end(), tr)); +} + +void DatabaseImpl::AttachStatementImpl(StatementImpl* st) +{ + if (st == 0) + throw LogicExceptionImpl("Database::AttachStatement", + _("Can't attach a null Statement object.")); + + mStatements.push_back(st); +} + +void DatabaseImpl::DetachStatementImpl(StatementImpl* st) +{ + if (st == 0) + throw LogicExceptionImpl("Database::DetachStatement", + _("Can't detach a null Statement object.")); + + mStatements.erase(std::find(mStatements.begin(), mStatements.end(), st)); +} + +void DatabaseImpl::AttachBlobImpl(BlobImpl* bb) +{ + if (bb == 0) + throw LogicExceptionImpl("Database::AttachBlob", + _("Can't attach a null Blob object.")); + + mBlobs.push_back(bb); +} + +void DatabaseImpl::DetachBlobImpl(BlobImpl* bb) +{ + if (bb == 0) + throw LogicExceptionImpl("Database::DetachBlob", + _("Can't detach a null Blob object.")); + + mBlobs.erase(std::find(mBlobs.begin(), mBlobs.end(), bb)); +} + +void DatabaseImpl::AttachArrayImpl(ArrayImpl* ar) +{ + if (ar == 0) + throw LogicExceptionImpl("Database::AttachArray", + _("Can't attach a null Array object.")); + + mArrays.push_back(ar); +} + +void DatabaseImpl::DetachArrayImpl(ArrayImpl* ar) +{ + if (ar == 0) + throw LogicExceptionImpl("Database::DetachArray", + _("Can't detach a null Array object.")); + + mArrays.erase(std::find(mArrays.begin(), mArrays.end(), ar)); +} + +void DatabaseImpl::AttachEventsImpl(EventsImpl* ev) +{ + if (ev == 0) + throw LogicExceptionImpl("Database::AttachEventsImpl", + _("Can't attach a null Events object.")); + + mEvents.push_back(ev); +} + +void DatabaseImpl::DetachEventsImpl(EventsImpl* ev) +{ + if (ev == 0) + throw LogicExceptionImpl("Database::DetachEventsImpl", + _("Can't detach a null Events object.")); + + mEvents.erase(std::find(mEvents.begin(), mEvents.end(), ev)); +} + +DatabaseImpl::DatabaseImpl(const std::string& ServerName, const std::string& DatabaseName, + const std::string& UserName, const std::string& UserPassword, + const std::string& RoleName, const std::string& CharSet, + const std::string& CreateParams) : + + mRefCount(0), mHandle(0), + mServerName(ServerName), mDatabaseName(DatabaseName), + mUserName(UserName), mUserPassword(UserPassword), mRoleName(RoleName), + mCharSet(CharSet), mCreateParams(CreateParams), + mDialect(3) +{ +} + +DatabaseImpl::~DatabaseImpl() +{ + try { if (Connected()) Disconnect(); } + catch(...) { } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/date.cpp b/stglibs/ibpp.lib/date.cpp new file mode 100644 index 00000000..f4838240 --- /dev/null +++ b/stglibs/ibpp.lib/date.cpp @@ -0,0 +1,209 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: date.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $ +// Subject : IBPP, Date class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include // Can't use thanks to MSVC6 buggy library + +using namespace ibpp_internals; + +void IBPP::Date::Today() +{ + time_t systime = time(0); + tm* loctime = localtime(&systime); + + if (! IBPP::itod(&mDate, loctime->tm_year + 1900, + loctime->tm_mon + 1, loctime->tm_mday)) + throw LogicExceptionImpl("Date::Today", _("Out of range")); +} + +void IBPP::Date::SetDate(int dt) +{ + if (! IBPP::dtoi(dt, 0, 0, 0)) + throw LogicExceptionImpl("Date::SetDate", _("Out of range")); + mDate = dt; +} + +void IBPP::Date::SetDate(int year, int month, int day) +{ + if (! IBPP::itod(&mDate, year, month, day)) + throw LogicExceptionImpl("Date::SetDate", _("Out of range")); +} + +void IBPP::Date::GetDate(int& year, int& month, int& day) const +{ + if (! IBPP::dtoi(mDate, &year, &month, &day)) + throw LogicExceptionImpl("Date::GetDate", _("Out of range")); +} + +int IBPP::Date::Year() const +{ + int year; + if (! IBPP::dtoi(mDate, &year, 0, 0)) + throw LogicExceptionImpl("Date::Year", _("Out of range")); + return year; +} + +int IBPP::Date::Month() const +{ + int month; + if (! IBPP::dtoi(mDate, 0, &month, 0)) + throw LogicExceptionImpl("Date::Month", _("Out of range")); + return month; +} + +int IBPP::Date::Day() const +{ + int day; + if (! IBPP::dtoi(mDate, 0, 0, &day)) + throw LogicExceptionImpl("Date::Day", _("Out of range")); + return day; +} + +void IBPP::Date::Add(int days) +{ + int newdate = mDate + days; // days can be signed + if (! IBPP::dtoi(newdate, 0, 0, 0)) + throw LogicExceptionImpl("Date::Add()", _("Out of range")); + mDate = newdate; +} + +void IBPP::Date::StartOfMonth() +{ + int year, month; + if (! IBPP::dtoi(mDate, &year, &month, 0)) + throw LogicExceptionImpl("Date::StartOfMonth()", _("Out of range")); + if (! IBPP::itod(&mDate, year, month, 1)) // First of same month + throw LogicExceptionImpl("Date::StartOfMonth()", _("Out of range")); +} + +void IBPP::Date::EndOfMonth() +{ + int year, month; + if (! IBPP::dtoi(mDate, &year, &month, 0)) + throw LogicExceptionImpl("Date::EndOfMonth()", _("Out of range")); + if (++month > 12) { month = 1; year++; } + if (! IBPP::itod(&mDate, year, month, 1)) // First of next month + throw LogicExceptionImpl("Date::EndOfMonth()", _("Out of range")); + mDate--; // Last day of original month, all weird cases accounted for +} + +IBPP::Date::Date(int year, int month, int day) +{ + SetDate(year, month, day); +} + +IBPP::Date::Date(const IBPP::Date& copied) +{ + mDate = copied.mDate; +} + +IBPP::Date& IBPP::Date::operator=(const IBPP::Timestamp& assigned) +{ + mDate = assigned.GetDate(); + return *this; +} + +IBPP::Date& IBPP::Date::operator=(const IBPP::Date& assigned) +{ + mDate = assigned.mDate; + return *this; +} + +// The following date calculations were inspired by web pages found on +// Peter Baum web homepage at 'http://www.capecod.net/~pbaum/'. +// His contact info is at : 'http://home.capecod.net/~pbaum/contact.htm'. +// Please, understand that Peter Baum is not related to this IBPP project. +// So __please__, do not contact him regarding IBPP matters. + +// Take a date, in its integer format as used in IBPP internals and splits +// it in year (4 digits), month (1-12), day (1-31) + +bool IBPP::dtoi (int date, int *y, int *m, int *d) +{ + int RataDie, Z, H, A, B, C; + int year, month, day; + + // Validity control. + if (date < IBPP::MinDate || date > IBPP::MaxDate) + return false; + + // The "Rata Die" is the date specified as the number of days elapsed since + // 31 Dec of year 0. So 1 Jan 0001 is 1. + + RataDie = date + ibpp_internals::consts::Dec31_1899; // Because IBPP sets the '0' on 31 Dec 1899. + + Z = RataDie + 306; + H = 100*Z - 25; + A = H/3652425; + B = A - A/4; + year = (100*B + H) / 36525; + C = B + Z - 365*year - year / 4; + month = (5*C + 456) / 153; + day = C - (153*month - 457) / 5; + if (month > 12) { year += 1; month -= 12; } + + if (y != 0) *y = (int)year; + if (m != 0) *m = (int)month; + if (d != 0) *d = (int)day; + + return true; +} + +// Take a date from its components year, month, day and convert it to the +// integer representation used internally in IBPP. + +bool IBPP::itod (int *pdate, int year, int month, int day) +{ + int RataDie, result; + int y, m, d; + + d = day; m = month; y = year; + if (m < 3) { m += 12; y -= 1; } + RataDie = d + (153*m - 457) / 5 + 365*y + y/4 - y/100 + y/400 - 306; + + result = RataDie - ibpp_internals::consts::Dec31_1899; // Because IBPP sets the '0' on 31 Dec 1899 + + // Validity control + if (result < IBPP::MinDate || result > IBPP::MaxDate) + return false; + + *pdate = result; + return true; +} + +// Eof diff --git a/stglibs/ibpp.lib/dbkey.cpp b/stglibs/ibpp.lib/dbkey.cpp new file mode 100644 index 00000000..080b4d77 --- /dev/null +++ b/stglibs/ibpp.lib/dbkey.cpp @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: dbkey.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $ +// Subject : IBPP, DBKey class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include +#include +#include + +using namespace ibpp_internals; + +// Private implementation + +// Public implementation + +void IBPP::DBKey::Clear() +{ + mDBKey.erase(); + mString.erase(); +} + +void IBPP::DBKey::SetKey(const void* key, int size) +{ + if (key == 0) + throw LogicExceptionImpl("IBPP::DBKey::SetKey", _("Null DBKey reference detected.")); + if (size <= 0 || ((size >> 3) << 3) != size) + throw LogicExceptionImpl("IBPP::DBKey::SetKey", _("Invalid DBKey size.")); + + mDBKey.assign((const char*)key, (size_t)size); + mString.erase(); +} + +void IBPP::DBKey::GetKey(void* key, int size) const +{ + if (mDBKey.empty()) + throw LogicExceptionImpl("IBPP::DBKey::GetKey", _("DBKey not assigned.")); + if (key == 0) + throw LogicExceptionImpl("IBPP::DBKey::GetKey", _("Null DBKey reference detected.")); + if (size != (int)mDBKey.size()) + throw LogicExceptionImpl("IBPP::DBKey::GetKey", _("Incompatible DBKey size detected.")); + + mDBKey.copy((char*)key, mDBKey.size()); +} + +const char* IBPP::DBKey::AsString() const +{ + if (mDBKey.empty()) + throw LogicExceptionImpl("IBPP::DBKey::GetString", _("DBKey not assigned.")); + + if (mString.empty()) + { + std::ostringstream hexkey; + hexkey.setf(std::ios::hex, std::ios::basefield); + hexkey.setf(std::ios::uppercase); + + const uint32_t* key = reinterpret_cast(mDBKey.data()); + int n = (int)mDBKey.size() / 8; + for (int i = 0; i < n; i++) + { + if (i != 0) hexkey<< "-"; + hexkey<< std::setw(4)<< key[i*2]<< ":"; + hexkey<< std::setw(8)<< key[i*2+1]; + } + + mString = hexkey.str(); + } + + return mString.c_str(); +} + +IBPP::DBKey::DBKey(const DBKey& copied) +{ + mDBKey = copied.mDBKey; + mString = copied.mString; +} + +IBPP::DBKey& IBPP::DBKey::operator=(const IBPP::DBKey& assigned) +{ + mDBKey = assigned.mDBKey; + mString = assigned.mString; + return *this; +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/deps b/stglibs/ibpp.lib/deps new file mode 100644 index 00000000..a15bf189 --- /dev/null +++ b/stglibs/ibpp.lib/deps @@ -0,0 +1,76 @@ +array.o: array.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +blob.o: blob.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +database.o: database.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +date.o: date.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +dbkey.o: dbkey.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +_dpb.o: _dpb.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +events.o: events.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +exception.o: exception.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +_ibpp.o: _ibpp.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +_ibs.o: _ibs.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +_rb.o: _rb.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +row.o: row.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +service.o: service.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +_spb.o: _spb.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +statement.o: statement.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +time.o: time.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +_tpb.o: _tpb.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +transaction.o: transaction.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< +user.o: user.cpp _ibpp.h ibpp.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + ibase.h iberror.h Makefile ../../Makefile.conf + $(CC) -g3 -W -Wall -I/usr/local/include -DARCH_LE -fPIC -DIBPP_LINUX -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -I ./ -DDEBUG -DLINUX -DSTG_TIME -c $< diff --git a/stglibs/ibpp.lib/events.cpp b/stglibs/ibpp.lib/events.cpp new file mode 100644 index 00000000..14ce1baa --- /dev/null +++ b/stglibs/ibpp.lib/events.cpp @@ -0,0 +1,372 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: events.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $ +// Subject : IBPP, internal EventsImpl class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +// SPECIAL WARNING COMMENT (by Olivier Mascia, 2000 Nov 12) +// The way this source file handles events is not publicly documented, in +// the ibase.h header file or in the IB 6.0 documentation. This documentation +// suggests to use the API isc_event_block to construct vectors of events. +// Unfortunately, this API takes a variable number of parameters to specify +// the list of event names. In addition, the documentation warn on not using +// more than 15 names. This is a sad limitation, partly because the maximum +// number of parameters safely processed in such an API is very compiler +// dependant and also because isc_event_counts() is designed to return counts +// through the IB status vector which is a vector of 20 32-bits integers ! +// From reverse engineering of the isc_event_block() API in +// source file jrd/alt.c (as available on fourceforge.net/project/InterBase as +// of 2000 Nov 12), it looks like the internal format of those EPB is simple. +// An EPB starts by a byte with value 1. A version identifier of some sort. +// Then a small cluster is used for each event name. The cluster starts with +// a byte for the length of the event name (no final '\0'). Followed by the N +// characters of the name itself (no final '\0'). The cluster ends with 4 bytes +// preset to 0. +// +// SPECIAL CREDIT (July 2004) : this is a complete re-implementation of this +// class, directly based on work by Val Samko. +// The whole event handling has then be completely redesigned, based on the old +// EPB class to bring up the current IBPP::Events implementation. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +using namespace ibpp_internals; + +const size_t EventsImpl::MAXEVENTNAMELEN = 127; + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void EventsImpl::Add(const std::string& eventname, IBPP::EventInterface* objref) +{ + if (eventname.size() == 0) + throw LogicExceptionImpl("Events::Add", _("Zero length event names not permitted")); + if (eventname.size() > MAXEVENTNAMELEN) + throw LogicExceptionImpl("Events::Add", _("Event name is too long")); + if ((mEventBuffer.size() + eventname.length() + 5) > 32766) // max signed 16 bits integer minus one + throw LogicExceptionImpl("Events::Add", + _("Can't add this event, the events list would overflow IB/FB limitation")); + + Cancel(); + + // 1) Alloc or grow the buffers + size_t prev_buffer_size = mEventBuffer.size(); + size_t needed = ((prev_buffer_size==0) ? 1 : 0) + eventname.length() + 5; + // Initial alloc will require one more byte, we need 4 more bytes for + // the count itself, and one byte for the string length prefix + + mEventBuffer.resize(mEventBuffer.size() + needed); + mResultsBuffer.resize(mResultsBuffer.size() + needed); + if (prev_buffer_size == 0) + mEventBuffer[0] = mResultsBuffer[0] = 1; // First byte is a 'one'. Documentation ?? + + // 2) Update the buffers (append) + { + Buffer::iterator it = mEventBuffer.begin() + + ((prev_buffer_size==0) ? 1 : prev_buffer_size); // Byte after current content + *(it++) = static_cast(eventname.length()); + it = std::copy(eventname.begin(), eventname.end(), it); + // We initialize the counts to (uint32_t)(-1) to initialize properly, see FireActions() + *(it++) = -1; *(it++) = -1; *(it++) = -1; *it = -1; + } + + // copying new event to the results buffer to keep event_buffer_ and results_buffer_ consistant, + // otherwise we might get a problem in `FireActions` + // Val Samko, val@digiways.com + std::copy(mEventBuffer.begin() + prev_buffer_size, + mEventBuffer.end(), mResultsBuffer.begin() + prev_buffer_size); + + // 3) Alloc or grow the objref array and update the objref array (append) + mObjectReferences.push_back(objref); + + Queue(); +} + +void EventsImpl::Drop(const std::string& eventname) +{ + if (eventname.size() == 0) + throw LogicExceptionImpl("EventsImpl::Drop", _("Zero length event names not permitted")); + if (eventname.size() > MAXEVENTNAMELEN) + throw LogicExceptionImpl("EventsImpl::Drop", _("Event name is too long")); + + if (mEventBuffer.size() <= 1) return; // Nothing to do, but not an error + + Cancel(); + + // 1) Find the event in the buffers + typedef EventBufferIterator EventIterator; + EventIterator eit(mEventBuffer.begin()+1); + EventIterator rit(mResultsBuffer.begin()+1); + + for (ObjRefs::iterator oit = mObjectReferences.begin(); + oit != mObjectReferences.end(); + ++oit, ++eit, ++rit) + { + if (eventname != eit.get_name()) continue; + + // 2) Event found, remove it + mEventBuffer.erase(eit.begin(), eit.end()); + mResultsBuffer.erase(rit.begin(), rit.end()); + mObjectReferences.erase(oit); + break; + } + + Queue(); +} + +void EventsImpl::List(std::vector& events) +{ + events.clear(); + + if (mEventBuffer.size() <= 1) return; // Nothing to do, but not an error + + typedef EventBufferIterator EventIterator; + EventIterator eit(mEventBuffer.begin()+1); + + for (ObjRefs::iterator oit = mObjectReferences.begin(); + oit != mObjectReferences.end(); + ++oit, ++eit) + { + events.push_back(eit.get_name()); + } +} + +void EventsImpl::Clear() +{ + Cancel(); + + mObjectReferences.clear(); + mEventBuffer.clear(); + mResultsBuffer.clear(); +} + +void EventsImpl::Dispatch() +{ + // If no events registered, nothing to do of course. + if (mEventBuffer.size() == 0) return; + + // Let's fire the events actions for all the events which triggered, if any, and requeue. + FireActions(); + Queue(); +} + +IBPP::Database EventsImpl::DatabasePtr() const +{ + if (mDatabase == 0) throw LogicExceptionImpl("Events::DatabasePtr", + _("No Database is attached.")); + return mDatabase; +} + +IBPP::IEvents* EventsImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + return this; +} + +void EventsImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void EventsImpl::Queue() +{ + if (! mQueued) + { + if (mDatabase->GetHandle() == 0) + throw LogicExceptionImpl("EventsImpl::Queue", + _("Database is not connected")); + + IBS vector; + mTrapped = false; + mQueued = true; + (*gds.Call()->m_que_events)(vector.Self(), mDatabase->GetHandlePtr(), &mId, + short(mEventBuffer.size()), &mEventBuffer[0], + (isc_callback)EventHandler, (char*)this); + + if (vector.Errors()) + { + mId = 0; // Should be, but better be safe + mQueued = false; + throw SQLExceptionImpl(vector, "EventsImpl::Queue", + _("isc_que_events failed")); + } + } +} + +void EventsImpl::Cancel() +{ + if (mQueued) + { + if (mDatabase->GetHandle() == 0) throw LogicExceptionImpl("EventsImpl::Cancel", + _("Database is not connected")); + + IBS vector; + + // A call to cancel_events will call *once* the handler routine, even + // though no events had fired. This is why we first set mEventsQueued + // to false, so that we can be sure to dismiss those unwanted callbacks + // subsequent to the execution of isc_cancel_events(). + mTrapped = false; + mQueued = false; + (*gds.Call()->m_cancel_events)(vector.Self(), mDatabase->GetHandlePtr(), &mId); + + if (vector.Errors()) + { + mQueued = true; // Need to restore this as cancel failed + throw SQLExceptionImpl(vector, "EventsImpl::Cancel", + _("isc_cancel_events failed")); + } + + mId = 0; // Should be, but better be safe + } +} + +void EventsImpl::FireActions() +{ + if (mTrapped) + { + typedef EventBufferIterator EventIterator; + EventIterator eit(mEventBuffer.begin()+1); + EventIterator rit(mResultsBuffer.begin()+1); + + for (ObjRefs::iterator oit = mObjectReferences.begin(); + oit != mObjectReferences.end(); + ++oit, ++eit, ++rit) + { + if (eit == EventIterator(mEventBuffer.end()) + || rit == EventIterator(mResultsBuffer.end())) + throw LogicExceptionImpl("EventsImpl::FireActions", _("Internal buffer size error")); + uint32_t vnew = rit.get_count(); + uint32_t vold = eit.get_count(); + if (vnew > vold) + { + // Fire the action + try + { + (*oit)->ibppEventHandler(this, eit.get_name(), (int)(vnew - vold)); + } + catch (...) + { + std::copy(rit.begin(), rit.end(), eit.begin()); + throw; + } + std::copy(rit.begin(), rit.end(), eit.begin()); + } + // This handles initialization too, where vold == (uint32_t)(-1) + // Thanks to M. Hieke for this idea and related initialization to (-1) + if (vnew != vold) + std::copy(rit.begin(), rit.end(), eit.begin()); + } + } +} + +// This function must keep this prototype to stay compatible with +// what isc_que_events() expects + +void EventsImpl::EventHandler(const char* object, short size, const char* tmpbuffer) +{ + // >>>>> This method is a STATIC member !! <<<<< + // Consider this method as a kind of "interrupt handler". It should do as + // few work as possible as quickly as possible and then return. + // Never forget: this is called by the Firebird client code, on *some* + // thread which might not be (and won't probably be) any of your application + // thread. This function is to be considered as an "interrupt-handler" of a + // hardware driver. + + // There can be spurious calls to EventHandler from FB internal. We must + // dismiss those calls. + if (object == 0 || size == 0 || tmpbuffer == 0) return; + + EventsImpl* evi = (EventsImpl*)object; // Ugly, but wanted, c-style cast + + if (evi->mQueued) + { + try + { + char* rb = &evi->mResultsBuffer[0]; + if (evi->mEventBuffer.size() < (unsigned)size) size = (short)evi->mEventBuffer.size(); + for (int i = 0; i < size; i++) + rb[i] = tmpbuffer[i]; + evi->mTrapped = true; + evi->mQueued = false; + } + catch (...) { } + } +} + +void EventsImpl::AttachDatabaseImpl(DatabaseImpl* database) +{ + if (database == 0) throw LogicExceptionImpl("EventsImpl::AttachDatabase", + _("Can't attach a null Database object.")); + + if (mDatabase != 0) mDatabase->DetachEventsImpl(this); + mDatabase = database; + mDatabase->AttachEventsImpl(this); +} + +void EventsImpl::DetachDatabaseImpl() +{ + if (mDatabase == 0) return; + + mDatabase->DetachEventsImpl(this); + mDatabase = 0; +} + +EventsImpl::EventsImpl(DatabaseImpl* database) + : mRefCount(0) +{ + mDatabase = 0; + mId = 0; + mQueued = mTrapped = false; + AttachDatabaseImpl(database); +} + +EventsImpl::~EventsImpl() +{ + try { Clear(); } + catch (...) { } + + try { if (mDatabase != 0) mDatabase->DetachEventsImpl(this); } + catch (...) { } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/exception.cpp b/stglibs/ibpp.lib/exception.cpp new file mode 100644 index 00000000..aa47e285 --- /dev/null +++ b/stglibs/ibpp.lib/exception.cpp @@ -0,0 +1,351 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: exception.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $ +// Subject : IBPP, Initialization of the library +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include +#include + +using namespace ibpp_internals; + +// None of the exception classes methods are implemented inline, because they +// are all declared throw() and Borland compilers at least, but possibly some +// others emit a warning like "W8026 - functions with exception specification +// are not expanded inline". Nothing we have to worry about, but we don't want +// people concerned by such warnings. + +IBPP::Exception::~Exception() throw() +{ +} + +IBPP::LogicException::~LogicException() throw() +{ +} + +IBPP::SQLException::~SQLException() throw() +{ +} + +IBPP::WrongType::~WrongType() throw() +{ +} + +// +// (((((((( ExceptionBase Implementation )))))))) +// + +void ExceptionBase::buildErrorMessage(const char* message) +{ + if (! mContext.empty()) + mWhat.append(_("Context: ")).append(mContext).append("\n"); + + if (message != 0 && *message != 0 ) + mWhat.append(_("Message: ")).append(message).append("\n"); + + mWhat.append("\n"); +} + +void ExceptionBase::raise(const std::string& context, const char* message, va_list argptr) +{ + mContext.assign(context); + + if (message != 0) + { + char buffer[1024]; +#if defined(_MSC_VER) || defined(__DMC__) + _vsnprintf(buffer, sizeof(buffer)-1, message, argptr); +#else + vsnprintf(buffer, sizeof(buffer)-1, message, argptr); +#endif + buffer[sizeof(buffer)-1] = 0; + + buildErrorMessage(buffer); + } + else + buildErrorMessage(0); +} + +ExceptionBase::ExceptionBase() throw() +{ +} + +ExceptionBase::ExceptionBase(const ExceptionBase& copied) throw() +{ + mContext = copied.mContext; + mWhat = copied.mWhat; +} + +ExceptionBase& ExceptionBase::operator=(const ExceptionBase& copied) throw() +{ + mContext = copied.mContext; + mWhat = copied.mWhat; + return *this; +} + +ExceptionBase::ExceptionBase(const std::string& context, + const char* message, ...) throw() +{ + va_list argptr; + va_start(argptr, message); + mWhat.assign("*** IBPP::Exception ***\n"); + raise(context, message, argptr); + va_end(argptr); +} + +ExceptionBase::~ExceptionBase() throw() +{ +} + +const char* ExceptionBase::Origin() const throw() +{ + return mContext.c_str(); +} + +const char* ExceptionBase::ErrorMessage() const throw() +{ + return mWhat.c_str(); +} + +const char* ExceptionBase::what() const throw() +{ + return mWhat.c_str(); +} + +// (((((((( LogicExceptionImpl Implementation )))))))) + +// The following constructors are small and could be inlined, but for object +// code compacity of the library it is much better to have them non-inlined. +// The amount of code generated by compilers for a throw is well-enough. + +LogicExceptionImpl::LogicExceptionImpl() throw() + : ExceptionBase() +{ +} + +LogicExceptionImpl::LogicExceptionImpl(const LogicExceptionImpl& copied) throw() + : IBPP::LogicException(), ExceptionBase(copied) +{ +} + +LogicExceptionImpl& LogicExceptionImpl::operator=(const LogicExceptionImpl& copied) throw() +{ + ExceptionBase::operator=(copied); + return *this; +} + +LogicExceptionImpl::LogicExceptionImpl(const std::string& context, + const char* message, ...) throw() +{ + va_list argptr; + va_start(argptr, message); + mWhat.assign("*** IBPP::LogicException ***\n"); + raise(context, message, argptr); + va_end(argptr); +} + +LogicExceptionImpl::~LogicExceptionImpl() throw () +{ +} + +const char* LogicExceptionImpl::Origin() const throw() +{ + return ExceptionBase::Origin(); +} + +const char* LogicExceptionImpl::ErrorMessage() const throw() +{ + return ExceptionBase::what(); +} + +const char* LogicExceptionImpl::what() const throw() +{ + return ExceptionBase::what(); +} + +// (((((((( SQLExceptionImpl Implementation )))))))) + +SQLExceptionImpl::SQLExceptionImpl() throw() + : ExceptionBase(), mSqlCode(0), mEngineCode(0) +{ +} + +SQLExceptionImpl::SQLExceptionImpl(const SQLExceptionImpl& copied) throw() + : IBPP::SQLException(), ExceptionBase(copied), mSqlCode(copied.mSqlCode), + mEngineCode(copied.mEngineCode) +{ +} + +SQLExceptionImpl& SQLExceptionImpl::operator=(const SQLExceptionImpl& copied) throw() +{ + ExceptionBase::operator=(copied); + mSqlCode = copied.mSqlCode; + mEngineCode = copied.mEngineCode; + return *this; +} + +SQLExceptionImpl::SQLExceptionImpl(const IBS& status, const std::string& context, + const char* message, ...) throw() +{ + va_list argptr; + va_start(argptr, message); + mWhat.assign("*** IBPP::SQLException ***\n"); + raise(context, message, argptr); + va_end(argptr); + mSqlCode = status.SqlCode(); + mEngineCode = status.EngineCode(); + mWhat.append(status.ErrorMessage()); +} + +SQLExceptionImpl::~SQLExceptionImpl() throw () +{ +} + +const char* SQLExceptionImpl::Origin() const throw() +{ + return ExceptionBase::Origin(); +} + +const char* SQLExceptionImpl::ErrorMessage() const throw() +{ + return ExceptionBase::what(); +} + +const char* SQLExceptionImpl::what() const throw() +{ + return ExceptionBase::what(); +} + +int SQLExceptionImpl::SqlCode() const throw() +{ + return mSqlCode; +} + +int SQLExceptionImpl::EngineCode() const throw() +{ + return mEngineCode; +} + +// (((((((( WrongTypeImpl Implementation )))))))) + +// The following constructors are small and could be inlined, but for object +// code compacity of the library it is much better to have them non-inlined. +// The amount of code generated by compilers for a throw is well-enough. + +WrongTypeImpl::WrongTypeImpl() throw() + : IBPP::WrongType(), ExceptionBase() +{ +} + +WrongTypeImpl::WrongTypeImpl(const WrongTypeImpl& copied) throw() + : IBPP::WrongType(), ExceptionBase(copied) +{ +} + +WrongTypeImpl& WrongTypeImpl::operator=(const WrongTypeImpl& copied) throw() +{ + ExceptionBase::operator=(copied); + return *this; +} + +WrongTypeImpl::WrongTypeImpl(const std::string& context, int sqlType, IITYPE varType, + const char* message, ...) throw() +{ + va_list argptr; + va_start(argptr, message); + mWhat.assign("*** IBPP::WrongType ***\n"); + raise(context, message, argptr); + va_end(argptr); + + std::string info; + switch (sqlType & ~1) + { + case SQL_TEXT : info.append("CHAR"); break; + case SQL_VARYING : info.append("VARCHAR"); break; + case SQL_SHORT : info.append("SMALLINT"); break; + case SQL_LONG : info.append("INTEGER"); break; + case SQL_INT64 : info.append("BIGINT"); break; + case SQL_FLOAT : info.append("FLOAT"); break; + case SQL_DOUBLE : info.append("DOUBLE"); break; + case SQL_TIMESTAMP : info.append("TIMESTAMP"); break; + case SQL_TYPE_DATE : info.append("DATE"); break; + case SQL_TYPE_TIME : info.append("TIME"); break; + case SQL_BLOB : info.append("BLOB"); break; + case SQL_ARRAY : info.append("ARRAY"); break; + } + info.append(" ").append(_(" and ")).append(" "); + switch (varType) + { + case ivArray : info.append("Array"); break; + case ivBlob : info.append("Blob"); break; + case ivDate : info.append("Date"); break; + case ivTime : info.append("Time"); break; + case ivTimestamp : info.append("Timestamp"); break; + case ivString : info.append("std::string"); break; + case ivInt16 : info.append("int16_t"); break; + case ivInt32 : info.append("int32_t"); break; + case ivInt64 : info.append("int64_t"); break; + case ivFloat : info.append("float"); break; + case ivDouble : info.append("double"); break; + case ivBool : info.append("bool"); break; + case ivDBKey : info.append("DBKey"); break; + case ivByte : info.append("int8_t"); break; + } + mWhat.append(info).append("\n"); +} + +WrongTypeImpl::~WrongTypeImpl() throw () +{ +} + +const char* WrongTypeImpl::Origin() const throw() +{ + return ExceptionBase::Origin(); +} + +const char* WrongTypeImpl::ErrorMessage() const throw() +{ + return ExceptionBase::what(); +} + +const char* WrongTypeImpl::what() const throw() +{ + return ExceptionBase::what(); +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/ibase.h b/stglibs/ibpp.lib/ibase.h new file mode 100644 index 00000000..c108aace --- /dev/null +++ b/stglibs/ibpp.lib/ibase.h @@ -0,0 +1,2851 @@ +/* + * MODULE: ibase.h + * DESCRIPTION: OSRI entrypoints and defines + * + * The contents of this file are subject to the Interbase Public + * License Version 1.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy + * of the License at http://www.Inprise.com/IPL.html + * + * Software distributed under the License is distributed on an + * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express + * or implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code was created by Inprise Corporation + * and its predecessors. Portions created by Inprise Corporation are + * Copyright (C) Inprise Corporation. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * Added TCP_NO_DELAY option for superserver on Linux + * FSG 16.03.2001 + * 2001.07.28: John Bellardo: Added blr_skip + * 2001.09.18: Ann Harrison: New info codes + * 17-Oct-2001 Mike Nordell: CPU affinity + * 2001-04-16 Paul Beach: ISC_TIME_SECONDS_PRECISION_SCALE modified for HP10 + * Compiler Compatibility + * 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete ports: + * - EPSON, XENIX, MAC (MAC_AUX), Cray and OS/2 + * 2002.10.29 Nickolay Samofatov: Added support for savepoints + * + * 2002.10.29 Sean Leyne - Removed support for obsolete IPX/SPX Protocol + * + */ +/* +$Id: ibase.h,v 1.2 2007/05/17 08:39:25 faust Exp $ + */ + +#ifndef JRD_IBASE_H +#define JRD_IBASE_H + + +/* + * + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. You may obtain a copy of the Licence at + * http://www.gnu.org/copyleft/gpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Relevant for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + * + * Contributor(s): + * Mike Nordel + * Mark O'Donohue + * + * + * $Id: ibase.h,v 1.2 2007/05/17 08:39:25 faust Exp $ + * + * 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "OS/2" port + * + */ + + +#ifndef INCLUDE_FB_TYPES_H +#define INCLUDE_FB_TYPES_H + + +/******************************************************************/ +/* Define type, export and other stuff based on c/c++ and Windows */ +/******************************************************************/ + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) +#ifndef __GNUC__ +typedef __int64 ISC_INT64; +typedef unsigned __int64 ISC_UINT64; +#define ISC_INT64_DEFINED +#endif +#define ISC_EXPORT __stdcall +#define ISC_EXPORT_VARARG __cdecl +#else +#define ISC_EXPORT +#define ISC_EXPORT_VARARG +#endif + +/*******************************************************************/ +/* 64 bit Integers */ +/*******************************************************************/ + +#ifdef ISC_INT64_DEFINED +#undef ISC_INT64_DEFINED +#else +typedef long long int ISC_INT64; +typedef unsigned long long int ISC_UINT64; +#endif + +// Nickolay: it is easier to assume that integer is at least 32-bit. +// This comes from limitation that we cannot reliably detect datatype size at +// compile time in cases when we do not control compilation (public headers) +// We are not going to support 16-bit platforms, right? +// +// Temporarly restrict new definition until ULONG clash with Windows +// type is solved. Win64 port is not possible before that point. +// Cannot use SIZEOF_LONG define here because we are in a public header +#if defined(_LP64) || defined(__LP64__) || defined(__arch64__) + /* EKU: Firebird requires (S)LONG to be 32 bit */ +# define LONG_DEFINED + typedef int SLONG; + typedef unsigned int ULONG; +#endif /* SIZEOF_LONG == 8 */ + + + +/* Basic data types */ + + +#ifdef NOT_USED_OR_REPLACED +typedef signed char SCHAR; +#else +/* TMN: TODO It seems SCHAR is used just about *everywhere* where a plain + * "char" is really intended. This currently forces us to this bad definition. + */ +typedef char SCHAR; +#endif + + +typedef unsigned char UCHAR; +typedef short SSHORT; +typedef unsigned short USHORT; + + +#ifndef LONG_DEFINED /* 32 bit */ +typedef long SLONG; +typedef unsigned long ULONG; +#else +#undef LONG_DEFINED +#endif + + +#ifndef SQUAD_DEFINED /* 64 bit */ +typedef struct { + SLONG high; + ULONG low; +} SQUAD; +#endif + + +#ifndef DEFINED_GDS_QUAD +#define DEFINED_GDS_QUAD +struct GDS_QUAD_t { + SLONG gds_quad_high; + ULONG gds_quad_low; +}; + +typedef struct GDS_QUAD_t GDS_QUAD; + +#endif /* DEFINED_GDS_QUAD */ + +// +// TMN: some misc data types from all over the place +// +struct vary +{ + USHORT vary_length; + char vary_string[1]; +}; +// TMN: Currently we can't do this, since remote uses a different +// definition of VARY than the rest of the code! :-< +//typedef vary* VARY; + +struct lstring +{ + ULONG lstr_length; + ULONG lstr_allocated; + UCHAR* lstr_address; +}; +typedef struct lstring LSTRING; + + +typedef unsigned char BOOLEAN; +typedef char TEXT; // To be expunged over time +//typedef unsigned char STEXT; Signed text - not used +//typedef unsigned char UTEXT; Unsigned text - not used +typedef unsigned char BYTE; // Unsigned byte - common +//typedef char SBYTE; Signed byte - not used +typedef long ISC_STATUS; +typedef long IPTR; +typedef unsigned long U_IPTR; +typedef void (*FPTR_VOID) (); +typedef void (*FPTR_VOID_PTR) (void *); +typedef int (*FPTR_INT) (); +typedef int (*FPTR_INT_VOID_PTR) (void *); +typedef ULONG RCRD_OFFSET; +typedef USHORT FLD_LENGTH; +typedef int (*lock_ast_t)(void *); + +typedef IPTR FB_THREAD_ID; + +#define ISC_STATUS_LENGTH 20 +typedef ISC_STATUS ISC_STATUS_ARRAY[ISC_STATUS_LENGTH]; + +/* Number of elements in an arry */ +#define FB_NELEM(x) ((int)(sizeof(x) / sizeof(x[0]))) +#define FB_ALIGN(n,b) ((n+b-1)&~(b-1)) + +#endif /* INCLUDE_FB_TYPES_H */ + +#define FB_API_VER 15 +#define isc_version4 + +#define ISC_TRUE 1 +#define ISC_FALSE 0 +#if !(defined __cplusplus) +#define ISC__TRUE ISC_TRUE +#define ISC__FALSE ISC_FALSE +#endif + +#define ISC_FAR + +// It is difficult to detect 64-bit long from the redistributable header +// we do not care of 16-bit platforms anymore thus we may use plain "int" +// which is 32-bit on all platforms we support +#if defined(_LP64) || defined(__LP64__) || defined(__arch64__) +typedef int ISC_LONG; +typedef unsigned int ISC_ULONG; +#else +typedef signed long ISC_LONG; +typedef unsigned long ISC_ULONG; +#endif + +typedef signed short ISC_SHORT; +typedef unsigned short ISC_USHORT; + +typedef unsigned char ISC_UCHAR; + +#define DSQL_close 1 +#define DSQL_drop 2 + + +/********************************/ +/* InterBase Handle Definitions */ +/********************************/ + +#ifndef JRD_Y_REF_H +#define FRBRD void +#endif + +typedef FRBRD * isc_att_handle; +typedef FRBRD * isc_blob_handle; +typedef FRBRD * isc_db_handle; +typedef FRBRD * isc_req_handle; +typedef FRBRD * isc_stmt_handle; +typedef FRBRD * isc_svc_handle; +typedef FRBRD * isc_tr_handle; +typedef void (* isc_callback) (); +typedef ISC_LONG isc_resv_handle; + +/*******************************************************************/ +/* Time & Date Support */ +/*******************************************************************/ + +#ifndef ISC_TIMESTAMP_DEFINED +typedef int ISC_DATE; +typedef unsigned int ISC_TIME; +typedef struct +{ + ISC_DATE timestamp_date; + ISC_TIME timestamp_time; +} ISC_TIMESTAMP; +#define ISC_TIMESTAMP_DEFINED +#endif /* ISC_TIMESTAMP_DEFINED */ + +#define ISC_TIME_SECONDS_PRECISION 10000L +#define ISC_TIME_SECONDS_PRECISION_SCALE (-4) + +/*******************************************************************/ +/* Blob id structure */ +/*******************************************************************/ + +#if !(defined __cplusplus) +typedef GDS_QUAD GDS__QUAD; +#endif /* !(defined __cplusplus) */ + +typedef struct GDS_QUAD_t ISC_QUAD; + +#define isc_quad_high gds_quad_high +#define isc_quad_low gds_quad_low + +typedef struct +{ + short array_bound_lower; + short array_bound_upper; +} ISC_ARRAY_BOUND; + +typedef struct +{ + unsigned char array_desc_dtype; + char array_desc_scale; + unsigned short array_desc_length; + char array_desc_field_name[32]; + char array_desc_relation_name[32]; + short array_desc_dimensions; + short array_desc_flags; + ISC_ARRAY_BOUND array_desc_bounds[16]; +} ISC_ARRAY_DESC; + +typedef struct +{ + short blob_desc_subtype; + short blob_desc_charset; + short blob_desc_segment_size; + unsigned char blob_desc_field_name[32]; + unsigned char blob_desc_relation_name[32]; +} ISC_BLOB_DESC; + + + +/***************************/ +/* Blob control structure */ +/***************************/ + +typedef struct isc_blob_ctl +{ + ISC_STATUS (* ctl_source)(); /* Source filter */ + struct isc_blob_ctl * ctl_source_handle; /* Argument to pass to source filter */ + short ctl_to_sub_type; /* Target type */ + short ctl_from_sub_type; /* Source type */ + unsigned short ctl_buffer_length; /* Length of buffer */ + unsigned short ctl_segment_length; /* Length of current segment */ + unsigned short ctl_bpb_length; /* Length of blob parameter block */ + char * ctl_bpb; /* Address of blob parameter block */ + unsigned char * ctl_buffer; /* Address of segment buffer */ + ISC_LONG ctl_max_segment; /* Length of longest segment */ + ISC_LONG ctl_number_segments; /* Total number of segments */ + ISC_LONG ctl_total_length; /* Total length of blob */ + ISC_STATUS * ctl_status; /* Address of status vector */ + long ctl_data[8]; /* Application specific data */ +} * ISC_BLOB_CTL; + +/***************************/ +/* Blob stream definitions */ +/***************************/ + +typedef struct bstream +{ + isc_blob_handle bstr_blob; /* Blob handle */ + char * bstr_buffer; /* Address of buffer */ + char * bstr_ptr; /* Next character */ + short bstr_length; /* Length of buffer */ + short bstr_cnt; /* Characters in buffer */ + char bstr_mode; /* (mode) ? OUTPUT : INPUT */ +} BSTREAM; + +/* Three ugly macros, one even using octal radix... sigh... */ +#define getb(p) (--(p)->bstr_cnt >= 0 ? *(p)->bstr_ptr++ & 0377: BLOB_get (p)) +#define putb(x,p) (((x) == '\n' || (!(--(p)->bstr_cnt))) ? BLOB_put ((x),p) : ((int) (*(p)->bstr_ptr++ = (unsigned) (x)))) +#define putbx(x,p) ((!(--(p)->bstr_cnt)) ? BLOB_put ((x),p) : ((int) (*(p)->bstr_ptr++ = (unsigned) (x)))) + + +/********************************************************************/ +/* CVC: Public blob interface definition held in val.h. */ +/* For some unknown reason, it was only documented in langRef */ +/* and being the structure passed by the engine to UDFs it never */ +/* made its way into this public definitions file. */ +/* Being its original name "blob", I renamed it blobcallback here. */ +/* I did the full definition with the proper parameters instead of */ +/* the weak C declaration with any number and type of parameters. */ +/* Since the first parameter -BLB- is unknown outside the engine, */ +/* it's more accurate to use void* than int* as the blob pointer */ +/********************************************************************/ + +#if !defined(JRD_VAL_H) && !defined(REQUESTER) +/* Blob passing structure */ + +enum lseek_mode {blb_seek_relative = 1, blb_seek_from_tail = 2}; + +typedef struct blobcallback { + short ( *blob_get_segment) + (void * hnd, unsigned char* buffer, ISC_USHORT buf_size, ISC_USHORT* result_len); + void *blob_handle; + ISC_LONG blob_number_segments; + ISC_LONG blob_max_segment; + ISC_LONG blob_total_length; + void ( *blob_put_segment) + (void * hnd, unsigned char* buffer, ISC_USHORT buf_size); + ISC_LONG ( *blob_lseek) + (void * hnd, ISC_USHORT mode, ISC_LONG offset); +} *BLOBCALLBACK; +#endif /* !defined(JRD_VAL_H) && !defined(REQUESTER) */ + + + +/********************************************************************/ +/* CVC: Public descriptor interface held in dsc.h. */ +/* We need it documented to be able to recognize NULL in UDFs. */ +/* Being its original name "dsc", I renamed it paramdsc here. */ +/* Notice that I adjust to the original definition: contrary to */ +/* other cases, the typedef is the same struct not the pointer. */ +/* I included the enumeration of dsc_dtype possible values. */ +/* Ultimately, dsc.h should be part of the public interface. */ +/********************************************************************/ + +#if !defined(JRD_DSC_H) +/* This is the famous internal descriptor that UDFs can use, too. */ +typedef struct paramdsc { + unsigned char dsc_dtype; + signed char dsc_scale; + ISC_USHORT dsc_length; + short dsc_sub_type; + ISC_USHORT dsc_flags; + unsigned char *dsc_address; +} PARAMDSC; + +#if !defined(JRD_VAL_H) +/* This is a helper struct to work with varchars. */ +typedef struct paramvary { + ISC_USHORT vary_length; + unsigned char vary_string [1]; +} PARAMVARY; +#endif /* !defined(JRD_VAL_H) */ + +/* values for dsc_flags */ +/* Note: DSC_null is only reliably set for local variables + (blr_variable) */ +#define DSC_null 1 +#define DSC_no_subtype 2 /* dsc has no sub type specified */ +#define DSC_nullable 4 /* not stored. instead, is derived + from metadata primarily to flag + SQLDA (in DSQL) */ + +/* Overload text typing information into the dsc_sub_type field. + See intl.h for definitions of text types */ + +#ifndef dsc_ttype +#define dsc_ttype dsc_sub_type +#endif + + +/* Note that dtype_null actually means that we do not yet know the + dtype for this descriptor. A nice cleanup item would be to globally + change it to dtype_unknown. --chrisj 1999-02-17 */ + +#define dtype_null 0 +#define dtype_text 1 +#define dtype_cstring 2 +#define dtype_varying 3 + +#define dtype_packed 6 +#define dtype_byte 7 +#define dtype_short 8 +#define dtype_long 9 +#define dtype_quad 10 +#define dtype_real 11 +#define dtype_double 12 +#define dtype_d_float 13 +#define dtype_sql_date 14 +#define dtype_sql_time 15 +#define dtype_timestamp 16 +#define dtype_blob 17 +#define dtype_array 18 +#define dtype_int64 19 +#define DTYPE_TYPE_MAX 20 +#endif /* !defined(JRD_DSC_H) */ + + +/***************************/ +/* Dynamic SQL definitions */ +/***************************/ + +/******************************/ +/* Declare the extended SQLDA */ +/******************************/ + +#ifndef FB_SQLDA + +typedef struct +{ + short sqltype; /* datatype of field */ + short sqlscale; /* scale factor */ + short sqlsubtype; /* datatype subtype - BLOBs & Text types only */ + short sqllen; /* length of data area */ + char * sqldata; /* address of data */ + short * sqlind; /* address of indicator variable */ + short sqlname_length; /* length of sqlname field */ + char sqlname[32]; /* name of field, name length + space for NULL */ + short relname_length; /* length of relation name */ + char relname[32]; /* field's relation name + space for NULL */ + short ownname_length; /* length of owner name */ + char ownname[32]; /* relation's owner name + space for NULL */ + short aliasname_length; /* length of alias name */ + char aliasname[32]; /* relation's alias name + space for NULL */ +} XSQLVAR; + +typedef struct +{ + short version; /* version of this XSQLDA */ + char sqldaid[8]; /* XSQLDA name field */ + ISC_LONG sqldabc; /* length in bytes of SQLDA */ + short sqln; /* number of fields allocated */ + short sqld; /* actual number of fields */ + XSQLVAR sqlvar[1]; /* first field address */ +} XSQLDA; + +#define XSQLDA_LENGTH(n) (sizeof (XSQLDA) + ((n)-1) * sizeof (XSQLVAR)) + +#define SQLDA_VERSION1 1 + +#define SQL_DIALECT_V5 1 /* meaning is same as DIALECT_xsqlda */ +#define SQL_DIALECT_V6_TRANSITION 2 /* flagging anything that is delimited + by double quotes as an error and + flagging keyword DATE as an error */ +#define SQL_DIALECT_V6 3 /* supports SQL delimited identifier, + SQLDATE/DATE, TIME, TIMESTAMP, + CURRENT_DATE, CURRENT_TIME, + CURRENT_TIMESTAMP, and 64-bit exact + numeric type */ +#define SQL_DIALECT_CURRENT SQL_DIALECT_V6 /* latest IB DIALECT */ + + +#define FB_SQLDA +#endif + +/***************************/ +/* OSRI database functions */ +/***************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +ISC_STATUS ISC_EXPORT isc_attach_database(ISC_STATUS *, + short, + char *, + isc_db_handle *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_array_gen_sdl(ISC_STATUS *, + ISC_ARRAY_DESC *, + short *, + char *, + short *); + +ISC_STATUS ISC_EXPORT isc_array_get_slice(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + ISC_QUAD *, + ISC_ARRAY_DESC *, + void *, + ISC_LONG *); + +ISC_STATUS ISC_EXPORT isc_array_lookup_bounds(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + char *, + char *, + ISC_ARRAY_DESC *); + +ISC_STATUS ISC_EXPORT isc_array_lookup_desc(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + char *, + char *, + ISC_ARRAY_DESC *); + +ISC_STATUS ISC_EXPORT isc_array_set_desc(ISC_STATUS *, + char *, + char *, + short *, + short *, + short *, + ISC_ARRAY_DESC *); + +ISC_STATUS ISC_EXPORT isc_array_put_slice(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + ISC_QUAD *, + ISC_ARRAY_DESC *, + void *, + ISC_LONG *); + +void ISC_EXPORT isc_blob_default_desc(ISC_BLOB_DESC *, + unsigned char *, + unsigned char *); + +ISC_STATUS ISC_EXPORT isc_blob_gen_bpb(ISC_STATUS *, + ISC_BLOB_DESC *, + ISC_BLOB_DESC *, + unsigned short, + unsigned char *, + unsigned short *); + +ISC_STATUS ISC_EXPORT isc_blob_info(ISC_STATUS *, + isc_blob_handle *, + short, + char *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_blob_lookup_desc(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned char *, + unsigned char *, + ISC_BLOB_DESC *, + unsigned char *); + +ISC_STATUS ISC_EXPORT isc_blob_set_desc(ISC_STATUS *, + unsigned char *, + unsigned char *, + short, + short, + short, + ISC_BLOB_DESC *); + +ISC_STATUS ISC_EXPORT isc_cancel_blob(ISC_STATUS *, + isc_blob_handle *); + +ISC_STATUS ISC_EXPORT isc_cancel_events(ISC_STATUS *, + isc_db_handle *, + ISC_LONG *); + +ISC_STATUS ISC_EXPORT isc_close_blob(ISC_STATUS *, + isc_blob_handle *); + +ISC_STATUS ISC_EXPORT isc_commit_retaining(ISC_STATUS *, + isc_tr_handle *); + +ISC_STATUS ISC_EXPORT isc_commit_transaction(ISC_STATUS *, + isc_tr_handle *); + +ISC_STATUS ISC_EXPORT isc_create_blob(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + isc_blob_handle *, + ISC_QUAD *); + +ISC_STATUS ISC_EXPORT isc_create_blob2(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + isc_blob_handle *, + ISC_QUAD *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_create_database(ISC_STATUS *, + short, + char *, + isc_db_handle *, + short, + char *, + short); + +ISC_STATUS ISC_EXPORT isc_database_info(ISC_STATUS *, + isc_db_handle *, + short, + char *, + short, + char *); + +void ISC_EXPORT isc_decode_date(ISC_QUAD *, + void *); + +void ISC_EXPORT isc_decode_sql_date(ISC_DATE *, + void *); + +void ISC_EXPORT isc_decode_sql_time(ISC_TIME *, + void *); + +void ISC_EXPORT isc_decode_timestamp(ISC_TIMESTAMP *, + void *); + +ISC_STATUS ISC_EXPORT isc_detach_database(ISC_STATUS *, + isc_db_handle *); + +ISC_STATUS ISC_EXPORT isc_drop_database(ISC_STATUS *, + isc_db_handle *); + +ISC_STATUS ISC_EXPORT isc_dsql_allocate_statement(ISC_STATUS *, + isc_db_handle *, + isc_stmt_handle *); + +ISC_STATUS ISC_EXPORT isc_dsql_alloc_statement2(ISC_STATUS *, + isc_db_handle *, + isc_stmt_handle *); + +ISC_STATUS ISC_EXPORT isc_dsql_describe(ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_describe_bind(ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_exec_immed2(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned short, + char *, + unsigned short, + XSQLDA *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_execute(ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_execute2(ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + XSQLDA *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_execute_immediate(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned short, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_fetch(ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_finish(isc_db_handle *); + +ISC_STATUS ISC_EXPORT isc_dsql_free_statement(ISC_STATUS *, + isc_stmt_handle *, + unsigned short); + +ISC_STATUS ISC_EXPORT isc_dsql_insert(ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_prepare(ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_dsql_set_cursor_name(ISC_STATUS *, + isc_stmt_handle *, + char *, + unsigned short); + +ISC_STATUS ISC_EXPORT isc_dsql_sql_info(ISC_STATUS *, + isc_stmt_handle *, + short, + const char *, + short, + char *); + +void ISC_EXPORT isc_encode_date(void *, + ISC_QUAD *); + +void ISC_EXPORT isc_encode_sql_date(void *, + ISC_DATE *); + +void ISC_EXPORT isc_encode_sql_time(void *, + ISC_TIME *); + +void ISC_EXPORT isc_encode_timestamp(void *, + ISC_TIMESTAMP *); + +ISC_LONG ISC_EXPORT_VARARG isc_event_block(char * *, + char * *, + unsigned short, ...); + +void ISC_EXPORT isc_event_counts(ISC_ULONG *, + short, + char *, + char *); + +/* 17 May 2001 - isc_expand_dpb is DEPRECATED */ +void ISC_EXPORT_VARARG isc_expand_dpb(char * *, + short *, ...); + +int ISC_EXPORT isc_modify_dpb(char * *, + short *, + unsigned short, + char *, + short); + +ISC_LONG ISC_EXPORT isc_free(char *); + +ISC_STATUS ISC_EXPORT isc_get_segment(ISC_STATUS *, + isc_blob_handle *, + unsigned short *, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_get_slice(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + ISC_QUAD *, + short, + char *, + short, + ISC_LONG *, + ISC_LONG, + void *, + ISC_LONG *); + +ISC_STATUS ISC_EXPORT isc_interprete(char *, + ISC_STATUS * *); + +ISC_STATUS ISC_EXPORT isc_open_blob(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + isc_blob_handle *, + ISC_QUAD *); + +ISC_STATUS ISC_EXPORT isc_open_blob2(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + isc_blob_handle *, + ISC_QUAD *, + ISC_USHORT, + ISC_UCHAR *); + +ISC_STATUS ISC_EXPORT isc_prepare_transaction2(ISC_STATUS *, + isc_tr_handle *, + ISC_USHORT, + ISC_UCHAR *); + +void ISC_EXPORT isc_print_sqlerror(ISC_SHORT, + ISC_STATUS *); + +ISC_STATUS ISC_EXPORT isc_print_status(ISC_STATUS *); + +ISC_STATUS ISC_EXPORT isc_put_segment(ISC_STATUS *, + isc_blob_handle *, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_put_slice(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + ISC_QUAD *, + short, + char *, + short, + ISC_LONG *, + ISC_LONG, + void *); + +ISC_STATUS ISC_EXPORT isc_que_events(ISC_STATUS *, + isc_db_handle *, + ISC_LONG *, + short, + char *, + isc_callback, + void *); + +ISC_STATUS ISC_EXPORT isc_rollback_retaining(ISC_STATUS *, + isc_tr_handle *); + +ISC_STATUS ISC_EXPORT isc_rollback_transaction(ISC_STATUS *, + isc_tr_handle *); + +ISC_STATUS ISC_EXPORT isc_start_multiple(ISC_STATUS *, + isc_tr_handle *, + short, + void *); + +ISC_STATUS ISC_EXPORT_VARARG isc_start_transaction(ISC_STATUS *, + isc_tr_handle *, + short, ...); + +ISC_LONG ISC_EXPORT isc_sqlcode(ISC_STATUS *); + +void ISC_EXPORT isc_sql_interprete(short, + char *, + short); + +ISC_STATUS ISC_EXPORT isc_transaction_info(ISC_STATUS *, + isc_tr_handle *, + short, + char *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_transact_request(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned short, + char *, + unsigned short, + char *, + unsigned short, + char *); + +ISC_LONG ISC_EXPORT isc_vax_integer(char *, + short); + +ISC_INT64 ISC_EXPORT isc_portable_integer(unsigned char *, + short); + +/*************************************/ +/* Security Functions and structures */ +/*************************************/ + +#define sec_uid_spec 0x01 +#define sec_gid_spec 0x02 +#define sec_server_spec 0x04 +#define sec_password_spec 0x08 +#define sec_group_name_spec 0x10 +#define sec_first_name_spec 0x20 +#define sec_middle_name_spec 0x40 +#define sec_last_name_spec 0x80 +#define sec_dba_user_name_spec 0x100 +#define sec_dba_password_spec 0x200 + +#define sec_protocol_tcpip 1 +#define sec_protocol_netbeui 2 +#define sec_protocol_spx 3 /* -- Deprecated Protocol. Declaration retained for compatibility */ +#define sec_protocol_local 4 + +typedef struct { + short sec_flags; /* which fields are specified */ + int uid; /* the user's id */ + int gid; /* the user's group id */ + int protocol; /* protocol to use for connection */ + char *server; /* server to administer */ + char *user_name; /* the user's name */ + char *password; /* the user's password */ + char *group_name; /* the group name */ + char *first_name; /* the user's first name */ + char *middle_name; /* the user's middle name */ + char *last_name; /* the user's last name */ + char *dba_user_name; /* the dba user name */ + char *dba_password; /* the dba password */ +} USER_SEC_DATA; + +int ISC_EXPORT isc_add_user(ISC_STATUS *, USER_SEC_DATA *); + +int ISC_EXPORT isc_delete_user(ISC_STATUS *, USER_SEC_DATA *); + +int ISC_EXPORT isc_modify_user(ISC_STATUS *, USER_SEC_DATA *); + +/**********************************/ +/* Other OSRI functions */ +/**********************************/ + +ISC_STATUS ISC_EXPORT isc_compile_request(ISC_STATUS *, + isc_db_handle *, + isc_req_handle *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_compile_request2(ISC_STATUS *, + isc_db_handle *, + isc_req_handle *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_ddl(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_prepare_transaction(ISC_STATUS *, + isc_tr_handle *); + + +ISC_STATUS ISC_EXPORT isc_receive(ISC_STATUS *, + isc_req_handle *, + short, + short, + void *, + short); + +ISC_STATUS ISC_EXPORT isc_reconnect_transaction(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_release_request(ISC_STATUS *, + isc_req_handle *); + +ISC_STATUS ISC_EXPORT isc_request_info(ISC_STATUS *, + isc_req_handle *, + short, + short, + char *, + short, + char *); + +ISC_STATUS ISC_EXPORT isc_seek_blob(ISC_STATUS *, + isc_blob_handle *, + short, + ISC_LONG, + ISC_LONG *); + +ISC_STATUS ISC_EXPORT isc_send(ISC_STATUS *, + isc_req_handle *, + short, + short, + void *, + short); + +ISC_STATUS ISC_EXPORT isc_start_and_send(ISC_STATUS *, + isc_req_handle *, + isc_tr_handle *, + short, + short, + void *, + short); + +ISC_STATUS ISC_EXPORT isc_start_request(ISC_STATUS *, + isc_req_handle *, + isc_tr_handle *, + short); + +ISC_STATUS ISC_EXPORT isc_unwind_request(ISC_STATUS *, + isc_tr_handle *, + short); + +ISC_STATUS ISC_EXPORT isc_wait_for_event(ISC_STATUS *, + isc_db_handle *, + short, + char *, + char *); + + +/*****************************/ +/* Other Sql functions */ +/*****************************/ + +ISC_STATUS ISC_EXPORT isc_close(ISC_STATUS *, + char *); + +ISC_STATUS ISC_EXPORT isc_declare(ISC_STATUS *, + char *, + char *); + +ISC_STATUS ISC_EXPORT isc_describe(ISC_STATUS *, + char *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_describe_bind(ISC_STATUS *, + char *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_execute(ISC_STATUS *, + isc_tr_handle *, + char *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_execute_immediate(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + short *, + char *); + +ISC_STATUS ISC_EXPORT isc_fetch(ISC_STATUS *, + char *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_open(ISC_STATUS *, + isc_tr_handle *, + char *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_prepare(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + char *, + short *, + char *, + XSQLDA *); + + +/*************************************/ +/* Other Dynamic sql functions */ +/*************************************/ + +ISC_STATUS ISC_EXPORT isc_dsql_execute_m(ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_dsql_execute2_m(ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_dsql_execute_immediate_m(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *, + unsigned short, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_dsql_exec_immed3_m(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *, + unsigned short, + unsigned short, + char *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_dsql_fetch_m(ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_dsql_insert_m(ISC_STATUS *, + isc_stmt_handle *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_dsql_prepare_m(ISC_STATUS *, + isc_tr_handle *, + isc_stmt_handle *, + unsigned short, + char *, + unsigned short, + unsigned short, + char *, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_dsql_release(ISC_STATUS *, + char *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_close(ISC_STATUS *, + char *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_declare(ISC_STATUS *, + char *, + char *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_describe(ISC_STATUS *, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_describe_bind(ISC_STATUS *, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_execute(ISC_STATUS *, + isc_tr_handle *, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_execute2(ISC_STATUS *, + isc_tr_handle *, + char *, + unsigned short, + XSQLDA *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_execute_immed(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + unsigned short, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_fetch(ISC_STATUS *, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_open(ISC_STATUS *, + isc_tr_handle *, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_open2(ISC_STATUS *, + isc_tr_handle *, + char *, + unsigned short, + XSQLDA *, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_insert(ISC_STATUS *, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_prepare(ISC_STATUS *, + isc_db_handle *, + isc_tr_handle *, + char *, + unsigned short, + char *, + unsigned short, + XSQLDA *); + +ISC_STATUS ISC_EXPORT isc_embed_dsql_release(ISC_STATUS *, + char *); + + +/******************************/ +/* Other Blob functions */ +/******************************/ + +BSTREAM *ISC_EXPORT BLOB_open(isc_blob_handle, + char *, + int); + +int ISC_EXPORT BLOB_put(char, + BSTREAM *); + +int ISC_EXPORT BLOB_close(BSTREAM *); + +int ISC_EXPORT BLOB_get(BSTREAM *); + +int ISC_EXPORT BLOB_display(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *); + +int ISC_EXPORT BLOB_dump(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *); + +int ISC_EXPORT BLOB_edit(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *); + +int ISC_EXPORT BLOB_load(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *); + +int ISC_EXPORT BLOB_text_dump(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *); + +int ISC_EXPORT BLOB_text_load(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *); + +BSTREAM *ISC_EXPORT Bopen(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *); + +BSTREAM *ISC_EXPORT Bopen2(ISC_QUAD *, + isc_db_handle, + isc_tr_handle, + char *, + unsigned short); + + +/******************************/ +/* Other Misc functions */ +/******************************/ + +ISC_LONG ISC_EXPORT isc_ftof(char *, + unsigned short, + char *, + unsigned short); + +ISC_STATUS ISC_EXPORT isc_print_blr(char *, + isc_callback, + void *, + short); + +void ISC_EXPORT isc_set_debug(int); + +void ISC_EXPORT isc_qtoq(ISC_QUAD *, + ISC_QUAD *); + +void ISC_EXPORT isc_vtof(char *, + char *, + unsigned short); + +void ISC_EXPORT isc_vtov(char *, + char *, + short); + +int ISC_EXPORT isc_version(isc_db_handle *, + isc_callback, + void *); + +ISC_LONG ISC_EXPORT isc_reset_fpe(unsigned short); + + +/*****************************************/ +/* Service manager functions */ +/*****************************************/ + +#define ADD_SPB_LENGTH(p, length) {*(p)++ = (length); *(p)++ = (length) >> 8;} + +#define ADD_SPB_NUMERIC(p, data) {*(p)++ = (SCHAR) (data); *(p)++ = (SCHAR) ((data) >> 8); *(p)++ = (SCHAR) ((data) >> 16); *(p)++ = (SCHAR) ((data) >> 24);} + +ISC_STATUS ISC_EXPORT isc_service_attach(ISC_STATUS *, + unsigned short, + char *, + isc_svc_handle *, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_service_detach(ISC_STATUS *, + isc_svc_handle *); + +ISC_STATUS ISC_EXPORT isc_service_query(ISC_STATUS *, + isc_svc_handle *, + isc_resv_handle *, + unsigned short, + char *, + unsigned short, + char *, + unsigned short, + char *); + +ISC_STATUS ISC_EXPORT isc_service_start(ISC_STATUS *, + isc_svc_handle *, + isc_resv_handle *, + unsigned short, + char *); + + +/********************************/ +/* Client information functions */ +/********************************/ + +void ISC_EXPORT isc_get_client_version ( char *); +int ISC_EXPORT isc_get_client_major_version (); +int ISC_EXPORT isc_get_client_minor_version (); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +/***************************************************/ +/* Actions to pass to the blob filter (ctl_source) */ +/***************************************************/ + +#define isc_blob_filter_open 0 +#define isc_blob_filter_get_segment 1 +#define isc_blob_filter_close 2 +#define isc_blob_filter_create 3 +#define isc_blob_filter_put_segment 4 +#define isc_blob_filter_alloc 5 +#define isc_blob_filter_free 6 +#define isc_blob_filter_seek 7 + +/*******************/ +/* Blr definitions */ +/*******************/ + +/* + * PROGRAM: C preprocessor + * MODULE: blr.h + * DESCRIPTION: BLR constants + * + * The contents of this file are subject to the Interbase Public + * License Version 1.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy + * of the License at http://www.Inprise.com/IPL.html + * + * Software distributed under the License is distributed on an + * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express + * or implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code was created by Inprise Corporation + * and its predecessors. Portions created by Inprise Corporation are + * Copyright (C) Inprise Corporation. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + * Claudio Valderrama: 2001.6.18: Add blr_current_role. + * 2002.09.28 Dmitry Yemanov: Reworked internal_info stuff, enhanced + * exception handling in SPs/triggers, + * implemented ROWS_AFFECTED system variable + * 2002.10.21 Nickolay Samofatov: Added support for explicit pessimistic locks + * 2002.10.29 Nickolay Samofatov: Added support for savepoints + */ + +#ifndef _JRD_BLR_H_ +#define _JRD_BLR_H_ + +/* WARNING: if you add a new BLR representing a data type, and the value + * is greater than the numerically greatest value which now + * represents a data type, you must change the define for + * DTYPE_BLR_MAX in jrd/align.h, and add the necessary entries + * to all the arrays in that file. + */ + +#define blr_text (unsigned char)14 +#define blr_text2 (unsigned char)15 /* added in 3.2 JPN */ +#define blr_short (unsigned char)7 +#define blr_long (unsigned char)8 +#define blr_quad (unsigned char)9 +#define blr_float (unsigned char)10 +#define blr_double (unsigned char)27 +#define blr_d_float (unsigned char)11 +#define blr_timestamp (unsigned char)35 +#define blr_varying (unsigned char)37 +#define blr_varying2 (unsigned char)38 /* added in 3.2 JPN */ +#define blr_blob (unsigned short)261 +#define blr_cstring (unsigned char)40 +#define blr_cstring2 (unsigned char)41 /* added in 3.2 JPN */ +#define blr_blob_id (unsigned char)45 /* added from gds.h */ +#define blr_sql_date (unsigned char)12 +#define blr_sql_time (unsigned char)13 +#define blr_int64 (unsigned char)16 + +/* Historical alias for pre V6 applications */ +#define blr_date blr_timestamp + +#define blr_inner (unsigned char)0 +#define blr_left (unsigned char)1 +#define blr_right (unsigned char)2 +#define blr_full (unsigned char)3 + +#define blr_gds_code (unsigned char)0 +#define blr_sql_code (unsigned char)1 +#define blr_exception (unsigned char)2 +#define blr_trigger_code (unsigned char)3 +#define blr_default_code (unsigned char)4 +#define blr_raise (unsigned char)5 +#define blr_exception_msg (unsigned char)6 + +#define blr_version4 (unsigned char)4 +#define blr_version5 (unsigned char)5 +#define blr_eoc (unsigned char)76 +#define blr_end (unsigned char)255 /* note: defined as -1 in gds.h */ + +#define blr_assignment (unsigned char)1 +#define blr_begin (unsigned char)2 +#define blr_dcl_variable (unsigned char)3 /* added from gds.h */ +#define blr_message (unsigned char)4 +#define blr_erase (unsigned char)5 +#define blr_fetch (unsigned char)6 +#define blr_for (unsigned char)7 +#define blr_if (unsigned char)8 +#define blr_loop (unsigned char)9 +#define blr_modify (unsigned char)10 +#define blr_handler (unsigned char)11 +#define blr_receive (unsigned char)12 +#define blr_select (unsigned char)13 +#define blr_send (unsigned char)14 +#define blr_store (unsigned char)15 +#define blr_label (unsigned char)17 +#define blr_leave (unsigned char)18 +#define blr_store2 (unsigned char)19 +#define blr_post (unsigned char)20 +#define blr_literal (unsigned char)21 +#define blr_dbkey (unsigned char)22 +#define blr_field (unsigned char)23 +#define blr_fid (unsigned char)24 +#define blr_parameter (unsigned char)25 +#define blr_variable (unsigned char)26 +#define blr_average (unsigned char)27 +#define blr_count (unsigned char)28 +#define blr_maximum (unsigned char)29 +#define blr_minimum (unsigned char)30 +#define blr_total (unsigned char)31 +/* count 2 +#define blr_count2 32 +*/ +#define blr_add (unsigned char)34 +#define blr_subtract (unsigned char)35 +#define blr_multiply (unsigned char)36 +#define blr_divide (unsigned char)37 +#define blr_negate (unsigned char)38 +#define blr_concatenate (unsigned char)39 +#define blr_substring (unsigned char)40 +#define blr_parameter2 (unsigned char)41 +#define blr_from (unsigned char)42 +#define blr_via (unsigned char)43 +#define blr_parameter2_old (unsigned char)44 /* Confusion */ +#define blr_user_name (unsigned char)44 /* added from gds.h */ +#define blr_null (unsigned char)45 + +#define blr_eql (unsigned char)47 +#define blr_neq (unsigned char)48 +#define blr_gtr (unsigned char)49 +#define blr_geq (unsigned char)50 +#define blr_lss (unsigned char)51 +#define blr_leq (unsigned char)52 +#define blr_containing (unsigned char)53 +#define blr_matching (unsigned char)54 +#define blr_starting (unsigned char)55 +#define blr_between (unsigned char)56 +#define blr_or (unsigned char)57 +#define blr_and (unsigned char)58 +#define blr_not (unsigned char)59 +#define blr_any (unsigned char)60 +#define blr_missing (unsigned char)61 +#define blr_unique (unsigned char)62 +#define blr_like (unsigned char)63 + +#define blr_stream (unsigned char)65 /* added from gds.h */ +#define blr_set_index (unsigned char)66 /* added from gds.h */ + +#define blr_rse (unsigned char)67 +#define blr_first (unsigned char)68 +#define blr_project (unsigned char)69 +#define blr_sort (unsigned char)70 +#define blr_boolean (unsigned char)71 +#define blr_ascending (unsigned char)72 +#define blr_descending (unsigned char)73 +#define blr_relation (unsigned char)74 +#define blr_rid (unsigned char)75 +#define blr_union (unsigned char)76 +#define blr_map (unsigned char)77 +#define blr_group_by (unsigned char)78 +#define blr_aggregate (unsigned char)79 +#define blr_join_type (unsigned char)80 + +#define blr_agg_count (unsigned char)83 +#define blr_agg_max (unsigned char)84 +#define blr_agg_min (unsigned char)85 +#define blr_agg_total (unsigned char)86 +#define blr_agg_average (unsigned char)87 +#define blr_parameter3 (unsigned char)88 /* same as Rdb definition */ +#define blr_run_max (unsigned char)89 +#define blr_run_min (unsigned char)90 +#define blr_run_total (unsigned char)91 +#define blr_run_average (unsigned char)92 +#define blr_agg_count2 (unsigned char)93 +#define blr_agg_count_distinct (unsigned char)94 +#define blr_agg_total_distinct (unsigned char)95 +#define blr_agg_average_distinct (unsigned char)96 + +#define blr_function (unsigned char)100 +#define blr_gen_id (unsigned char)101 +#define blr_prot_mask (unsigned char)102 +#define blr_upcase (unsigned char)103 +#define blr_lock_state (unsigned char)104 +#define blr_value_if (unsigned char)105 +#define blr_matching2 (unsigned char)106 +#define blr_index (unsigned char)107 +#define blr_ansi_like (unsigned char)108 +#define blr_bookmark (unsigned char)109 +#define blr_crack (unsigned char)110 +#define blr_force_crack (unsigned char)111 +#define blr_seek (unsigned char)112 +#define blr_find (unsigned char)113 + +/* these indicate directions for blr_seek and blr_find */ + +#define blr_continue (unsigned char)0 +#define blr_forward (unsigned char)1 +#define blr_backward (unsigned char)2 +#define blr_bof_forward (unsigned char)3 +#define blr_eof_backward (unsigned char)4 + +#define blr_lock_relation (unsigned char)114 +#define blr_lock_record (unsigned char)115 +#define blr_set_bookmark (unsigned char)116 +#define blr_get_bookmark (unsigned char)117 + +#define blr_run_count (unsigned char)118 /* changed from 88 to avoid conflict with blr_parameter3 */ +#define blr_rs_stream (unsigned char)119 +#define blr_exec_proc (unsigned char)120 +#define blr_begin_range (unsigned char)121 +#define blr_end_range (unsigned char)122 +#define blr_delete_range (unsigned char)123 +#define blr_procedure (unsigned char)124 +#define blr_pid (unsigned char)125 +#define blr_exec_pid (unsigned char)126 +#define blr_singular (unsigned char)127 +#define blr_abort (unsigned char)128 +#define blr_block (unsigned char)129 +#define blr_error_handler (unsigned char)130 + +#define blr_cast (unsigned char)131 +#define blr_release_lock (unsigned char)132 +#define blr_release_locks (unsigned char)133 +#define blr_start_savepoint (unsigned char)134 +#define blr_end_savepoint (unsigned char)135 +#define blr_find_dbkey (unsigned char)136 +#define blr_range_relation (unsigned char)137 +#define blr_delete_ranges (unsigned char)138 + +#define blr_plan (unsigned char)139 /* access plan items */ +#define blr_merge (unsigned char)140 +#define blr_join (unsigned char)141 +#define blr_sequential (unsigned char)142 +#define blr_navigational (unsigned char)143 +#define blr_indices (unsigned char)144 +#define blr_retrieve (unsigned char)145 + +#define blr_relation2 (unsigned char)146 +#define blr_rid2 (unsigned char)147 +#define blr_reset_stream (unsigned char)148 +#define blr_release_bookmark (unsigned char)149 + +#define blr_set_generator (unsigned char)150 + +#define blr_ansi_any (unsigned char)151 /* required for NULL handling */ +#define blr_exists (unsigned char)152 /* required for NULL handling */ +#define blr_cardinality (unsigned char)153 + +#define blr_record_version (unsigned char)154 /* get tid of record */ +#define blr_stall (unsigned char)155 /* fake server stall */ + +#define blr_seek_no_warn (unsigned char)156 +#define blr_find_dbkey_version (unsigned char)157 /* find dbkey with record version */ +#define blr_ansi_all (unsigned char)158 /* required for NULL handling */ + +#define blr_extract (unsigned char)159 + +/* sub parameters for blr_extract */ + +#define blr_extract_year (unsigned char)0 +#define blr_extract_month (unsigned char)1 +#define blr_extract_day (unsigned char)2 +#define blr_extract_hour (unsigned char)3 +#define blr_extract_minute (unsigned char)4 +#define blr_extract_second (unsigned char)5 +#define blr_extract_weekday (unsigned char)6 +#define blr_extract_yearday (unsigned char)7 + +#define blr_current_date (unsigned char)160 +#define blr_current_timestamp (unsigned char)161 +#define blr_current_time (unsigned char)162 + +/* FB 1.0 specific BLR */ + +#define blr_current_role (unsigned char)174 +#define blr_skip (unsigned char)175 + +/* FB 1.5 specific BLR */ + +#define blr_exec_sql (unsigned char)176 +#define blr_internal_info (unsigned char)177 +#define blr_nullsfirst (unsigned char)178 +#define blr_writelock (unsigned char)179 +#define blr_nullslast (unsigned char)180 + +/* These codes reuse BLR code space */ + +#define blr_post_arg (unsigned char)163 +#define blr_exec_into (unsigned char)164 +#define blr_user_savepoint (unsigned char)165 + +/* These codes are actions for user-defined savepoints */ + +#define blr_savepoint_set (unsigned char)0 +#define blr_savepoint_release (unsigned char)1 +#define blr_savepoint_undo (unsigned char)2 +#define blr_savepoint_release_single (unsigned char)3 + +#endif /* _JRD_BLR_H_ */ + + +/**********************************/ +/* Database parameter block stuff */ +/**********************************/ + +#define isc_dpb_version1 1 +#define isc_dpb_cdd_pathname 1 +#define isc_dpb_allocation 2 +#define isc_dpb_journal 3 +#define isc_dpb_page_size 4 +#define isc_dpb_num_buffers 5 +#define isc_dpb_buffer_length 6 +#define isc_dpb_debug 7 +#define isc_dpb_garbage_collect 8 +#define isc_dpb_verify 9 +#define isc_dpb_sweep 10 +#define isc_dpb_enable_journal 11 +#define isc_dpb_disable_journal 12 +#define isc_dpb_dbkey_scope 13 +#define isc_dpb_number_of_users 14 +#define isc_dpb_trace 15 +#define isc_dpb_no_garbage_collect 16 +#define isc_dpb_damaged 17 +#define isc_dpb_license 18 +#define isc_dpb_sys_user_name 19 +#define isc_dpb_encrypt_key 20 +#define isc_dpb_activate_shadow 21 +#define isc_dpb_sweep_interval 22 +#define isc_dpb_delete_shadow 23 +#define isc_dpb_force_write 24 +#define isc_dpb_begin_log 25 +#define isc_dpb_quit_log 26 +#define isc_dpb_no_reserve 27 +#define isc_dpb_user_name 28 +#define isc_dpb_password 29 +#define isc_dpb_password_enc 30 +#define isc_dpb_sys_user_name_enc 31 +#define isc_dpb_interp 32 +#define isc_dpb_online_dump 33 +#define isc_dpb_old_file_size 34 +#define isc_dpb_old_num_files 35 +#define isc_dpb_old_file 36 +#define isc_dpb_old_start_page 37 +#define isc_dpb_old_start_seqno 38 +#define isc_dpb_old_start_file 39 +#define isc_dpb_drop_walfile 40 +#define isc_dpb_old_dump_id 41 +#define isc_dpb_wal_backup_dir 42 +#define isc_dpb_wal_chkptlen 43 +#define isc_dpb_wal_numbufs 44 +#define isc_dpb_wal_bufsize 45 +#define isc_dpb_wal_grp_cmt_wait 46 +#define isc_dpb_lc_messages 47 +#define isc_dpb_lc_ctype 48 +#define isc_dpb_cache_manager 49 +#define isc_dpb_shutdown 50 +#define isc_dpb_online 51 +#define isc_dpb_shutdown_delay 52 +#define isc_dpb_reserved 53 +#define isc_dpb_overwrite 54 +#define isc_dpb_sec_attach 55 +#define isc_dpb_disable_wal 56 +#define isc_dpb_connect_timeout 57 +#define isc_dpb_dummy_packet_interval 58 +#define isc_dpb_gbak_attach 59 +#define isc_dpb_sql_role_name 60 +#define isc_dpb_set_page_buffers 61 +#define isc_dpb_working_directory 62 +#define isc_dpb_sql_dialect 63 +#define isc_dpb_set_db_readonly 64 +#define isc_dpb_set_db_sql_dialect 65 +#define isc_dpb_gfix_attach 66 +#define isc_dpb_gstat_attach 67 +#define isc_dpb_set_db_charset 68 + +/*********************************/ +/* isc_dpb_verify specific flags */ +/*********************************/ + +#define isc_dpb_pages 1 +#define isc_dpb_records 2 +#define isc_dpb_indices 4 +#define isc_dpb_transactions 8 +#define isc_dpb_no_update 16 +#define isc_dpb_repair 32 +#define isc_dpb_ignore 64 + +/***********************************/ +/* isc_dpb_shutdown specific flags */ +/***********************************/ + +#define isc_dpb_shut_cache 1 +#define isc_dpb_shut_attachment 2 +#define isc_dpb_shut_transaction 4 +#define isc_dpb_shut_force 8 + +/**************************************/ +/* Bit assignments in RDB$SYSTEM_FLAG */ +/**************************************/ + +#define RDB_system 1 +#define RDB_id_assigned 2 + + +/*************************************/ +/* Transaction parameter block stuff */ +/*************************************/ + +#define isc_tpb_version1 1 +#define isc_tpb_version3 3 +#define isc_tpb_consistency 1 +#define isc_tpb_concurrency 2 +#define isc_tpb_shared 3 +#define isc_tpb_protected 4 +#define isc_tpb_exclusive 5 +#define isc_tpb_wait 6 +#define isc_tpb_nowait 7 +#define isc_tpb_read 8 +#define isc_tpb_write 9 +#define isc_tpb_lock_read 10 +#define isc_tpb_lock_write 11 +#define isc_tpb_verb_time 12 +#define isc_tpb_commit_time 13 +#define isc_tpb_ignore_limbo 14 +#define isc_tpb_read_committed 15 +#define isc_tpb_autocommit 16 +#define isc_tpb_rec_version 17 +#define isc_tpb_no_rec_version 18 +#define isc_tpb_restart_requests 19 +#define isc_tpb_no_auto_undo 20 + + +/************************/ +/* Blob Parameter Block */ +/************************/ + +#define isc_bpb_version1 1 +#define isc_bpb_source_type 1 +#define isc_bpb_target_type 2 +#define isc_bpb_type 3 +#define isc_bpb_source_interp 4 +#define isc_bpb_target_interp 5 +#define isc_bpb_filter_parameter 6 + +#define isc_bpb_type_segmented 0 +#define isc_bpb_type_stream 1 + + +/*********************************/ +/* Service parameter block stuff */ +/*********************************/ + +#define isc_spb_version1 1 +#define isc_spb_current_version 2 +#define isc_spb_version isc_spb_current_version +#define isc_spb_user_name isc_dpb_user_name +#define isc_spb_sys_user_name isc_dpb_sys_user_name +#define isc_spb_sys_user_name_enc isc_dpb_sys_user_name_enc +#define isc_spb_password isc_dpb_password +#define isc_spb_password_enc isc_dpb_password_enc +#define isc_spb_command_line 105 +#define isc_spb_dbname 106 +#define isc_spb_verbose 107 +#define isc_spb_options 108 + +#define isc_spb_connect_timeout isc_dpb_connect_timeout +#define isc_spb_dummy_packet_interval isc_dpb_dummy_packet_interval +#define isc_spb_sql_role_name isc_dpb_sql_role_name + + +/*********************************/ +/* Information call declarations */ +/*********************************/ + +/****************************/ +/* Common, structural codes */ +/****************************/ + +#define isc_info_end 1 +#define isc_info_truncated 2 +#define isc_info_error 3 +#define isc_info_data_not_ready 4 +#define isc_info_flag_end 127 + +/******************************/ +/* Database information items */ +/******************************/ + +enum db_info_types + { + isc_info_db_id = 4, + isc_info_reads = 5, + isc_info_writes = 6, + isc_info_fetches = 7, + isc_info_marks = 8, + + isc_info_implementation = 11, + isc_info_isc_version = 12, + isc_info_base_level = 13, + isc_info_page_size = 14, + isc_info_num_buffers = 15, + isc_info_limbo = 16, + isc_info_current_memory = 17, + isc_info_max_memory = 18, + isc_info_window_turns = 19, + isc_info_license = 20, + + isc_info_allocation = 21, + isc_info_attachment_id = 22, + isc_info_read_seq_count = 23, + isc_info_read_idx_count = 24, + isc_info_insert_count = 25, + isc_info_update_count = 26, + isc_info_delete_count = 27, + isc_info_backout_count = 28, + isc_info_purge_count = 29, + isc_info_expunge_count = 30, + + isc_info_sweep_interval = 31, + isc_info_ods_version = 32, + isc_info_ods_minor_version = 33, + isc_info_no_reserve = 34, + isc_info_logfile = 35, + isc_info_cur_logfile_name = 36, + isc_info_cur_log_part_offset = 37, + isc_info_num_wal_buffers = 38, + isc_info_wal_buffer_size = 39, + isc_info_wal_ckpt_length = 40, + + isc_info_wal_cur_ckpt_interval = 41, + isc_info_wal_prv_ckpt_fname = 42, + isc_info_wal_prv_ckpt_poffset = 43, + isc_info_wal_recv_ckpt_fname = 44, + isc_info_wal_recv_ckpt_poffset = 45, + isc_info_wal_grpc_wait_usecs = 47, + isc_info_wal_num_io = 48, + isc_info_wal_avg_io_size = 49, + isc_info_wal_num_commits = 50, + + isc_info_wal_avg_grpc_size = 51, + isc_info_forced_writes = 52, + isc_info_user_names = 53, + isc_info_page_errors = 54, + isc_info_record_errors = 55, + isc_info_bpage_errors = 56, + isc_info_dpage_errors = 57, + isc_info_ipage_errors = 58, + isc_info_ppage_errors = 59, + isc_info_tpage_errors = 60, + + isc_info_set_page_buffers = 61, + isc_info_db_sql_dialect = 62, + isc_info_db_read_only = 63, + isc_info_db_size_in_pages = 64, + + /* Values 65 -100 unused to avoid conflict with InterBase */ + + frb_info_att_charset = 101, + isc_info_db_class = 102, + isc_info_firebird_version = 103, + isc_info_oldest_transaction = 104, + isc_info_oldest_active = 105, + isc_info_oldest_snapshot = 106, + isc_info_next_transaction = 107, + isc_info_db_provider = 108, + isc_info_active_transactions = 109, + + isc_info_db_last_value /* Leave this LAST! */ + }; + +#define isc_info_version isc_info_isc_version + + +/**************************************/ +/* Database information return values */ +/**************************************/ + +enum info_db_implementations + { + isc_info_db_impl_rdb_vms = 1, + isc_info_db_impl_rdb_eln = 2, + isc_info_db_impl_rdb_eln_dev = 3, + isc_info_db_impl_rdb_vms_y = 4, + isc_info_db_impl_rdb_eln_y = 5, + isc_info_db_impl_jri = 6, + isc_info_db_impl_jsv = 7, + + isc_info_db_impl_isc_apl_68K = 25, + isc_info_db_impl_isc_vax_ultr = 26, + isc_info_db_impl_isc_vms = 27, + isc_info_db_impl_isc_sun_68k = 28, + isc_info_db_impl_isc_os2 = 29, + isc_info_db_impl_isc_sun4 = 30, /* 30 */ + + isc_info_db_impl_isc_hp_ux = 31, + isc_info_db_impl_isc_sun_386i = 32, + isc_info_db_impl_isc_vms_orcl = 33, + isc_info_db_impl_isc_mac_aux = 34, + isc_info_db_impl_isc_rt_aix = 35, + isc_info_db_impl_isc_mips_ult = 36, + isc_info_db_impl_isc_xenix = 37, + isc_info_db_impl_isc_dg = 38, + isc_info_db_impl_isc_hp_mpexl = 39, + isc_info_db_impl_isc_hp_ux68K = 40, /* 40 */ + + isc_info_db_impl_isc_sgi = 41, + isc_info_db_impl_isc_sco_unix = 42, + isc_info_db_impl_isc_cray = 43, + isc_info_db_impl_isc_imp = 44, + isc_info_db_impl_isc_delta = 45, + isc_info_db_impl_isc_next = 46, + isc_info_db_impl_isc_dos = 47, + isc_info_db_impl_m88K = 48, + isc_info_db_impl_unixware = 49, + isc_info_db_impl_isc_winnt_x86 = 50, + + isc_info_db_impl_isc_epson = 51, + isc_info_db_impl_alpha_osf = 52, + isc_info_db_impl_alpha_vms = 53, + isc_info_db_impl_netware_386 = 54, + isc_info_db_impl_win_only = 55, + isc_info_db_impl_ncr_3000 = 56, + isc_info_db_impl_winnt_ppc = 57, + isc_info_db_impl_dg_x86 = 58, + isc_info_db_impl_sco_ev = 59, + isc_info_db_impl_i386 = 60, + + isc_info_db_impl_freebsd = 61, + isc_info_db_impl_netbsd = 62, + isc_info_db_impl_darwin = 63, + isc_info_db_impl_sinixz = 64, + + isc_info_db_impl_linux_sparc = 65, + isc_info_db_impl_linux_amd64 = 66, + + isc_info_db_impl_last_value /* Leave this LAST! */ + }; + +#define isc_info_db_impl_isc_a isc_info_db_impl_isc_apl_68K +#define isc_info_db_impl_isc_u isc_info_db_impl_isc_vax_ultr +#define isc_info_db_impl_isc_v isc_info_db_impl_isc_vms +#define isc_info_db_impl_isc_s isc_info_db_impl_isc_sun_68k + + +enum info_db_class + { + isc_info_db_class_access = 1, + isc_info_db_class_y_valve = 2, + isc_info_db_class_rem_int = 3, + isc_info_db_class_rem_srvr = 4, + isc_info_db_class_pipe_int = 7, + isc_info_db_class_pipe_srvr = 8, + isc_info_db_class_sam_int = 9, + isc_info_db_class_sam_srvr = 10, + isc_info_db_class_gateway = 11, + isc_info_db_class_cache = 12, + isc_info_db_class_classic_access = 13, + isc_info_db_class_server_access = 14, + + isc_info_db_class_last_value /* Leave this LAST! */ + }; + +enum info_db_provider + { + isc_info_db_code_rdb_eln = 1, + isc_info_db_code_rdb_vms = 2, + isc_info_db_code_interbase = 3, + isc_info_db_code_firebird = 4, + + isc_info_db_code_last_value /* Leave this LAST! */ + }; + + +/*****************************/ +/* Request information items */ +/*****************************/ + +#define isc_info_number_messages 4 +#define isc_info_max_message 5 +#define isc_info_max_send 6 +#define isc_info_max_receive 7 +#define isc_info_state 8 +#define isc_info_message_number 9 +#define isc_info_message_size 10 +#define isc_info_request_cost 11 +#define isc_info_access_path 12 +#define isc_info_req_select_count 13 +#define isc_info_req_insert_count 14 +#define isc_info_req_update_count 15 +#define isc_info_req_delete_count 16 + + +/*********************/ +/* Access path items */ +/*********************/ + +#define isc_info_rsb_end 0 +#define isc_info_rsb_begin 1 +#define isc_info_rsb_type 2 +#define isc_info_rsb_relation 3 +#define isc_info_rsb_plan 4 + +/*************/ +/* Rsb types */ +/*************/ + +#define isc_info_rsb_unknown 1 +#define isc_info_rsb_indexed 2 +#define isc_info_rsb_navigate 3 +#define isc_info_rsb_sequential 4 +#define isc_info_rsb_cross 5 +#define isc_info_rsb_sort 6 +#define isc_info_rsb_first 7 +#define isc_info_rsb_boolean 8 +#define isc_info_rsb_union 9 +#define isc_info_rsb_aggregate 10 +#define isc_info_rsb_merge 11 +#define isc_info_rsb_ext_sequential 12 +#define isc_info_rsb_ext_indexed 13 +#define isc_info_rsb_ext_dbkey 14 +#define isc_info_rsb_left_cross 15 +#define isc_info_rsb_select 16 +#define isc_info_rsb_sql_join 17 +#define isc_info_rsb_simulate 18 +#define isc_info_rsb_sim_cross 19 +#define isc_info_rsb_once 20 +#define isc_info_rsb_procedure 21 + +/**********************/ +/* Bitmap expressions */ +/**********************/ + +#define isc_info_rsb_and 1 +#define isc_info_rsb_or 2 +#define isc_info_rsb_dbkey 3 +#define isc_info_rsb_index 4 + +#define isc_info_req_active 2 +#define isc_info_req_inactive 3 +#define isc_info_req_send 4 +#define isc_info_req_receive 5 +#define isc_info_req_select 6 +#define isc_info_req_sql_stall 7 + +/**************************/ +/* Blob information items */ +/**************************/ + +#define isc_info_blob_num_segments 4 +#define isc_info_blob_max_segment 5 +#define isc_info_blob_total_length 6 +#define isc_info_blob_type 7 + +/*********************************/ +/* Transaction information items */ +/*********************************/ + +#define isc_info_tra_id 4 + +/***************************** + * Service action items * + *****************************/ + +#define isc_action_svc_backup 1 /* Starts database backup process on the server */ +#define isc_action_svc_restore 2 /* Starts database restore process on the server */ +#define isc_action_svc_repair 3 /* Starts database repair process on the server */ +#define isc_action_svc_add_user 4 /* Adds a new user to the security database */ +#define isc_action_svc_delete_user 5 /* Deletes a user record from the security database */ +#define isc_action_svc_modify_user 6 /* Modifies a user record in the security database */ +#define isc_action_svc_display_user 7 /* Displays a user record from the security database */ +#define isc_action_svc_properties 8 /* Sets database properties */ +#define isc_action_svc_add_license 9 /* Adds a license to the license file */ +#define isc_action_svc_remove_license 10 /* Removes a license from the license file */ +#define isc_action_svc_db_stats 11 /* Retrieves database statistics */ +#define isc_action_svc_get_ib_log 12 /* Retrieves the InterBase log file from the server */ + +/***************************** + * Service information items * + *****************************/ + +#define isc_info_svc_svr_db_info 50 /* Retrieves the number of attachments and databases */ +#define isc_info_svc_get_license 51 /* Retrieves all license keys and IDs from the license file */ +#define isc_info_svc_get_license_mask 52 /* Retrieves a bitmask representing licensed options on the server */ +#define isc_info_svc_get_config 53 /* Retrieves the parameters and values for IB_CONFIG */ +#define isc_info_svc_version 54 /* Retrieves the version of the services manager */ +#define isc_info_svc_server_version 55 /* Retrieves the version of the InterBase server */ +#define isc_info_svc_implementation 56 /* Retrieves the implementation of the InterBase server */ +#define isc_info_svc_capabilities 57 /* Retrieves a bitmask representing the server's capabilities */ +#define isc_info_svc_user_dbpath 58 /* Retrieves the path to the security database in use by the server */ +#define isc_info_svc_get_env 59 /* Retrieves the setting of $INTERBASE */ +#define isc_info_svc_get_env_lock 60 /* Retrieves the setting of $INTERBASE_LCK */ +#define isc_info_svc_get_env_msg 61 /* Retrieves the setting of $INTERBASE_MSG */ +#define isc_info_svc_line 62 /* Retrieves 1 line of service output per call */ +#define isc_info_svc_to_eof 63 /* Retrieves as much of the server output as will fit in the supplied buffer */ +#define isc_info_svc_timeout 64 /* Sets / signifies a timeout value for reading service information */ +#define isc_info_svc_get_licensed_users 65 /* Retrieves the number of users licensed for accessing the server */ +#define isc_info_svc_limbo_trans 66 /* Retrieve the limbo transactions */ +#define isc_info_svc_running 67 /* Checks to see if a service is running on an attachment */ +#define isc_info_svc_get_users 68 /* Returns the user information from isc_action_svc_display_users */ + +/****************************************************** + * Parameters for isc_action_{add|delete|modify)_user * + ******************************************************/ + +#define isc_spb_sec_userid 5 +#define isc_spb_sec_groupid 6 +#define isc_spb_sec_username 7 +#define isc_spb_sec_password 8 +#define isc_spb_sec_groupname 9 +#define isc_spb_sec_firstname 10 +#define isc_spb_sec_middlename 11 +#define isc_spb_sec_lastname 12 + +/******************************************************* + * Parameters for isc_action_svc_(add|remove)_license, * + * isc_info_svc_get_license * + *******************************************************/ + +#define isc_spb_lic_key 5 +#define isc_spb_lic_id 6 +#define isc_spb_lic_desc 7 + + +/***************************************** + * Parameters for isc_action_svc_backup * + *****************************************/ + +#define isc_spb_bkp_file 5 +#define isc_spb_bkp_factor 6 +#define isc_spb_bkp_length 7 +#define isc_spb_bkp_ignore_checksums 0x01 +#define isc_spb_bkp_ignore_limbo 0x02 +#define isc_spb_bkp_metadata_only 0x04 +#define isc_spb_bkp_no_garbage_collect 0x08 +#define isc_spb_bkp_old_descriptions 0x10 +#define isc_spb_bkp_non_transportable 0x20 +#define isc_spb_bkp_convert 0x40 +#define isc_spb_bkp_expand 0x80 + +/******************************************** + * Parameters for isc_action_svc_properties * + ********************************************/ + +#define isc_spb_prp_page_buffers 5 +#define isc_spb_prp_sweep_interval 6 +#define isc_spb_prp_shutdown_db 7 +#define isc_spb_prp_deny_new_attachments 9 +#define isc_spb_prp_deny_new_transactions 10 +#define isc_spb_prp_reserve_space 11 +#define isc_spb_prp_write_mode 12 +#define isc_spb_prp_access_mode 13 +#define isc_spb_prp_set_sql_dialect 14 +#define isc_spb_prp_activate 0x0100 +#define isc_spb_prp_db_online 0x0200 + +/******************************************** + * Parameters for isc_spb_prp_reserve_space * + ********************************************/ + +#define isc_spb_prp_res_use_full 35 +#define isc_spb_prp_res 36 + +/****************************************** + * Parameters for isc_spb_prp_write_mode * + ******************************************/ + +#define isc_spb_prp_wm_async 37 +#define isc_spb_prp_wm_sync 38 + +/****************************************** + * Parameters for isc_spb_prp_access_mode * + ******************************************/ + +#define isc_spb_prp_am_readonly 39 +#define isc_spb_prp_am_readwrite 40 + +/***************************************** + * Parameters for isc_action_svc_repair * + *****************************************/ + +#define isc_spb_rpr_commit_trans 15 +#define isc_spb_rpr_rollback_trans 34 +#define isc_spb_rpr_recover_two_phase 17 +#define isc_spb_tra_id 18 +#define isc_spb_single_tra_id 19 +#define isc_spb_multi_tra_id 20 +#define isc_spb_tra_state 21 +#define isc_spb_tra_state_limbo 22 +#define isc_spb_tra_state_commit 23 +#define isc_spb_tra_state_rollback 24 +#define isc_spb_tra_state_unknown 25 +#define isc_spb_tra_host_site 26 +#define isc_spb_tra_remote_site 27 +#define isc_spb_tra_db_path 28 +#define isc_spb_tra_advise 29 +#define isc_spb_tra_advise_commit 30 +#define isc_spb_tra_advise_rollback 31 +#define isc_spb_tra_advise_unknown 33 + +#define isc_spb_rpr_validate_db 0x01 +#define isc_spb_rpr_sweep_db 0x02 +#define isc_spb_rpr_mend_db 0x04 +#define isc_spb_rpr_list_limbo_trans 0x08 +#define isc_spb_rpr_check_db 0x10 +#define isc_spb_rpr_ignore_checksum 0x20 +#define isc_spb_rpr_kill_shadows 0x40 +#define isc_spb_rpr_full 0x80 + +/***************************************** + * Parameters for isc_action_svc_restore * + *****************************************/ + +#define isc_spb_res_buffers 9 +#define isc_spb_res_page_size 10 +#define isc_spb_res_length 11 +#define isc_spb_res_access_mode 12 +#define isc_spb_res_deactivate_idx 0x0100 +#define isc_spb_res_no_shadow 0x0200 +#define isc_spb_res_no_validity 0x0400 +#define isc_spb_res_one_at_a_time 0x0800 +#define isc_spb_res_replace 0x1000 +#define isc_spb_res_create 0x2000 +#define isc_spb_res_use_all_space 0x4000 + +/****************************************** + * Parameters for isc_spb_res_access_mode * + ******************************************/ + +#define isc_spb_res_am_readonly isc_spb_prp_am_readonly +#define isc_spb_res_am_readwrite isc_spb_prp_am_readwrite + +/******************************************* + * Parameters for isc_info_svc_svr_db_info * + *******************************************/ + +#define isc_spb_num_att 5 +#define isc_spb_num_db 6 + +/***************************************** + * Parameters for isc_info_svc_db_stats * + *****************************************/ + +#define isc_spb_sts_data_pages 0x01 +#define isc_spb_sts_db_log 0x02 +#define isc_spb_sts_hdr_pages 0x04 +#define isc_spb_sts_idx_pages 0x08 +#define isc_spb_sts_sys_relations 0x10 +#define isc_spb_sts_record_versions 0x20 +#define isc_spb_sts_table 0x40 + +/*************************/ +/* SQL information items */ +/*************************/ + +#define isc_info_sql_select 4 +#define isc_info_sql_bind 5 +#define isc_info_sql_num_variables 6 +#define isc_info_sql_describe_vars 7 +#define isc_info_sql_describe_end 8 +#define isc_info_sql_sqlda_seq 9 +#define isc_info_sql_message_seq 10 +#define isc_info_sql_type 11 +#define isc_info_sql_sub_type 12 +#define isc_info_sql_scale 13 +#define isc_info_sql_length 14 +#define isc_info_sql_null_ind 15 +#define isc_info_sql_field 16 +#define isc_info_sql_relation 17 +#define isc_info_sql_owner 18 +#define isc_info_sql_alias 19 +#define isc_info_sql_sqlda_start 20 +#define isc_info_sql_stmt_type 21 +#define isc_info_sql_get_plan 22 +#define isc_info_sql_records 23 +#define isc_info_sql_batch_fetch 24 + +/*********************************/ +/* SQL information return values */ +/*********************************/ + +#define isc_info_sql_stmt_select 1 +#define isc_info_sql_stmt_insert 2 +#define isc_info_sql_stmt_update 3 +#define isc_info_sql_stmt_delete 4 +#define isc_info_sql_stmt_ddl 5 +#define isc_info_sql_stmt_get_segment 6 +#define isc_info_sql_stmt_put_segment 7 +#define isc_info_sql_stmt_exec_procedure 8 +#define isc_info_sql_stmt_start_trans 9 +#define isc_info_sql_stmt_commit 10 +#define isc_info_sql_stmt_rollback 11 +#define isc_info_sql_stmt_select_for_upd 12 +#define isc_info_sql_stmt_set_generator 13 +#define isc_info_sql_stmt_savepoint 14 + + +/***********************************/ +/* Server configuration key values */ +/***********************************/ + +/* Not available in Firebird 1.5 */ + + +/**********************************************/ +/* Dynamic Data Definition Language operators */ +/**********************************************/ + +/******************/ +/* Version number */ +/******************/ + +#define isc_dyn_version_1 1 +#define isc_dyn_eoc 255 + +/******************************/ +/* Operations (may be nested) */ +/******************************/ + +#define isc_dyn_begin 2 +#define isc_dyn_end 3 +#define isc_dyn_if 4 +#define isc_dyn_def_database 5 +#define isc_dyn_def_global_fld 6 +#define isc_dyn_def_local_fld 7 +#define isc_dyn_def_idx 8 +#define isc_dyn_def_rel 9 +#define isc_dyn_def_sql_fld 10 +#define isc_dyn_def_view 12 +#define isc_dyn_def_trigger 15 +#define isc_dyn_def_security_class 120 +#define isc_dyn_def_dimension 140 +#define isc_dyn_def_generator 24 +#define isc_dyn_def_function 25 +#define isc_dyn_def_filter 26 +#define isc_dyn_def_function_arg 27 +#define isc_dyn_def_shadow 34 +#define isc_dyn_def_trigger_msg 17 +#define isc_dyn_def_file 36 +#define isc_dyn_mod_database 39 +#define isc_dyn_mod_rel 11 +#define isc_dyn_mod_global_fld 13 +#define isc_dyn_mod_idx 102 +#define isc_dyn_mod_local_fld 14 +#define isc_dyn_mod_sql_fld 216 +#define isc_dyn_mod_view 16 +#define isc_dyn_mod_security_class 122 +#define isc_dyn_mod_trigger 113 +#define isc_dyn_mod_trigger_msg 28 +#define isc_dyn_delete_database 18 +#define isc_dyn_delete_rel 19 +#define isc_dyn_delete_global_fld 20 +#define isc_dyn_delete_local_fld 21 +#define isc_dyn_delete_idx 22 +#define isc_dyn_delete_security_class 123 +#define isc_dyn_delete_dimensions 143 +#define isc_dyn_delete_trigger 23 +#define isc_dyn_delete_trigger_msg 29 +#define isc_dyn_delete_filter 32 +#define isc_dyn_delete_function 33 +#define isc_dyn_delete_shadow 35 +#define isc_dyn_grant 30 +#define isc_dyn_revoke 31 +#define isc_dyn_def_primary_key 37 +#define isc_dyn_def_foreign_key 38 +#define isc_dyn_def_unique 40 +#define isc_dyn_def_procedure 164 +#define isc_dyn_delete_procedure 165 +#define isc_dyn_def_parameter 135 +#define isc_dyn_delete_parameter 136 +#define isc_dyn_mod_procedure 175 +#define isc_dyn_def_log_file 176 +#define isc_dyn_def_cache_file 180 +#define isc_dyn_def_exception 181 +#define isc_dyn_mod_exception 182 +#define isc_dyn_del_exception 183 +#define isc_dyn_drop_log 194 +#define isc_dyn_drop_cache 195 +#define isc_dyn_def_default_log 202 + +/***********************/ +/* View specific stuff */ +/***********************/ + +#define isc_dyn_view_blr 43 +#define isc_dyn_view_source 44 +#define isc_dyn_view_relation 45 +#define isc_dyn_view_context 46 +#define isc_dyn_view_context_name 47 + +/**********************/ +/* Generic attributes */ +/**********************/ + +#define isc_dyn_rel_name 50 +#define isc_dyn_fld_name 51 +#define isc_dyn_new_fld_name 215 +#define isc_dyn_idx_name 52 +#define isc_dyn_description 53 +#define isc_dyn_security_class 54 +#define isc_dyn_system_flag 55 +#define isc_dyn_update_flag 56 +#define isc_dyn_prc_name 166 +#define isc_dyn_prm_name 137 +#define isc_dyn_sql_object 196 +#define isc_dyn_fld_character_set_name 174 + +/********************************/ +/* Relation specific attributes */ +/********************************/ + +#define isc_dyn_rel_dbkey_length 61 +#define isc_dyn_rel_store_trig 62 +#define isc_dyn_rel_modify_trig 63 +#define isc_dyn_rel_erase_trig 64 +#define isc_dyn_rel_store_trig_source 65 +#define isc_dyn_rel_modify_trig_source 66 +#define isc_dyn_rel_erase_trig_source 67 +#define isc_dyn_rel_ext_file 68 +#define isc_dyn_rel_sql_protection 69 +#define isc_dyn_rel_constraint 162 +#define isc_dyn_delete_rel_constraint 163 + +/************************************/ +/* Global field specific attributes */ +/************************************/ + +#define isc_dyn_fld_type 70 +#define isc_dyn_fld_length 71 +#define isc_dyn_fld_scale 72 +#define isc_dyn_fld_sub_type 73 +#define isc_dyn_fld_segment_length 74 +#define isc_dyn_fld_query_header 75 +#define isc_dyn_fld_edit_string 76 +#define isc_dyn_fld_validation_blr 77 +#define isc_dyn_fld_validation_source 78 +#define isc_dyn_fld_computed_blr 79 +#define isc_dyn_fld_computed_source 80 +#define isc_dyn_fld_missing_value 81 +#define isc_dyn_fld_default_value 82 +#define isc_dyn_fld_query_name 83 +#define isc_dyn_fld_dimensions 84 +#define isc_dyn_fld_not_null 85 +#define isc_dyn_fld_precision 86 +#define isc_dyn_fld_char_length 172 +#define isc_dyn_fld_collation 173 +#define isc_dyn_fld_default_source 193 +#define isc_dyn_del_default 197 +#define isc_dyn_del_validation 198 +#define isc_dyn_single_validation 199 +#define isc_dyn_fld_character_set 203 + +/***********************************/ +/* Local field specific attributes */ +/***********************************/ + +#define isc_dyn_fld_source 90 +#define isc_dyn_fld_base_fld 91 +#define isc_dyn_fld_position 92 +#define isc_dyn_fld_update_flag 93 + +/*****************************/ +/* Index specific attributes */ +/*****************************/ + +#define isc_dyn_idx_unique 100 +#define isc_dyn_idx_inactive 101 +#define isc_dyn_idx_type 103 +#define isc_dyn_idx_foreign_key 104 +#define isc_dyn_idx_ref_column 105 +#define isc_dyn_idx_statistic 204 + +/*******************************/ +/* Trigger specific attributes */ +/*******************************/ + +#define isc_dyn_trg_type 110 +#define isc_dyn_trg_blr 111 +#define isc_dyn_trg_source 112 +#define isc_dyn_trg_name 114 +#define isc_dyn_trg_sequence 115 +#define isc_dyn_trg_inactive 116 +#define isc_dyn_trg_msg_number 117 +#define isc_dyn_trg_msg 118 + +/**************************************/ +/* Security Class specific attributes */ +/**************************************/ + +#define isc_dyn_scl_acl 121 +#define isc_dyn_grant_user 130 +#define isc_dyn_grant_user_explicit 219 +#define isc_dyn_grant_proc 186 +#define isc_dyn_grant_trig 187 +#define isc_dyn_grant_view 188 +#define isc_dyn_grant_options 132 +#define isc_dyn_grant_user_group 205 +#define isc_dyn_grant_role 218 + + +/**********************************/ +/* Dimension specific information */ +/**********************************/ + +#define isc_dyn_dim_lower 141 +#define isc_dyn_dim_upper 142 + +/****************************/ +/* File specific attributes */ +/****************************/ + +#define isc_dyn_file_name 125 +#define isc_dyn_file_start 126 +#define isc_dyn_file_length 127 +#define isc_dyn_shadow_number 128 +#define isc_dyn_shadow_man_auto 129 +#define isc_dyn_shadow_conditional 130 + +/********************************/ +/* Log file specific attributes */ +/********************************/ + +#define isc_dyn_log_file_sequence 177 +#define isc_dyn_log_file_partitions 178 +#define isc_dyn_log_file_serial 179 +#define isc_dyn_log_file_overflow 200 +#define isc_dyn_log_file_raw 201 + +/***************************/ +/* Log specific attributes */ +/***************************/ + +#define isc_dyn_log_group_commit_wait 189 +#define isc_dyn_log_buffer_size 190 +#define isc_dyn_log_check_point_length 191 +#define isc_dyn_log_num_of_buffers 192 + +/********************************/ +/* Function specific attributes */ +/********************************/ + +#define isc_dyn_function_name 145 +#define isc_dyn_function_type 146 +#define isc_dyn_func_module_name 147 +#define isc_dyn_func_entry_point 148 +#define isc_dyn_func_return_argument 149 +#define isc_dyn_func_arg_position 150 +#define isc_dyn_func_mechanism 151 +#define isc_dyn_filter_in_subtype 152 +#define isc_dyn_filter_out_subtype 153 + + +#define isc_dyn_description2 154 +#define isc_dyn_fld_computed_source2 155 +#define isc_dyn_fld_edit_string2 156 +#define isc_dyn_fld_query_header2 157 +#define isc_dyn_fld_validation_source2 158 +#define isc_dyn_trg_msg2 159 +#define isc_dyn_trg_source2 160 +#define isc_dyn_view_source2 161 +#define isc_dyn_xcp_msg2 184 + +/*********************************/ +/* Generator specific attributes */ +/*********************************/ + +#define isc_dyn_generator_name 95 +#define isc_dyn_generator_id 96 + +/*********************************/ +/* Procedure specific attributes */ +/*********************************/ + +#define isc_dyn_prc_inputs 167 +#define isc_dyn_prc_outputs 168 +#define isc_dyn_prc_source 169 +#define isc_dyn_prc_blr 170 +#define isc_dyn_prc_source2 171 + +/*********************************/ +/* Parameter specific attributes */ +/*********************************/ + +#define isc_dyn_prm_number 138 +#define isc_dyn_prm_type 139 + +/********************************/ +/* Relation specific attributes */ +/********************************/ + +#define isc_dyn_xcp_msg 185 + +/**********************************************/ +/* Cascading referential integrity values */ +/**********************************************/ +#define isc_dyn_foreign_key_update 205 +#define isc_dyn_foreign_key_delete 206 +#define isc_dyn_foreign_key_cascade 207 +#define isc_dyn_foreign_key_default 208 +#define isc_dyn_foreign_key_null 209 +#define isc_dyn_foreign_key_none 210 + +/***********************/ +/* SQL role values */ +/***********************/ +#define isc_dyn_def_sql_role 211 +#define isc_dyn_sql_role_name 212 +#define isc_dyn_grant_admin_options 213 +#define isc_dyn_del_sql_role 214 +/* 215 & 216 are used some lines above. */ + +/**********************************************/ +/* Generators again */ +/**********************************************/ + +#ifndef __cplusplus /* c definitions */ +#define gds_dyn_delete_generator 217 +#else /* c++ definitions */ +const unsigned char gds_dyn_delete_generator = 217; +#endif + +/****************************/ +/* Last $dyn value assigned */ +/****************************/ + +#define isc_dyn_last_dyn_value 219 + +/******************************************/ +/* Array slice description language (SDL) */ +/******************************************/ + +#define isc_sdl_version1 1 +#define isc_sdl_eoc 255 +#define isc_sdl_relation 2 +#define isc_sdl_rid 3 +#define isc_sdl_field 4 +#define isc_sdl_fid 5 +#define isc_sdl_struct 6 +#define isc_sdl_variable 7 +#define isc_sdl_scalar 8 +#define isc_sdl_tiny_integer 9 +#define isc_sdl_short_integer 10 +#define isc_sdl_long_integer 11 +#define isc_sdl_literal 12 +#define isc_sdl_add 13 +#define isc_sdl_subtract 14 +#define isc_sdl_multiply 15 +#define isc_sdl_divide 16 +#define isc_sdl_negate 17 +#define isc_sdl_eql 18 +#define isc_sdl_neq 19 +#define isc_sdl_gtr 20 +#define isc_sdl_geq 21 +#define isc_sdl_lss 22 +#define isc_sdl_leq 23 +#define isc_sdl_and 24 +#define isc_sdl_or 25 +#define isc_sdl_not 26 +#define isc_sdl_while 27 +#define isc_sdl_assignment 28 +#define isc_sdl_label 29 +#define isc_sdl_leave 30 +#define isc_sdl_begin 31 +#define isc_sdl_end 32 +#define isc_sdl_do3 33 +#define isc_sdl_do2 34 +#define isc_sdl_do1 35 +#define isc_sdl_element 36 + +/********************************************/ +/* International text interpretation values */ +/********************************************/ + +#define isc_interp_eng_ascii 0 +#define isc_interp_jpn_sjis 5 +#define isc_interp_jpn_euc 6 + +/*******************/ +/* SQL definitions */ +/*******************/ + +#define SQL_TEXT 452 +#define SQL_VARYING 448 +#define SQL_SHORT 500 +#define SQL_LONG 496 +#define SQL_FLOAT 482 +#define SQL_DOUBLE 480 +#define SQL_D_FLOAT 530 +#define SQL_TIMESTAMP 510 +#define SQL_BLOB 520 +#define SQL_ARRAY 540 +#define SQL_QUAD 550 +#define SQL_TYPE_TIME 560 +#define SQL_TYPE_DATE 570 +#define SQL_INT64 580 + +/* Historical alias for pre V6 applications */ +#define SQL_DATE SQL_TIMESTAMP + +/*****************/ +/* Blob Subtypes */ +/*****************/ + +/* types less than zero are reserved for customer use */ + +#define isc_blob_untyped 0 + +/* internal subtypes */ + +#define isc_blob_text 1 +#define isc_blob_blr 2 +#define isc_blob_acl 3 +#define isc_blob_ranges 4 +#define isc_blob_summary 5 +#define isc_blob_format 6 +#define isc_blob_tra 7 +#define isc_blob_extfile 8 + +/* the range 20-30 is reserved for dBASE and Paradox types */ + +#define isc_blob_formatted_memo 20 +#define isc_blob_paradox_ole 21 +#define isc_blob_graphic 22 +#define isc_blob_dbase_ole 23 +#define isc_blob_typed_binary 24 + +/* Deprecated definitions maintained for compatibility only */ + +#define isc_info_db_SQL_dialect 62 +#define isc_dpb_SQL_dialect 63 +#define isc_dpb_set_db_SQL_dialect 65 + + +#include "iberror.h" + +#endif /* JRD_IBASE_H */ + diff --git a/stglibs/ibpp.lib/iberror.h b/stglibs/ibpp.lib/iberror.h new file mode 100644 index 00000000..653beb6b --- /dev/null +++ b/stglibs/ibpp.lib/iberror.h @@ -0,0 +1,771 @@ + +#ifndef _JRD_GEN_IBERROR_H +#define _JRD_GEN_IBERROR_H +/* + * The contents of this file are subject to the Interbase Public + * License Version 1.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy + * of the License at http://www.Inprise.com/IPL.html + * + * Software distributed under the License is distributed on an + * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express + * or implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The content of this file was generated by the Firebird project + * using the program jrd/codes.epp + */ +/* + * + * *** WARNING *** - This file is automatically generated by codes.e - do not edit! + * + */ +/* + * MODULE: iberror.h + * DESCRIPTION: ISC error codes + * + */ + + + +/***********************/ +/* ISC Error Codes */ +/***********************/ + +#define isc_facility 20 +#define isc_base 335544320L +#define isc_factor 1 + +#define isc_arg_end 0 /* end of argument list */ +#define isc_arg_gds 1 /* generic DSRI status value */ +#define isc_arg_string 2 /* string argument */ +#define isc_arg_cstring 3 /* count & string argument */ +#define isc_arg_number 4 /* numeric argument (long) */ +#define isc_arg_interpreted 5 /* interpreted status code (string) */ +#define isc_arg_vms 6 /* VAX/VMS status code (long) */ +#define isc_arg_unix 7 /* UNIX error code */ +#define isc_arg_domain 8 /* Apollo/Domain error code */ +#define isc_arg_dos 9 /* MSDOS/OS2 error code */ +#define isc_arg_mpexl 10 /* HP MPE/XL error code */ +#define isc_arg_mpexl_ipc 11 /* HP MPE/XL IPC error code */ +#define isc_arg_next_mach 15 /* NeXT/Mach error code */ +#define isc_arg_netware 16 /* NetWare error code */ +#define isc_arg_win32 17 /* Win32 error code */ +#define isc_arg_warning 18 /* warning argument */ + +#define isc_arith_except 335544321L +#define isc_bad_dbkey 335544322L +#define isc_bad_db_format 335544323L +#define isc_bad_db_handle 335544324L +#define isc_bad_dpb_content 335544325L +#define isc_bad_dpb_form 335544326L +#define isc_bad_req_handle 335544327L +#define isc_bad_segstr_handle 335544328L +#define isc_bad_segstr_id 335544329L +#define isc_bad_tpb_content 335544330L +#define isc_bad_tpb_form 335544331L +#define isc_bad_trans_handle 335544332L +#define isc_bug_check 335544333L +#define isc_convert_error 335544334L +#define isc_db_corrupt 335544335L +#define isc_deadlock 335544336L +#define isc_excess_trans 335544337L +#define isc_from_no_match 335544338L +#define isc_infinap 335544339L +#define isc_infona 335544340L +#define isc_infunk 335544341L +#define isc_integ_fail 335544342L +#define isc_invalid_blr 335544343L +#define isc_io_error 335544344L +#define isc_lock_conflict 335544345L +#define isc_metadata_corrupt 335544346L +#define isc_not_valid 335544347L +#define isc_no_cur_rec 335544348L +#define isc_no_dup 335544349L +#define isc_no_finish 335544350L +#define isc_no_meta_update 335544351L +#define isc_no_priv 335544352L +#define isc_no_recon 335544353L +#define isc_no_record 335544354L +#define isc_no_segstr_close 335544355L +#define isc_obsolete_metadata 335544356L +#define isc_open_trans 335544357L +#define isc_port_len 335544358L +#define isc_read_only_field 335544359L +#define isc_read_only_rel 335544360L +#define isc_read_only_trans 335544361L +#define isc_read_only_view 335544362L +#define isc_req_no_trans 335544363L +#define isc_req_sync 335544364L +#define isc_req_wrong_db 335544365L +#define isc_segment 335544366L +#define isc_segstr_eof 335544367L +#define isc_segstr_no_op 335544368L +#define isc_segstr_no_read 335544369L +#define isc_segstr_no_trans 335544370L +#define isc_segstr_no_write 335544371L +#define isc_segstr_wrong_db 335544372L +#define isc_sys_request 335544373L +#define isc_stream_eof 335544374L +#define isc_unavailable 335544375L +#define isc_unres_rel 335544376L +#define isc_uns_ext 335544377L +#define isc_wish_list 335544378L +#define isc_wrong_ods 335544379L +#define isc_wronumarg 335544380L +#define isc_imp_exc 335544381L +#define isc_random 335544382L +#define isc_fatal_conflict 335544383L +#define isc_badblk 335544384L +#define isc_invpoolcl 335544385L +#define isc_nopoolids 335544386L +#define isc_relbadblk 335544387L +#define isc_blktoobig 335544388L +#define isc_bufexh 335544389L +#define isc_syntaxerr 335544390L +#define isc_bufinuse 335544391L +#define isc_bdbincon 335544392L +#define isc_reqinuse 335544393L +#define isc_badodsver 335544394L +#define isc_relnotdef 335544395L +#define isc_fldnotdef 335544396L +#define isc_dirtypage 335544397L +#define isc_waifortra 335544398L +#define isc_doubleloc 335544399L +#define isc_nodnotfnd 335544400L +#define isc_dupnodfnd 335544401L +#define isc_locnotmar 335544402L +#define isc_badpagtyp 335544403L +#define isc_corrupt 335544404L +#define isc_badpage 335544405L +#define isc_badindex 335544406L +#define isc_dbbnotzer 335544407L +#define isc_tranotzer 335544408L +#define isc_trareqmis 335544409L +#define isc_badhndcnt 335544410L +#define isc_wrotpbver 335544411L +#define isc_wroblrver 335544412L +#define isc_wrodpbver 335544413L +#define isc_blobnotsup 335544414L +#define isc_badrelation 335544415L +#define isc_nodetach 335544416L +#define isc_notremote 335544417L +#define isc_trainlim 335544418L +#define isc_notinlim 335544419L +#define isc_traoutsta 335544420L +#define isc_connect_reject 335544421L +#define isc_dbfile 335544422L +#define isc_orphan 335544423L +#define isc_no_lock_mgr 335544424L +#define isc_ctxinuse 335544425L +#define isc_ctxnotdef 335544426L +#define isc_datnotsup 335544427L +#define isc_badmsgnum 335544428L +#define isc_badparnum 335544429L +#define isc_virmemexh 335544430L +#define isc_blocking_signal 335544431L +#define isc_lockmanerr 335544432L +#define isc_journerr 335544433L +#define isc_keytoobig 335544434L +#define isc_nullsegkey 335544435L +#define isc_sqlerr 335544436L +#define isc_wrodynver 335544437L +#define isc_funnotdef 335544438L +#define isc_funmismat 335544439L +#define isc_bad_msg_vec 335544440L +#define isc_bad_detach 335544441L +#define isc_noargacc_read 335544442L +#define isc_noargacc_write 335544443L +#define isc_read_only 335544444L +#define isc_ext_err 335544445L +#define isc_non_updatable 335544446L +#define isc_no_rollback 335544447L +#define isc_bad_sec_info 335544448L +#define isc_invalid_sec_info 335544449L +#define isc_misc_interpreted 335544450L +#define isc_update_conflict 335544451L +#define isc_unlicensed 335544452L +#define isc_obj_in_use 335544453L +#define isc_nofilter 335544454L +#define isc_shadow_accessed 335544455L +#define isc_invalid_sdl 335544456L +#define isc_out_of_bounds 335544457L +#define isc_invalid_dimension 335544458L +#define isc_rec_in_limbo 335544459L +#define isc_shadow_missing 335544460L +#define isc_cant_validate 335544461L +#define isc_cant_start_journal 335544462L +#define isc_gennotdef 335544463L +#define isc_cant_start_logging 335544464L +#define isc_bad_segstr_type 335544465L +#define isc_foreign_key 335544466L +#define isc_high_minor 335544467L +#define isc_tra_state 335544468L +#define isc_trans_invalid 335544469L +#define isc_buf_invalid 335544470L +#define isc_indexnotdefined 335544471L +#define isc_login 335544472L +#define isc_invalid_bookmark 335544473L +#define isc_bad_lock_level 335544474L +#define isc_relation_lock 335544475L +#define isc_record_lock 335544476L +#define isc_max_idx 335544477L +#define isc_jrn_enable 335544478L +#define isc_old_failure 335544479L +#define isc_old_in_progress 335544480L +#define isc_old_no_space 335544481L +#define isc_no_wal_no_jrn 335544482L +#define isc_num_old_files 335544483L +#define isc_wal_file_open 335544484L +#define isc_bad_stmt_handle 335544485L +#define isc_wal_failure 335544486L +#define isc_walw_err 335544487L +#define isc_logh_small 335544488L +#define isc_logh_inv_version 335544489L +#define isc_logh_open_flag 335544490L +#define isc_logh_open_flag2 335544491L +#define isc_logh_diff_dbname 335544492L +#define isc_logf_unexpected_eof 335544493L +#define isc_logr_incomplete 335544494L +#define isc_logr_header_small 335544495L +#define isc_logb_small 335544496L +#define isc_wal_illegal_attach 335544497L +#define isc_wal_invalid_wpb 335544498L +#define isc_wal_err_rollover 335544499L +#define isc_no_wal 335544500L +#define isc_drop_wal 335544501L +#define isc_stream_not_defined 335544502L +#define isc_wal_subsys_error 335544503L +#define isc_wal_subsys_corrupt 335544504L +#define isc_no_archive 335544505L +#define isc_shutinprog 335544506L +#define isc_range_in_use 335544507L +#define isc_range_not_found 335544508L +#define isc_charset_not_found 335544509L +#define isc_lock_timeout 335544510L +#define isc_prcnotdef 335544511L +#define isc_prcmismat 335544512L +#define isc_wal_bugcheck 335544513L +#define isc_wal_cant_expand 335544514L +#define isc_codnotdef 335544515L +#define isc_xcpnotdef 335544516L +#define isc_except 335544517L +#define isc_cache_restart 335544518L +#define isc_bad_lock_handle 335544519L +#define isc_jrn_present 335544520L +#define isc_wal_err_rollover2 335544521L +#define isc_wal_err_logwrite 335544522L +#define isc_wal_err_jrn_comm 335544523L +#define isc_wal_err_expansion 335544524L +#define isc_wal_err_setup 335544525L +#define isc_wal_err_ww_sync 335544526L +#define isc_wal_err_ww_start 335544527L +#define isc_shutdown 335544528L +#define isc_existing_priv_mod 335544529L +#define isc_primary_key_ref 335544530L +#define isc_primary_key_notnull 335544531L +#define isc_ref_cnstrnt_notfound 335544532L +#define isc_foreign_key_notfound 335544533L +#define isc_ref_cnstrnt_update 335544534L +#define isc_check_cnstrnt_update 335544535L +#define isc_check_cnstrnt_del 335544536L +#define isc_integ_index_seg_del 335544537L +#define isc_integ_index_seg_mod 335544538L +#define isc_integ_index_del 335544539L +#define isc_integ_index_mod 335544540L +#define isc_check_trig_del 335544541L +#define isc_check_trig_update 335544542L +#define isc_cnstrnt_fld_del 335544543L +#define isc_cnstrnt_fld_rename 335544544L +#define isc_rel_cnstrnt_update 335544545L +#define isc_constaint_on_view 335544546L +#define isc_invld_cnstrnt_type 335544547L +#define isc_primary_key_exists 335544548L +#define isc_systrig_update 335544549L +#define isc_not_rel_owner 335544550L +#define isc_grant_obj_notfound 335544551L +#define isc_grant_fld_notfound 335544552L +#define isc_grant_nopriv 335544553L +#define isc_nonsql_security_rel 335544554L +#define isc_nonsql_security_fld 335544555L +#define isc_wal_cache_err 335544556L +#define isc_shutfail 335544557L +#define isc_check_constraint 335544558L +#define isc_bad_svc_handle 335544559L +#define isc_shutwarn 335544560L +#define isc_wrospbver 335544561L +#define isc_bad_spb_form 335544562L +#define isc_svcnotdef 335544563L +#define isc_no_jrn 335544564L +#define isc_transliteration_failed 335544565L +#define isc_start_cm_for_wal 335544566L +#define isc_wal_ovflow_log_required 335544567L +#define isc_text_subtype 335544568L +#define isc_dsql_error 335544569L +#define isc_dsql_command_err 335544570L +#define isc_dsql_constant_err 335544571L +#define isc_dsql_cursor_err 335544572L +#define isc_dsql_datatype_err 335544573L +#define isc_dsql_decl_err 335544574L +#define isc_dsql_cursor_update_err 335544575L +#define isc_dsql_cursor_open_err 335544576L +#define isc_dsql_cursor_close_err 335544577L +#define isc_dsql_field_err 335544578L +#define isc_dsql_internal_err 335544579L +#define isc_dsql_relation_err 335544580L +#define isc_dsql_procedure_err 335544581L +#define isc_dsql_request_err 335544582L +#define isc_dsql_sqlda_err 335544583L +#define isc_dsql_var_count_err 335544584L +#define isc_dsql_stmt_handle 335544585L +#define isc_dsql_function_err 335544586L +#define isc_dsql_blob_err 335544587L +#define isc_collation_not_found 335544588L +#define isc_collation_not_for_charset 335544589L +#define isc_dsql_dup_option 335544590L +#define isc_dsql_tran_err 335544591L +#define isc_dsql_invalid_array 335544592L +#define isc_dsql_max_arr_dim_exceeded 335544593L +#define isc_dsql_arr_range_error 335544594L +#define isc_dsql_trigger_err 335544595L +#define isc_dsql_subselect_err 335544596L +#define isc_dsql_crdb_prepare_err 335544597L +#define isc_specify_field_err 335544598L +#define isc_num_field_err 335544599L +#define isc_col_name_err 335544600L +#define isc_where_err 335544601L +#define isc_table_view_err 335544602L +#define isc_distinct_err 335544603L +#define isc_key_field_count_err 335544604L +#define isc_subquery_err 335544605L +#define isc_expression_eval_err 335544606L +#define isc_node_err 335544607L +#define isc_command_end_err 335544608L +#define isc_index_name 335544609L +#define isc_exception_name 335544610L +#define isc_field_name 335544611L +#define isc_token_err 335544612L +#define isc_union_err 335544613L +#define isc_dsql_construct_err 335544614L +#define isc_field_aggregate_err 335544615L +#define isc_field_ref_err 335544616L +#define isc_order_by_err 335544617L +#define isc_return_mode_err 335544618L +#define isc_extern_func_err 335544619L +#define isc_alias_conflict_err 335544620L +#define isc_procedure_conflict_error 335544621L +#define isc_relation_conflict_err 335544622L +#define isc_dsql_domain_err 335544623L +#define isc_idx_seg_err 335544624L +#define isc_node_name_err 335544625L +#define isc_table_name 335544626L +#define isc_proc_name 335544627L +#define isc_idx_create_err 335544628L +#define isc_wal_shadow_err 335544629L +#define isc_dependency 335544630L +#define isc_idx_key_err 335544631L +#define isc_dsql_file_length_err 335544632L +#define isc_dsql_shadow_number_err 335544633L +#define isc_dsql_token_unk_err 335544634L +#define isc_dsql_no_relation_alias 335544635L +#define isc_indexname 335544636L +#define isc_no_stream_plan 335544637L +#define isc_stream_twice 335544638L +#define isc_stream_not_found 335544639L +#define isc_collation_requires_text 335544640L +#define isc_dsql_domain_not_found 335544641L +#define isc_index_unused 335544642L +#define isc_dsql_self_join 335544643L +#define isc_stream_bof 335544644L +#define isc_stream_crack 335544645L +#define isc_db_or_file_exists 335544646L +#define isc_invalid_operator 335544647L +#define isc_conn_lost 335544648L +#define isc_bad_checksum 335544649L +#define isc_page_type_err 335544650L +#define isc_ext_readonly_err 335544651L +#define isc_sing_select_err 335544652L +#define isc_psw_attach 335544653L +#define isc_psw_start_trans 335544654L +#define isc_invalid_direction 335544655L +#define isc_dsql_var_conflict 335544656L +#define isc_dsql_no_blob_array 335544657L +#define isc_dsql_base_table 335544658L +#define isc_duplicate_base_table 335544659L +#define isc_view_alias 335544660L +#define isc_index_root_page_full 335544661L +#define isc_dsql_blob_type_unknown 335544662L +#define isc_req_max_clones_exceeded 335544663L +#define isc_dsql_duplicate_spec 335544664L +#define isc_unique_key_violation 335544665L +#define isc_srvr_version_too_old 335544666L +#define isc_drdb_completed_with_errs 335544667L +#define isc_dsql_procedure_use_err 335544668L +#define isc_dsql_count_mismatch 335544669L +#define isc_blob_idx_err 335544670L +#define isc_array_idx_err 335544671L +#define isc_key_field_err 335544672L +#define isc_no_delete 335544673L +#define isc_del_last_field 335544674L +#define isc_sort_err 335544675L +#define isc_sort_mem_err 335544676L +#define isc_version_err 335544677L +#define isc_inval_key_posn 335544678L +#define isc_no_segments_err 335544679L +#define isc_crrp_data_err 335544680L +#define isc_rec_size_err 335544681L +#define isc_dsql_field_ref 335544682L +#define isc_req_depth_exceeded 335544683L +#define isc_no_field_access 335544684L +#define isc_no_dbkey 335544685L +#define isc_jrn_format_err 335544686L +#define isc_jrn_file_full 335544687L +#define isc_dsql_open_cursor_request 335544688L +#define isc_ib_error 335544689L +#define isc_cache_redef 335544690L +#define isc_cache_too_small 335544691L +#define isc_log_redef 335544692L +#define isc_log_too_small 335544693L +#define isc_partition_too_small 335544694L +#define isc_partition_not_supp 335544695L +#define isc_log_length_spec 335544696L +#define isc_precision_err 335544697L +#define isc_scale_nogt 335544698L +#define isc_expec_short 335544699L +#define isc_expec_long 335544700L +#define isc_expec_ushort 335544701L +#define isc_like_escape_invalid 335544702L +#define isc_svcnoexe 335544703L +#define isc_net_lookup_err 335544704L +#define isc_service_unknown 335544705L +#define isc_host_unknown 335544706L +#define isc_grant_nopriv_on_base 335544707L +#define isc_dyn_fld_ambiguous 335544708L +#define isc_dsql_agg_ref_err 335544709L +#define isc_complex_view 335544710L +#define isc_unprepared_stmt 335544711L +#define isc_expec_positive 335544712L +#define isc_dsql_sqlda_value_err 335544713L +#define isc_invalid_array_id 335544714L +#define isc_extfile_uns_op 335544715L +#define isc_svc_in_use 335544716L +#define isc_err_stack_limit 335544717L +#define isc_invalid_key 335544718L +#define isc_net_init_error 335544719L +#define isc_loadlib_failure 335544720L +#define isc_network_error 335544721L +#define isc_net_connect_err 335544722L +#define isc_net_connect_listen_err 335544723L +#define isc_net_event_connect_err 335544724L +#define isc_net_event_listen_err 335544725L +#define isc_net_read_err 335544726L +#define isc_net_write_err 335544727L +#define isc_integ_index_deactivate 335544728L +#define isc_integ_deactivate_primary 335544729L +#define isc_cse_not_supported 335544730L +#define isc_tra_must_sweep 335544731L +#define isc_unsupported_network_drive 335544732L +#define isc_io_create_err 335544733L +#define isc_io_open_err 335544734L +#define isc_io_close_err 335544735L +#define isc_io_read_err 335544736L +#define isc_io_write_err 335544737L +#define isc_io_delete_err 335544738L +#define isc_io_access_err 335544739L +#define isc_udf_exception 335544740L +#define isc_lost_db_connection 335544741L +#define isc_no_write_user_priv 335544742L +#define isc_token_too_long 335544743L +#define isc_max_att_exceeded 335544744L +#define isc_login_same_as_role_name 335544745L +#define isc_reftable_requires_pk 335544746L +#define isc_usrname_too_long 335544747L +#define isc_password_too_long 335544748L +#define isc_usrname_required 335544749L +#define isc_password_required 335544750L +#define isc_bad_protocol 335544751L +#define isc_dup_usrname_found 335544752L +#define isc_usrname_not_found 335544753L +#define isc_error_adding_sec_record 335544754L +#define isc_error_modifying_sec_record 335544755L +#define isc_error_deleting_sec_record 335544756L +#define isc_error_updating_sec_db 335544757L +#define isc_sort_rec_size_err 335544758L +#define isc_bad_default_value 335544759L +#define isc_invalid_clause 335544760L +#define isc_too_many_handles 335544761L +#define isc_optimizer_blk_exc 335544762L +#define isc_invalid_string_constant 335544763L +#define isc_transitional_date 335544764L +#define isc_read_only_database 335544765L +#define isc_must_be_dialect_2_and_up 335544766L +#define isc_blob_filter_exception 335544767L +#define isc_exception_access_violation 335544768L +#define isc_exception_datatype_missalignment 335544769L +#define isc_exception_array_bounds_exceeded 335544770L +#define isc_exception_float_denormal_operand 335544771L +#define isc_exception_float_divide_by_zero 335544772L +#define isc_exception_float_inexact_result 335544773L +#define isc_exception_float_invalid_operand 335544774L +#define isc_exception_float_overflow 335544775L +#define isc_exception_float_stack_check 335544776L +#define isc_exception_float_underflow 335544777L +#define isc_exception_integer_divide_by_zero 335544778L +#define isc_exception_integer_overflow 335544779L +#define isc_exception_unknown 335544780L +#define isc_exception_stack_overflow 335544781L +#define isc_exception_sigsegv 335544782L +#define isc_exception_sigill 335544783L +#define isc_exception_sigbus 335544784L +#define isc_exception_sigfpe 335544785L +#define isc_ext_file_delete 335544786L +#define isc_ext_file_modify 335544787L +#define isc_adm_task_denied 335544788L +#define isc_extract_input_mismatch 335544789L +#define isc_insufficient_svc_privileges 335544790L +#define isc_file_in_use 335544791L +#define isc_service_att_err 335544792L +#define isc_ddl_not_allowed_by_db_sql_dial 335544793L +#define isc_cancelled 335544794L +#define isc_unexp_spb_form 335544795L +#define isc_sql_dialect_datatype_unsupport 335544796L +#define isc_svcnouser 335544797L +#define isc_depend_on_uncommitted_rel 335544798L +#define isc_svc_name_missing 335544799L +#define isc_too_many_contexts 335544800L +#define isc_datype_notsup 335544801L +#define isc_dialect_reset_warning 335544802L +#define isc_dialect_not_changed 335544803L +#define isc_database_create_failed 335544804L +#define isc_inv_dialect_specified 335544805L +#define isc_valid_db_dialects 335544806L +#define isc_sqlwarn 335544807L +#define isc_dtype_renamed 335544808L +#define isc_extern_func_dir_error 335544809L +#define isc_date_range_exceeded 335544810L +#define isc_inv_client_dialect_specified 335544811L +#define isc_valid_client_dialects 335544812L +#define isc_optimizer_between_err 335544813L +#define isc_service_not_supported 335544814L +#define isc_generator_name 335544815L +#define isc_udf_name 335544816L +#define isc_bad_limit_param 335544817L +#define isc_bad_skip_param 335544818L +#define isc_io_32bit_exceeded_err 335544819L +#define isc_invalid_savepoint 335544820L +#define isc_dsql_column_pos_err 335544821L +#define isc_dsql_agg_where_err 335544822L +#define isc_dsql_agg_group_err 335544823L +#define isc_dsql_agg_column_err 335544824L +#define isc_dsql_agg_having_err 335544825L +#define isc_dsql_agg_nested_err 335544826L +#define isc_exec_sql_invalid_arg 335544827L +#define isc_exec_sql_invalid_req 335544828L +#define isc_exec_sql_invalid_var 335544829L +#define isc_exec_sql_max_call_exceeded 335544830L +#define isc_conf_access_denied 335544831L +#define isc_gfix_db_name 335740929L +#define isc_gfix_invalid_sw 335740930L +#define isc_gfix_incmp_sw 335740932L +#define isc_gfix_replay_req 335740933L +#define isc_gfix_pgbuf_req 335740934L +#define isc_gfix_val_req 335740935L +#define isc_gfix_pval_req 335740936L +#define isc_gfix_trn_req 335740937L +#define isc_gfix_full_req 335740940L +#define isc_gfix_usrname_req 335740941L +#define isc_gfix_pass_req 335740942L +#define isc_gfix_subs_name 335740943L +#define isc_gfix_wal_req 335740944L +#define isc_gfix_sec_req 335740945L +#define isc_gfix_nval_req 335740946L +#define isc_gfix_type_shut 335740947L +#define isc_gfix_retry 335740948L +#define isc_gfix_retry_db 335740951L +#define isc_gfix_exceed_max 335740991L +#define isc_gfix_corrupt_pool 335740992L +#define isc_gfix_mem_exhausted 335740993L +#define isc_gfix_bad_pool 335740994L +#define isc_gfix_trn_not_valid 335740995L +#define isc_gfix_unexp_eoi 335741012L +#define isc_gfix_recon_fail 335741018L +#define isc_gfix_trn_unknown 335741036L +#define isc_gfix_mode_req 335741038L +#define isc_gfix_opt_SQL_dialect 335741039L +#define isc_dsql_dbkey_from_non_table 336003074L +#define isc_dsql_transitional_numeric 336003075L +#define isc_dsql_dialect_warning_expr 336003076L +#define isc_sql_db_dialect_dtype_unsupport 336003077L +#define isc_isc_sql_dialect_conflict_num 336003079L +#define isc_dsql_warning_number_ambiguous 336003080L +#define isc_dsql_warning_number_ambiguous1 336003081L +#define isc_dsql_warn_precision_ambiguous 336003082L +#define isc_dsql_warn_precision_ambiguous1 336003083L +#define isc_dsql_warn_precision_ambiguous2 336003084L +#define isc_dsql_ambiguous_field_name 336003085L +#define isc_dsql_udf_return_pos_err 336003086L +#define isc_dsql_invalid_label 336003087L +#define isc_dsql_datatypes_not_comparable 336003088L +#define isc_dyn_role_does_not_exist 336068796L +#define isc_dyn_no_grant_admin_opt 336068797L +#define isc_dyn_user_not_role_member 336068798L +#define isc_dyn_delete_role_failed 336068799L +#define isc_dyn_grant_role_to_user 336068800L +#define isc_dyn_inv_sql_role_name 336068801L +#define isc_dyn_dup_sql_role 336068802L +#define isc_dyn_kywd_spec_for_role 336068803L +#define isc_dyn_roles_not_supported 336068804L +#define isc_dyn_domain_name_exists 336068812L +#define isc_dyn_field_name_exists 336068813L +#define isc_dyn_dependency_exists 336068814L +#define isc_dyn_dtype_invalid 336068815L +#define isc_dyn_char_fld_too_small 336068816L +#define isc_dyn_invalid_dtype_conversion 336068817L +#define isc_dyn_dtype_conv_invalid 336068818L +#define isc_dyn_zero_len_id 336068820L +#define isc_gbak_unknown_switch 336330753L +#define isc_gbak_page_size_missing 336330754L +#define isc_gbak_page_size_toobig 336330755L +#define isc_gbak_redir_ouput_missing 336330756L +#define isc_gbak_switches_conflict 336330757L +#define isc_gbak_unknown_device 336330758L +#define isc_gbak_no_protection 336330759L +#define isc_gbak_page_size_not_allowed 336330760L +#define isc_gbak_multi_source_dest 336330761L +#define isc_gbak_filename_missing 336330762L +#define isc_gbak_dup_inout_names 336330763L +#define isc_gbak_inv_page_size 336330764L +#define isc_gbak_db_specified 336330765L +#define isc_gbak_db_exists 336330766L +#define isc_gbak_unk_device 336330767L +#define isc_gbak_blob_info_failed 336330772L +#define isc_gbak_unk_blob_item 336330773L +#define isc_gbak_get_seg_failed 336330774L +#define isc_gbak_close_blob_failed 336330775L +#define isc_gbak_open_blob_failed 336330776L +#define isc_gbak_put_blr_gen_id_failed 336330777L +#define isc_gbak_unk_type 336330778L +#define isc_gbak_comp_req_failed 336330779L +#define isc_gbak_start_req_failed 336330780L +#define isc_gbak_rec_failed 336330781L +#define isc_gbak_rel_req_failed 336330782L +#define isc_gbak_db_info_failed 336330783L +#define isc_gbak_no_db_desc 336330784L +#define isc_gbak_db_create_failed 336330785L +#define isc_gbak_decomp_len_error 336330786L +#define isc_gbak_tbl_missing 336330787L +#define isc_gbak_blob_col_missing 336330788L +#define isc_gbak_create_blob_failed 336330789L +#define isc_gbak_put_seg_failed 336330790L +#define isc_gbak_rec_len_exp 336330791L +#define isc_gbak_inv_rec_len 336330792L +#define isc_gbak_exp_data_type 336330793L +#define isc_gbak_gen_id_failed 336330794L +#define isc_gbak_unk_rec_type 336330795L +#define isc_gbak_inv_bkup_ver 336330796L +#define isc_gbak_missing_bkup_desc 336330797L +#define isc_gbak_string_trunc 336330798L +#define isc_gbak_cant_rest_record 336330799L +#define isc_gbak_send_failed 336330800L +#define isc_gbak_no_tbl_name 336330801L +#define isc_gbak_unexp_eof 336330802L +#define isc_gbak_db_format_too_old 336330803L +#define isc_gbak_inv_array_dim 336330804L +#define isc_gbak_xdr_len_expected 336330807L +#define isc_gbak_open_bkup_error 336330817L +#define isc_gbak_open_error 336330818L +#define isc_gbak_missing_block_fac 336330934L +#define isc_gbak_inv_block_fac 336330935L +#define isc_gbak_block_fac_specified 336330936L +#define isc_gbak_missing_username 336330940L +#define isc_gbak_missing_password 336330941L +#define isc_gbak_missing_skipped_bytes 336330952L +#define isc_gbak_inv_skipped_bytes 336330953L +#define isc_gbak_err_restore_charset 336330965L +#define isc_gbak_err_restore_collation 336330967L +#define isc_gbak_read_error 336330972L +#define isc_gbak_write_error 336330973L +#define isc_gbak_db_in_use 336330985L +#define isc_gbak_sysmemex 336330990L +#define isc_gbak_restore_role_failed 336331002L +#define isc_gbak_role_op_missing 336331005L +#define isc_gbak_page_buffers_missing 336331010L +#define isc_gbak_page_buffers_wrong_param 336331011L +#define isc_gbak_page_buffers_restore 336331012L +#define isc_gbak_inv_size 336331014L +#define isc_gbak_file_outof_sequence 336331015L +#define isc_gbak_join_file_missing 336331016L +#define isc_gbak_stdin_not_supptd 336331017L +#define isc_gbak_stdout_not_supptd 336331018L +#define isc_gbak_bkup_corrupt 336331019L +#define isc_gbak_unk_db_file_spec 336331020L +#define isc_gbak_hdr_write_failed 336331021L +#define isc_gbak_disk_space_ex 336331022L +#define isc_gbak_size_lt_min 336331023L +#define isc_gbak_svc_name_missing 336331025L +#define isc_gbak_not_ownr 336331026L +#define isc_gbak_mode_req 336331031L +#define isc_gbak_just_data 336331033L +#define isc_gbak_data_only 336331034L +#define isc_gsec_cant_open_db 336723983L +#define isc_gsec_switches_error 336723984L +#define isc_gsec_no_op_spec 336723985L +#define isc_gsec_no_usr_name 336723986L +#define isc_gsec_err_add 336723987L +#define isc_gsec_err_modify 336723988L +#define isc_gsec_err_find_mod 336723989L +#define isc_gsec_err_rec_not_found 336723990L +#define isc_gsec_err_delete 336723991L +#define isc_gsec_err_find_del 336723992L +#define isc_gsec_err_find_disp 336723996L +#define isc_gsec_inv_param 336723997L +#define isc_gsec_op_specified 336723998L +#define isc_gsec_pw_specified 336723999L +#define isc_gsec_uid_specified 336724000L +#define isc_gsec_gid_specified 336724001L +#define isc_gsec_proj_specified 336724002L +#define isc_gsec_org_specified 336724003L +#define isc_gsec_fname_specified 336724004L +#define isc_gsec_mname_specified 336724005L +#define isc_gsec_lname_specified 336724006L +#define isc_gsec_inv_switch 336724008L +#define isc_gsec_amb_switch 336724009L +#define isc_gsec_no_op_specified 336724010L +#define isc_gsec_params_not_allowed 336724011L +#define isc_gsec_incompat_switch 336724012L +#define isc_gsec_inv_username 336724044L +#define isc_gsec_inv_pw_length 336724045L +#define isc_gsec_db_specified 336724046L +#define isc_gsec_db_admin_specified 336724047L +#define isc_gsec_db_admin_pw_specified 336724048L +#define isc_gsec_sql_role_specified 336724049L +#define isc_license_no_file 336789504L +#define isc_license_op_specified 336789523L +#define isc_license_op_missing 336789524L +#define isc_license_inv_switch 336789525L +#define isc_license_inv_switch_combo 336789526L +#define isc_license_inv_op_combo 336789527L +#define isc_license_amb_switch 336789528L +#define isc_license_inv_parameter 336789529L +#define isc_license_param_specified 336789530L +#define isc_license_param_req 336789531L +#define isc_license_syntx_error 336789532L +#define isc_license_dup_id 336789534L +#define isc_license_inv_id_key 336789535L +#define isc_license_err_remove 336789536L +#define isc_license_err_update 336789537L +#define isc_license_err_convert 336789538L +#define isc_license_err_unk 336789539L +#define isc_license_svc_err_add 336789540L +#define isc_license_svc_err_remove 336789541L +#define isc_license_eval_exists 336789563L +#define isc_gstat_unknown_switch 336920577L +#define isc_gstat_retry 336920578L +#define isc_gstat_wrong_ods 336920579L +#define isc_gstat_unexpected_eof 336920580L +#define isc_gstat_open_err 336920605L +#define isc_gstat_read_err 336920606L +#define isc_gstat_sysmemex 336920607L +#define isc_err_max 713 + +#endif /* JRD_GEN_IBERROR_H */ diff --git a/stglibs/ibpp.lib/ibpp.h b/stglibs/ibpp.lib/ibpp.h new file mode 100644 index 00000000..7795c7db --- /dev/null +++ b/stglibs/ibpp.lib/ibpp.h @@ -0,0 +1,929 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: ibpp.h,v 1.3 2007/10/28 11:17:44 nobunaga Exp $ +// Subject : IBPP public header file. This is _the_ only file you include in +// your application files when developing with IBPP. +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +// Contributor(s): +// +// Olivier Mascia, main coding +// Matt Hortman, initial linux port +// Mark Jordan, design contributions +// Maxim Abrashkin, enhancement patches +// Torsten Martinsen, enhancement patches +// Michael Hieke, darwin (OS X) port, enhancement patches +// Val Samko, enhancement patches and debugging +// Mike Nordell, invaluable C++ advices +// Claudio Valderrama, help with not-so-well documented IB/FB features +// Many others, excellent suggestions, bug finding, and support +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// Tabulations should be set every four characters when editing this file. +// +// When compiling a project using IBPP, the following defines should be made +// on the command-line (or in makefiles) according to the OS platform and +// compiler used. +// +// Select the platform: IBPP_WINDOWS | IBPP_LINUX | IBPP_DARWIN +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __IBPP_H__ +#define __IBPP_H__ + +#if !defined(IBPP_WINDOWS) && !defined(IBPP_LINUX) && !defined(IBPP_DARWIN) +#error Please define IBPP_WINDOWS/IBPP_LINUX/IBPP_DARWIN before compiling ! +#endif + +#if !defined(__BCPLUSPLUS__) && !defined(__GNUC__) && !defined(_MSC_VER) && !defined(__DMC__) +#error Your compiler is not recognized. +#endif + +#if defined(IBPP_LINUX) || defined(IBPP_DARWIN) +#define IBPP_UNIX // IBPP_UNIX stands as a common denominator to *NIX flavours +#endif + +// IBPP is written for 32 bits systems or higher. +// The standard type 'int' is assumed to be at least 32 bits. +// And the standard type 'short' is assumed to be exactly 16 bits. +// Everywhere possible, where the exact size of an integer does not matter, +// the standard type 'int' is used. And where an exact integer size is required +// the standard exact precision types definitions of C 99 standard are used. + +#if defined(_MSC_VER) || defined(__DMC__) || defined(__BCPLUSPLUS__) +// C99 §7.18.1.1 Exact-width integer types (only those used by IBPP) +#if defined(_MSC_VER) && (_MSC_VER < 1300) // MSVC 6 should be < 1300 + typedef short int16_t; + typedef int int32_t; + typedef unsigned int uint32_t; +#else + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; +#endif + typedef __int64 int64_t; +#else + #include // C99 (§7.18) integer types definitions +#endif + +#if !defined(_) +#define _(s) s +#endif + +#include +#include +#include + +namespace IBPP +{ + // Typically you use this constant in a call IBPP::CheckVersion as in: + // if (! IBPP::CheckVersion(IBPP::Version)) { throw .... ; } + const uint32_t Version = (2<<24) + (5<<16) + (3<<8) + 0; // Version == 2.5.3.0 + + // Dates range checking + const int MinDate = -693594; // 1 JAN 0001 + const int MaxDate = 2958464; // 31 DEC 9999 + + // Transaction Access Modes + enum TAM {amWrite, amRead}; + + // Transaction Isolation Levels + enum TIL {ilConcurrency, ilReadDirty, ilReadCommitted, ilConsistency}; + + // Transaction Lock Resolution + enum TLR {lrWait, lrNoWait}; + + // Transaction Table Reservation + enum TTR {trSharedWrite, trSharedRead, trProtectedWrite, trProtectedRead}; + + // Prepared Statement Types + enum STT {stUnknown, stUnsupported, + stSelect, stInsert, stUpdate, stDelete, stDDL, stExecProcedure, + stSelectUpdate, stSetGenerator, stSavePoint}; + + // SQL Data Types + enum SDT {sdArray, sdBlob, sdDate, sdTime, sdTimestamp, sdString, + sdSmallint, sdInteger, sdLargeint, sdFloat, sdDouble}; + + // Array Data Types + enum ADT {adDate, adTime, adTimestamp, adString, + adBool, adInt16, adInt32, adInt64, adFloat, adDouble}; + + // Database::Shutdown Modes + enum DSM {dsForce, dsDenyTrans, dsDenyAttach}; + + // Service::StartBackup && Service::StartRestore Flags + enum BRF { + brVerbose = 0x1, + // Backup flags + brIgnoreChecksums = 0x100, brIgnoreLimbo = 0x200, + brMetadataOnly = 0x400, brNoGarbageCollect = 0x800, + brNonTransportable = 0x1000, brConvertExtTables = 0x2000, + // Restore flags + brReplace = 0x10000, brDeactivateIdx = 0x20000, + brNoShadow = 0x40000, brNoValidity = 0x80000, + brPerTableCommit = 0x100000, brUseAllSpace = 0x200000 + }; + + // Service::Repair Flags + enum RPF + { + // Mandatory and mutually exclusives + rpMendRecords = 0x1, rpValidatePages = 0x2, rpValidateFull = 0x4, + // Options + rpReadOnly = 0x100, rpIgnoreChecksums = 0x200, rpKillShadows = 0x400 + }; + + // TransactionFactory Flags + enum TFF {tfIgnoreLimbo = 0x1, tfAutoCommit = 0x2, tfNoAutoUndo = 0x4}; + + /* IBPP never return any error codes. It throws exceptions. + * On database engine reported errors, an IBPP::SQLException is thrown. + * In all other cases, IBPP throws IBPP::LogicException. + * Also note that the runtime and the language might also throw exceptions + * while executing some IBPP methods. A failing new operator will throw + * std::bad_alloc, IBPP does nothing to alter the standard behaviour. + * + * std::exception + * | + * IBPP::Exception + * / \ + * IBPP::LogicException IBPP::SQLException + * | + * IBPP::WrongType + */ + + class Exception : public std::exception + { + public: + virtual const char* Origin() const throw() = 0; + virtual const char* ErrorMessage() const throw() = 0; // Deprecated, use what() + virtual const char* what() const throw() = 0; + virtual ~Exception() throw(); + }; + + class LogicException : public Exception + { + public: + virtual ~LogicException() throw(); + }; + + class SQLException : public Exception + { + public: + virtual int SqlCode() const throw() = 0; + virtual int EngineCode() const throw() = 0; + + virtual ~SQLException() throw(); + }; + + class WrongType : public LogicException + { + public: + virtual ~WrongType() throw(); + }; + + /* Classes Date, Time, Timestamp and DBKey are 'helper' classes. They help + * in retrieving or setting some special SQL types. Dates, times and dbkeys + * are often read and written as strings in SQL scripts. When programming + * with IBPP, we handle those data with these specific classes, which + * enhance their usefullness and free us of format problems (M/D/Y, D/M/Y, + * Y-M-D ?, and so on...). */ + + /* Class Date represent purely a Date (no time part specified). It is + * usefull in interactions with the SQL DATE type of Interbase. You can add + * or substract a number from a Date, that will modify it to represent the + * correct date, X days later or sooner. All the Y2K details taken into + * account. + * The full range goes from integer values IBPP::MinDate to IBPP::MaxDate + * which means from 01 Jan 0001 to 31 Dec 9999. ( Which is inherently + * incorrect as this assumes Gregorian calendar. ) */ + + class Timestamp; // Cross-reference between Timestamp, Date and Time + + class Date + { + protected: + int mDate; // The date : 1 == 1 Jan 1900 + + public: + void Clear() { mDate = MinDate - 1; }; + void Today(); + void SetDate(int year, int month, int day); + void SetDate(int dt); + void GetDate(int& year, int& month, int& day) const; + int GetDate() const { return mDate; } + int Year() const; + int Month() const; + int Day() const; + void Add(int days); + void StartOfMonth(); + void EndOfMonth(); + + Date() { Clear(); }; + Date(int dt) { SetDate(dt); } + Date(int year, int month, int day); + Date(const Date&); // Copy Constructor + Date& operator=(const Timestamp&); // Timestamp Assignment operator + Date& operator=(const Date&); // Date Assignment operator + + bool operator==(const Date& rv) const { return mDate == rv.GetDate(); } + bool operator!=(const Date& rv) const { return mDate != rv.GetDate(); } + bool operator<(const Date& rv) const { return mDate < rv.GetDate(); } + bool operator>(const Date& rv) const { return mDate > rv.GetDate(); } + + virtual ~Date() { }; + }; + + /* Class Time represent purely a Time. It is usefull in interactions + * with the SQL TIME type of Interbase. */ + + class Time + { + protected: + int mTime; // The time, in ten-thousandths of seconds since midnight + + public: + void Clear() { mTime = 0; } + void Now(); + void SetTime(int hour, int minute, int second, int tenthousandths = 0); + void SetTime(int tm); + void GetTime(int& hour, int& minute, int& second) const; + void GetTime(int& hour, int& minute, int& second, int& tenthousandths) const; + int GetTime() const { return mTime; } + int Hours() const; + int Minutes() const; + int Seconds() const; + int SubSeconds() const; // Actually tenthousandths of seconds + Time() { Clear(); } + Time(int tm) { SetTime(tm); } + Time(int hour, int minute, int second, int tenthousandths = 0); + Time(const Time&); // Copy Constructor + Time& operator=(const Timestamp&); // Timestamp Assignment operator + Time& operator=(const Time&); // Time Assignment operator + + bool operator==(const Time& rv) const { return mTime == rv.GetTime(); } + bool operator!=(const Time& rv) const { return mTime != rv.GetTime(); } + bool operator<(const Time& rv) const { return mTime < rv.GetTime(); } + bool operator>(const Time& rv) const { return mTime > rv.GetTime(); } + + virtual ~Time() { }; + }; + + /* Class Timestamp represent a date AND a time. It is usefull in + * interactions with the SQL TIMESTAMP type of Interbase. This class + * inherits from Date and Time and completely inline implements its small + * specific details. */ + + class Timestamp : public Date, public Time + { + public: + void Clear() { Date::Clear(); Time::Clear(); } + void Today() { Date::Today(); Time::Clear(); } + void Now() { Date::Today(); Time::Now(); } + + Timestamp() { Clear(); } + + Timestamp(int y, int m, int d) + { Date::SetDate(y, m, d); Time::Clear(); } + + Timestamp(int y, int mo, int d, int h, int mi, int s, int t = 0) + { Date::SetDate(y, mo, d); Time::SetTime(h, mi, s, t); } + + Timestamp(const Timestamp& rv) + : Date(rv.mDate), Time(rv.mTime) {} // Copy Constructor + + Timestamp(const Date& rv) + { mDate = rv.GetDate(); mTime = 0; } + + Timestamp(const Time& rv) + { mDate = 0; mTime = rv.GetTime(); } + + Timestamp& operator=(const Timestamp& rv) // Timestamp Assignment operator + { mDate = rv.mDate; mTime = rv.mTime; return *this; } + + Timestamp& operator=(const Date& rv) // Date Assignment operator + { mDate = rv.GetDate(); return *this; } + + Timestamp& operator=(const Time& rv) // Time Assignment operator + { mTime = rv.GetTime(); return *this; } + + bool operator==(const Timestamp& rv) const + { return (mDate == rv.GetDate()) && (mTime == rv.GetTime()); } + + bool operator!=(const Timestamp& rv) const + { return (mDate != rv.GetDate()) || (mTime != rv.GetTime()); } + + bool operator<(const Timestamp& rv) const + { return (mDate < rv.GetDate()) || + (mDate == rv.GetDate() && mTime < rv.GetTime()); } + + bool operator>(const Timestamp& rv) const + { return (mDate > rv.GetDate()) || + (mDate == rv.GetDate() && mTime > rv.GetTime()); } + + ~Timestamp() { } + }; + + /* Class DBKey can store a DBKEY, that special value which the hidden + * RDB$DBKEY can give you from a select statement. A DBKey is nothing + * specific to IBPP. It's a feature of the Firebird database engine. See its + * documentation for more information. */ + + class DBKey + { + private: + std::string mDBKey; // Stores the binary DBKey + mutable std::string mString;// String (temporary) representation of it + + public: + void Clear(); + int Size() const { return (int)mDBKey.size(); } + void SetKey(const void*, int size); + void GetKey(void*, int size) const; + const char* AsString() const; + + DBKey& operator=(const DBKey&); // Assignment operator + DBKey(const DBKey&); // Copy Constructor + DBKey() { } + ~DBKey() { } + }; + + /* Class User wraps all the information about a user that the engine can manage. */ + + class User + { + public: + std::string username; + std::string password; + std::string firstname; + std::string middlename; + std::string lastname; + uint32_t userid; // Only relevant on unixes + uint32_t groupid; // Only relevant on unixes + + private: + void copyfrom(const User& r); + + public: + void clear(); + User& operator=(const User& r) { copyfrom(r); return *this; } + User(const User& r) { copyfrom(r); } + User() : userid(0), groupid(0) { } + ~User() { }; + }; + + // Interface Wrapper + template + class Ptr + { + private: + T* mObject; + + public: + void clear() + { + if (mObject != 0) { mObject->Release(); mObject = 0; } + } + + T* intf() const { return mObject; } + T* operator->() const { return mObject; } + + bool operator==(const T* p) const { return mObject == p; } + bool operator==(const Ptr& r) const { return mObject == r.mObject; } + bool operator!=(const T* p) const { return mObject != p; } + bool operator!=(const Ptr& r) const { return mObject != r.mObject; } + + Ptr& operator=(T* p) + { + // AddRef _before_ Release gives correct behaviour on self-assigns + T* tmp = (p == 0 ? 0 : p->AddRef()); // Take care of 0 + if (mObject != 0) mObject->Release(); + mObject = tmp; return *this; + } + + Ptr& operator=(const Ptr& r) + { + // AddRef _before_ Release gives correct behaviour on self-assigns + T* tmp = (r.intf() == 0 ? 0 : r->AddRef());// Take care of 0 + if (mObject != 0) mObject->Release(); + mObject = tmp; return *this; + } + + Ptr(T* p) : mObject(p == 0 ? 0 : p->AddRef()) { } + Ptr(const Ptr& r) : mObject(r.intf() == 0 ? 0 : r->AddRef()) { } + + Ptr() : mObject(0) { } + ~Ptr() { clear(); } + }; + + // --- Interface Classes --- // + + /* Interfaces IBlob, IArray, IService, IDatabase, ITransaction and + * IStatement are at the core of IBPP. Though it is possible to program your + * applications by using theses interfaces directly (as was the case with + * IBPP 1.x), you should refrain from using them and prefer the new IBPP + * Objects Blob, Array, ... (without the I in front). Those new objects are + * typedef'd right after each interface class definition as you can read + * below. If you program using the Blob (instead of the IBlob interface + * itself), you'll never have to care about AddRef/Release and you'll never + * have to care about deleting your objects. */ + + class IBlob; typedef Ptr Blob; + class IArray; typedef Ptr Array; + class IService; typedef Ptr Service; + class IDatabase; typedef Ptr Database; + class ITransaction; typedef Ptr Transaction; + class IStatement; typedef Ptr Statement; + class IEvents; typedef Ptr Events; + class IRow; typedef Ptr Row; + + /* IBlob is the interface to the blob capabilities of IBPP. Blob is the + * object class you actually use in your programming. In Firebird, at the + * row level, a blob is merely a handle to a blob, stored elsewhere in the + * database. Blob allows you to retrieve such a handle and then read from or + * write to the blob, much in the same manner than you would do with a file. */ + + class IBlob + { + public: + virtual void Create() = 0; + virtual void Open() = 0; + virtual void Close() = 0; + virtual void Cancel() = 0; + virtual int Read(void*, int size) = 0; + virtual void Write(const void*, int size) = 0; + virtual void Info(int* Size, int* Largest, int* Segments) = 0; + + virtual void Save(const std::string& data) = 0; + virtual void Load(std::string& data) = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IBlob* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IBlob() { }; + }; + + /* IArray is the interface to the array capabilities of IBPP. Array is the + * object class you actually use in your programming. With an Array object, you + * can create, read and write Interbase Arrays, as a whole or in slices. */ + + class IArray + { + public: + virtual void Describe(const std::string& table, const std::string& column) = 0; + virtual void ReadTo(ADT, void* buffer, int elemcount) = 0; + virtual void WriteFrom(ADT, const void* buffer, int elemcount) = 0; + virtual SDT ElementType() = 0; + virtual int ElementSize() = 0; + virtual int ElementScale() = 0; + virtual int Dimensions() = 0; + virtual void Bounds(int dim, int* low, int* high) = 0; + virtual void SetBounds(int dim, int low, int high) = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IArray* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IArray() { }; + }; + + /* IService is the interface to the service capabilities of IBPP. Service is + * the object class you actually use in your programming. With a Service + * object, you can do some maintenance work of databases and servers + * (backup, restore, create/update users, ...) */ + + class IService + { + public: + virtual void Connect() = 0; + virtual bool Connected() = 0; + virtual void Disconnect() = 0; + + virtual void GetVersion(std::string& version) = 0; + + virtual void AddUser(const User&) = 0; + virtual void GetUser(User&) = 0; + virtual void GetUsers(std::vector&) = 0; + virtual void ModifyUser(const User&) = 0; + virtual void RemoveUser(const std::string& username) = 0; + + virtual void SetPageBuffers(const std::string& dbfile, int buffers) = 0; + virtual void SetSweepInterval(const std::string& dbfile, int sweep) = 0; + virtual void SetSyncWrite(const std::string& dbfile, bool) = 0; + virtual void SetReadOnly(const std::string& dbfile, bool) = 0; + virtual void SetReserveSpace(const std::string& dbfile, bool) = 0; + + virtual void Shutdown(const std::string& dbfile, DSM mode, int sectimeout) = 0; + virtual void Restart(const std::string& dbfile) = 0; + virtual void Sweep(const std::string& dbfile) = 0; + virtual void Repair(const std::string& dbfile, RPF flags) = 0; + + virtual void StartBackup(const std::string& dbfile, + const std::string& bkfile, BRF flags = BRF(0)) = 0; + virtual void StartRestore(const std::string& bkfile, const std::string& dbfile, + int pagesize = 0, BRF flags = BRF(0)) = 0; + + virtual const char* WaitMsg() = 0; // With reporting (does not block) + virtual void Wait() = 0; // Without reporting (does block) + + virtual IService* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IService() { }; + }; + + /* IDatabase is the interface to the database connections in IBPP. Database + * is the object class you actually use in your programming. With a Database + * object, you can create/drop/connect databases. */ + + class EventInterface; // Cross-reference between EventInterface and IDatabase + + class IDatabase + { + public: + virtual const char* ServerName() const = 0; + virtual const char* DatabaseName() const = 0; + virtual const char* Username() const = 0; + virtual const char* UserPassword() const = 0; + virtual const char* RoleName() const = 0; + virtual const char* CharSet() const = 0; + virtual const char* CreateParams() const = 0; + + virtual void Info(int* ODS, int* ODSMinor, int* PageSize, + int* Pages, int* Buffers, int* Sweep, bool* Sync, + bool* Reserve) = 0; + virtual void Statistics(int* Fetches, int* Marks, + int* Reads, int* Writes) = 0; + virtual void Counts(int* Insert, int* Update, int* Delete, + int* ReadIdx, int* ReadSeq) = 0; + virtual void Users(std::vector& users) = 0; + virtual int Dialect() = 0; + + virtual void Create(int dialect) = 0; + virtual void Connect() = 0; + virtual bool Connected() = 0; + virtual void Inactivate() = 0; + virtual void Disconnect() = 0; + virtual void Drop() = 0; + + virtual IDatabase* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IDatabase() { }; + }; + + /* ITransaction is the interface to the transaction connections in IBPP. + * Transaction is the object class you actually use in your programming. A + * Transaction object can be associated with more than one Database, + * allowing for distributed transactions spanning multiple databases, + * possibly located on different servers. IBPP is one among the few + * programming interfaces to Firebird that allows you to support distributed + * transactions. */ + + class ITransaction + { + public: + virtual void AttachDatabase(Database db, TAM am = amWrite, + TIL il = ilConcurrency, TLR lr = lrWait, TFF flags = TFF(0)) = 0; + virtual void DetachDatabase(Database db) = 0; + virtual void AddReservation(Database db, + const std::string& table, TTR tr) = 0; + + virtual void Start() = 0; + virtual bool Started() = 0; + virtual void Commit() = 0; + virtual void Rollback() = 0; + virtual void CommitRetain() = 0; + virtual void RollbackRetain() = 0; + + virtual ITransaction* AddRef() = 0; + virtual void Release() = 0; + + virtual ~ITransaction() { }; + }; + + /* + * Class Row can hold all the values of a row (from a SELECT for instance). + */ + + class IRow + { + public: + virtual void SetNull(int) = 0; + virtual void Set(int, bool) = 0; + virtual void Set(int, const void*, int) = 0; // byte buffers + virtual void Set(int, const char*) = 0; // c-string + virtual void Set(int, const std::string&) = 0; + virtual void Set(int, int16_t) = 0; + virtual void Set(int, int32_t) = 0; + virtual void Set(int, int64_t) = 0; + virtual void Set(int, float) = 0; + virtual void Set(int, double) = 0; + virtual void Set(int, const Timestamp&) = 0; + virtual void Set(int, const Date&) = 0; + virtual void Set(int, const Time&) = 0; + virtual void Set(int, const DBKey&) = 0; + virtual void Set(int, const Blob&) = 0; + virtual void Set(int, const Array&) = 0; + + virtual bool IsNull(int) = 0; + virtual bool Get(int, bool&) = 0; + virtual bool Get(int, void*, int&) = 0; // byte buffers + virtual bool Get(int, std::string&) = 0; + virtual bool Get(int, int16_t&) = 0; + virtual bool Get(int, int32_t&) = 0; + virtual bool Get(int, int64_t&) = 0; + virtual bool Get(int, float&) = 0; + virtual bool Get(int, double&) = 0; + virtual bool Get(int, Timestamp&) = 0; + virtual bool Get(int, Date&) = 0; + virtual bool Get(int, Time&) = 0; + virtual bool Get(int, DBKey&) = 0; + virtual bool Get(int, Blob&) = 0; + virtual bool Get(int, Array&) = 0; + + virtual bool IsNull(const std::string&) = 0; + virtual bool Get(const std::string&, bool&) = 0; + virtual bool Get(const std::string&, void*, int&) = 0; // byte buffers + virtual bool Get(const std::string&, std::string&) = 0; + virtual bool Get(const std::string&, int16_t&) = 0; + virtual bool Get(const std::string&, int32_t&) = 0; + virtual bool Get(const std::string&, int64_t&) = 0; + virtual bool Get(const std::string&, float&) = 0; + virtual bool Get(const std::string&, double&) = 0; + virtual bool Get(const std::string&, Timestamp&) = 0; + virtual bool Get(const std::string&, Date&) = 0; + virtual bool Get(const std::string&, Time&) = 0; + virtual bool Get(const std::string&, DBKey&) = 0; + virtual bool Get(const std::string&, Blob&) = 0; + virtual bool Get(const std::string&, Array&) = 0; + + virtual int ColumnNum(const std::string&) = 0; + virtual const char* ColumnName(int) = 0; + virtual const char* ColumnAlias(int) = 0; + virtual const char* ColumnTable(int) = 0; + virtual SDT ColumnType(int) = 0; + virtual int ColumnSubtype(int) = 0; + virtual int ColumnSize(int) = 0; + virtual int ColumnScale(int) = 0; + virtual int Columns() = 0; + + virtual bool ColumnUpdated(int) = 0; + virtual bool Updated() = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IRow* Clone() = 0; + virtual IRow* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IRow() {}; + }; + + /* IStatement is the interface to the statements execution in IBPP. + * Statement is the object class you actually use in your programming. A + * Statement object is the work horse of IBPP. All your data manipulation + * statements will be done through it. It is also used to access the result + * set of a query (when the statement is such), one row at a time and in + * strict forward direction. */ + + class IStatement + { + public: + virtual void Prepare(const std::string&) = 0; + virtual void Execute() = 0; + virtual void Execute(const std::string&) = 0; + virtual void ExecuteImmediate(const std::string&) = 0; + virtual void CursorExecute(const std::string& cursor) = 0; + virtual void CursorExecute(const std::string& cursor, const std::string&) = 0; + virtual bool Fetch() = 0; + virtual bool Fetch(Row&) = 0; + virtual int AffectedRows() = 0; + virtual void Close() = 0; + virtual std::string& Sql() = 0; + virtual STT Type() = 0; + + virtual void SetNull(int) = 0; + virtual void Set(int, bool) = 0; + virtual void Set(int, const void*, int) = 0; // byte buffers + virtual void Set(int, const char*) = 0; // c-string + virtual void Set(int, const std::string&) = 0; + virtual void Set(int, int16_t value) = 0; + virtual void Set(int, int32_t value) = 0; + virtual void Set(int, int64_t value) = 0; + virtual void Set(int, float value) = 0; + virtual void Set(int, double value) = 0; + virtual void Set(int, const Timestamp& value) = 0; + virtual void Set(int, const Date& value) = 0; + virtual void Set(int, const Time& value) = 0; + virtual void Set(int, const DBKey& value) = 0; + virtual void Set(int, const Blob& value) = 0; + virtual void Set(int, const Array& value) = 0; + + virtual bool IsNull(int) = 0; + virtual bool Get(int, bool&) = 0; + virtual bool Get(int, void*, int&) = 0; // byte buffers + virtual bool Get(int, std::string&) = 0; + virtual bool Get(int, int16_t&) = 0; + virtual bool Get(int, int32_t&) = 0; + virtual bool Get(int, int64_t&) = 0; + virtual bool Get(int, float&) = 0; + virtual bool Get(int, double&) = 0; + virtual bool Get(int, Timestamp& value) = 0; + virtual bool Get(int, Date& value) = 0; + virtual bool Get(int, Time& value) = 0; + virtual bool Get(int, DBKey& value) = 0; + virtual bool Get(int, Blob& value) = 0; + virtual bool Get(int, Array& value) = 0; + + virtual bool IsNull(const std::string&) = 0; + virtual bool Get(const std::string&, bool&) = 0; + virtual bool Get(const std::string&, void*, int&) = 0; // byte buffers + virtual bool Get(const std::string&, std::string&) = 0; + virtual bool Get(const std::string&, int16_t&) = 0; + virtual bool Get(const std::string&, int32_t&) = 0; + virtual bool Get(const std::string&, int64_t&) = 0; + virtual bool Get(const std::string&, float&) = 0; + virtual bool Get(const std::string&, double&) = 0; + virtual bool Get(const std::string&, Timestamp& value) = 0; + virtual bool Get(const std::string&, Date& value) = 0; + virtual bool Get(const std::string&, Time& value) = 0; + virtual bool Get(const std::string&, DBKey& value) = 0; + virtual bool Get(const std::string&, Blob& value) = 0; + virtual bool Get(const std::string&, Array& value) = 0; + + virtual int ColumnNum(const std::string&) = 0; + virtual const char* ColumnName(int) = 0; + virtual const char* ColumnAlias(int) = 0; + virtual const char* ColumnTable(int) = 0; + virtual SDT ColumnType(int) = 0; + virtual int ColumnSubtype(int) = 0; + virtual int ColumnSize(int) = 0; + virtual int ColumnScale(int) = 0; + virtual int Columns() = 0; + + virtual SDT ParameterType(int) = 0; + virtual int ParameterSubtype(int) = 0; + virtual int ParameterSize(int) = 0; + virtual int ParameterScale(int) = 0; + virtual int Parameters() = 0; + + virtual void Plan(std::string&) = 0; + + virtual Database DatabasePtr() const = 0; + virtual Transaction TransactionPtr() const = 0; + + virtual IStatement* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IStatement() { }; + + // DEPRECATED METHODS (WON'T BE AVAILABLE IN VERSIONS 3.x) + virtual bool Get(int, char*) = 0; // DEPRECATED + virtual bool Get(const std::string&, char*) = 0; // DEPRECATED + virtual bool Get(int, bool*) = 0; // DEPRECATED + virtual bool Get(const std::string&, bool*) = 0; // DEPRECATED + virtual bool Get(int, int16_t*) = 0; // DEPRECATED + virtual bool Get(const std::string&, int16_t*) = 0; // DEPRECATED + virtual bool Get(int, int32_t*) = 0; // DEPRECATED + virtual bool Get(const std::string&, int32_t*) = 0; // DEPRECATED + virtual bool Get(int, int64_t*) = 0; // DEPRECATED + virtual bool Get(const std::string&, int64_t*) = 0; // DEPRECATED + virtual bool Get(int, float*) = 0; // DEPRECATED + virtual bool Get(const std::string&, float*) = 0; // DEPRECATED + virtual bool Get(int, double*) = 0; // DEPRECATED + virtual bool Get(const std::string&, double*) = 0; // DEPRECATED + }; + + class IEvents + { + public: + virtual void Add(const std::string&, EventInterface*) = 0; + virtual void Drop(const std::string&) = 0; + virtual void List(std::vector&) = 0; + virtual void Clear() = 0; // Drop all events + virtual void Dispatch() = 0; // Dispatch events (calls handlers) + + virtual Database DatabasePtr() const = 0; + + virtual IEvents* AddRef() = 0; + virtual void Release() = 0; + + virtual ~IEvents() { }; + }; + + /* Class EventInterface is merely a pure interface. + * It is _not_ implemented by IBPP. It is only a base class definition from + * which your own event interface classes have to derive from. + * Please read the reference guide at http://www.ibpp.org for more info. */ + + class EventInterface + { + public: + virtual void ibppEventHandler(Events, const std::string&, int) = 0; + virtual ~EventInterface() { }; + }; + + // --- Factories --- + // These methods are the only way to get one of the above + // Interfaces. They are at the heart of how you program using IBPP. For + // instance, to get access to a database, you'll write code similar to this: + // { + // Database db = DatabaseFactory("server", "databasename", + // "user", "password"); + // db->Connect(); + // ... + // db->Disconnect(); + // } + + Service ServiceFactory(const std::string& ServerName, + const std::string& UserName, const std::string& UserPassword); + + Database DatabaseFactory(const std::string& ServerName, + const std::string& DatabaseName, const std::string& UserName, + const std::string& UserPassword, const std::string& RoleName, + const std::string& CharSet, const std::string& CreateParams); + + inline Database DatabaseFactory(const std::string& ServerName, + const std::string& DatabaseName, const std::string& UserName, + const std::string& UserPassword) + { return DatabaseFactory(ServerName, DatabaseName, UserName, UserPassword, "", "", ""); } + + Transaction TransactionFactory(Database db, TAM am = amWrite, + TIL il = ilConcurrency, TLR lr = lrWait, TFF flags = TFF(0)); + + Statement StatementFactory(Database db, Transaction tr, + const std::string& sql); + + inline Statement StatementFactory(Database db, Transaction tr) + { return StatementFactory(db, tr, ""); } + + Blob BlobFactory(Database db, Transaction tr); + + Array ArrayFactory(Database db, Transaction tr); + + Events EventsFactory(Database db); + + /* IBPP uses a self initialization system. Each time an object that may + * require the usage of the Interbase client C-API library is used, the + * library internal handling details are automatically initialized, if not + * already done. You can kick this initialization at the start of an + * application by calling IBPP::CheckVersion(). This is recommended, because + * IBPP::CheckVersion will assure you that YOUR code has been compiled + * against a compatible version of the library. */ + + bool CheckVersion(uint32_t); + int GDSVersion(); + + /* On Win32 platform, ClientLibSearchPaths() allows to setup + * one or multiple additional paths (separated with a ';') where IBPP + * will look for the client library (before the default implicit search + * locations). This is usefull for applications distributed with a 'private' + * copy of Firebird, when the registry is useless to identify the location + * from where to attempt loading the fbclient.dll / gds32.dll. + * If called, this function must be called *early* by the application, + * before *any* other function or object methods of IBPP. + * Currently, this is a NO-OP on platforms other than Win32. */ + + void ClientLibSearchPaths(const std::string&); + + /* Finally, here are some date and time conversion routines used by IBPP and + * that may be helpful at the application level. They do not depend on + * anything related to Firebird/Interbase. Just a bonus. dtoi and itod + * return false on invalid parameters or out of range conversions. */ + + bool dtoi(int date, int* py, int* pm, int* pd); + bool itod(int* pdate, int year, int month, int day); + void ttoi(int itime, int* phour, int* pminute, int* psecond, int* ptt); + void itot(int* ptime, int hour, int minute, int second = 0, int tenthousandths = 0); + +} + +#endif + +// +// EOF +// diff --git a/stglibs/ibpp.lib/libibpp.so b/stglibs/ibpp.lib/libibpp.so new file mode 100755 index 0000000000000000000000000000000000000000..7169ab54972b05bf2fbda27d1864d5675ee39222 GIT binary patch literal 4113890 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�bg4{&z6;C0zl2hVo%>*$4sx}4 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 S+lh6?h19FRiq53qzWx+2wcW&@Y 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

itYcrv{zKaq*5`rx2Y>&v)wjM6a_NoxZO?( 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 z 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

xcZbQ5D~?sR&3(Q~SZM&&Yw3JZ?-r5KqV1UNEe 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

KnXD?(pgKnk7G@zQJeZ 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

*j(*u77I^iL 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 z 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

Us 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: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

;A;5b*ndU4HwR1!6 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νawg;P9-pLN7^l%CcyHQ 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 z%0A0X*BrQ5&(}3sPD(pk?f|_}&)ETLaM(fKgHb`GO+d7-Up&9=ae0!KkOoxv?slf@ z+D>n6gXjOdJ{x+u8?Y9_itGWnzhUSh-8b$opbYTBf7eegpnUn_e?LGg5Zo1%QC~_9 zh)M8cSH1{#x_VSEC%UTn{q&urI(3?QP_HDKp|EzlsfA1H;jB!Qg1=0GB(ISrNcaQ| z>{J!>l~G@H`V1ymM_N-mqs3CU=?A7+U0Y-0IgsPqRo{PUiqUO#ipoT9Mn!jJ)8q53-qV+&bGzS)it-1QK}|K9U3war zd^_r?LoMj}#5vFVov1SU=y8mHFfwivjCI1OsQj@iDoW(+mv#YN>Xb27M@>J=N$GO7 zmhWkbR!F@Y6_e>|`Fgtadr>c$V3e-cd^?`6AKcty8zK|6oA4U%eb(CQ8Zu>b3K2 z^l(P&>HI2DcAv>~TeNa971FjGt%qVA zp5G*DdNX4}W8(BczD<j(Oz5M!DP)p7t6B%=afybT z5W<@JWOad^$+`rOPxN?3Zk`a!5v+dGjafI~35k;bx>)|HbY~|fT5cG~b;E`g4epw{ zy^|6}8%-*vr@K5k(NkVze~>zwmb@_eS65~`B~jLms0avlw|O9^CUVsiaxwEyql|W1 zqDGG$vsDW9tO`Fp(Nnj5Ql86bR%cI4YnV%$ucexLQldq6u&ogD&ZRQ&j6{ofM4c7- z5VU_mtY`1cL<6xO#LGLAY=2ft57!Vws0^ZMXx$R_jI5Jya-!GW(5k+(aroxXBwM|e_^6jH0N1A-$jX%a-J@hIg!kMaiXIHHE42$TV>=Dt?YhD zqT|i&^wQ7Trk6}f6#e)MQ?m6nWN!f({nA9oR>so%ro8OOzuZNY-!Dt_Sc#hVW;uW3 z@HbTcjTKR3==R=(&Q%&uNKyz1F5i%*azqzYVoEtNf}&BR(2i-OEm* z{)&TaApGh?(VlaGk}|_Ji4t!Dvo$zNK}|5o8e!Kas^P-&2U8DVkTvnIE7S|)xgmYl z&cD7;Pkm|ZSxYZ>cdX9_*+}aRiL!2{xS}4~jfo<*Bb&KT5GCtv^n3@o)oklg4NH1L zYONBxIZ+NX2jZg}WYyQqL?g}~NU11svl106Vg34dOOlOv+>$7;9Mg6q3aPO2=GH_W z?#vwSbkW2oR8*+kmQvNAJs$?kl$@w31KggdnUmnZl#s7mu1@wl)}!k-+zWl6+zoAi zdHr{;N8cyi-I?zvxGT}M2du9?*jtNS@7djn+{RRJd)R{}e&Zsj>XzKLn42h>d(N;u+s;H4 z<;@3EDvW9sI-qMYOrkwdu`di0!~M{ z=BccXDt1{vOi3|CDQH z8C&Bu6_`Pp>BU5Y&ahN_-85v)mlBO6)2ALxiDUYpQkCHko8#Fto+RmqZ_HKbH>C39gcJF1>^75N5 zE1SKaQHmu6%QmdyuxrXZ38{5+$fSt{wOiJNmP^@?g~x6vf+ZmC{I(JeG)Ghmy8Vac*S zr>{(l-SBw-Nl+WEL8=@W#Fv6rztZSGt{$ordo1o0LYuj$o!{ zy&~VH)XYP`0UB(?&LUAkYzPU$MpJKX=%w$ zhxGbg-R^tux$`au9aJ}T=&&J!cltY@WhnX6p}l?|27&guRqlB zyKd*5h7RUi5sEsTsJ-{yWv9Id*X^?RPD6(d9_j?w^{wLsd6Hqf?7~5%rM)VW(+nLv z_-}Q)>^}rSuYq~dgZJ5$Z-DfEwqIU6>bc&F^JCG@m6mLEgipBc({6L;{dZGRFYQ~; zt;VRjkeHG`H}u+6x3uRjb{V^`KQ$)zwcAd+3?AlhYxAa(t($voQg`4k!}N(1>2ff$ z#%Uh%v4`%u?y_9TW|V=+w)*?vzw<$l%~5aYVY^M55J6t% z6w16tuC(_S{!A@h>BNnP_wUs;Jz&Y}-V)y!!uAuFdFHRIXQdhaJkCZqdsk<+A+cl*dAL z-^D#Go>gzY%)#J0Lyx)Me365n><*mk&38D)HMG{Zjn4Jv%Npa@o4FGs^A(M8jq0b$ z_2!EiP#lA^-aYtz@Z;X#f-mUxlj!~K6^pfcNO52F`#ZVHxb9>2F zf9&;JZfVz@hU{)%qbaM~X`i8c#+bUYT<^_%j;&+h-!`k~OE3Jg&glMQw`xu2`%FBU zoB!#<>pwP6$HnD(l_O_%Pm!~PSPCh08rwUL?%%XkSKeu2G@pR6)WwL@tC&8V4*=91 zuv6XM7>GOT)gJ!gcW3xrx9i~D_S|RiuEh1kxM60T@zDfz9A4UMoA|_caV8|om-gCI z#qyA0d+xhW|M8onR?@#CTegSFfQ)ri>gn#e@85UYXV3j~j?&zf`<3MG7+R8B053!M zYp*NG9rN#!+!=5Ugue&){iDlEa%HeBgum0K6px?8`3K)nlG_a?cxDrdwVri^V$&N- za+|@QVQUx!I$r4#KR?Lv*F#EjJHl@dB^~e|$PF*ay#z1Afu#K)P`a-LrBiHug=4d+ zTn7fhws1aN0Bb#OPAkd11&gLTHoUSVw-FpXqa=3-bX?^)`RbCKSPV;G(`z^%X+8)x zSVeoZ?F!*{xkX@vEqGuLi_lH85+N^&?j{ex zLENj*eG1*zGSGbprTd5Qt>?XGOLC%QF?NGGI0DXwbD(yqWAbw)IdREy$BgGoa^fA& zRx3(!TX7G2fqvib+3$stoH+1B(gk%daek0p*7tlHImgCtl4qb6!rx0Dm*lQrTavp6 z7D4!Xo8S9?QIczde?#~?pWp3Y69*ST_?yY^8@}Z|gC!9D-sShly-IW6z^{6jhUnaG z-KDulVG*o`@V7VVJm43l#^Hpb5t`s~&vHVs@h=^}CKP{y{;-#4459MG|CJxw&^gaD zn^3Iu^!-(7PHg2FLMX<2P9QuH&I6@eb``@ua5R5Zk{baJV`Cxv3fWW0enR#Vvd^z! zAXLIxFd4c^N^{+C7v<;OFr`mv?oxQAFKLDO{YrBSpx<{&a~r_B@E&aQz0%wO`0@8k zb3cItH*{&emGmCO?;C&M$Zd@Mq4Ot>H-1){6M7EPeF_%BO3&AX>tLf_J2ofW0#5W? zK{yq@^8EBSJO}9TJWeQnzlmc%Lh-C;*l$a7;zZ9~gyQFAj*T|ux-i)@mr#7-8MYb6 z!(P90JV_|ldA9jIwt>l>lmAef6C3=|QA;RBd!8f|^Zw-6wSQ?&bbGEL6dP>r_?A#? zxQ2){beF_dr^ zOoT}=%`=No{OTaeTll@Fh44tl9qc%ia5zlyEGAq6-#di+g-tyJ2*nh*6lQo<5w2F; zp^g^_UxZ%`cMK&Q1`}ZtO!Lel6u&x*binUDErdrZ?r_JUgu`KqXEEUt_+B09gH1gH z2*nh*6lQo<5w2F;5snuKUxZ)PJBAVtgNZN+rg>%&ieEL54jAq^mQY*=*TZVhhK-aL zPy@9v-ZOkpmJ%+5FFl*IQeMDN&!L3kM$dDE z%b{$H;~#`WpvQ9wp?De=!6&d5zVv)cC^l`Q{)0bxwjvb!!hW#7=MX~iFZef{;5n60 z+yFPiEuK3G#e1H$gktk{%1x;7Y)2@@czOuM^`2V^#T?K3gkt9o$6kcuSeO8(dd?vf z^F2R5ih2dgJo^(K0Ec@<5sHgFYY4?>o-I4E3sib`AQbJM1%%>BPp>Y@bNH_3Cxl|K z=MqA31x$sj;A*%5ZiHLlR=CTvf>2a+JLHdx-FqD43B^)R-?6->(C9gjP<-UsY#h%W zj`hqU6rXyw9gp7w$9fhJitillID}B#;8{l~{(g+(a>DR8mESL&#`^`UU^V>ybjRU@ zlF$8L$LxRNm%t7G!VYlRziCszdB-|VpMb9h6OMD7d^~<3>~o^yZztn-z&W1fgrfH; zj;#pAL7rAZ@#?7#r8E2;LE0vt>$r?iT<>|AP;7jjec{^*lwW^V|ZrLgysM zYiCj~!>qF%yPiY+4a+^h;oPFzqjZG7^|i~+yQnnxgS#BRB^3LC+6Zc+3ANJ{ce-Z* zq4*Zo*AAELJ9lG$&i}K^9Y362niB_Ji7nwOxEj`YUYtQa1)soL*!L=~3*WugvH$g? z3-*}l7&wb}3wF50aXjI7xn_UR@6&H}oO2t`7Vf>BG6CMaqcryx=)R@9qh~4MGI$-{ zfS=ss7)B`0^IT3SzCYXXS3+?U+zbQmb!<<#1GIUj5sDR_4+uYm$~lhB?&JM}ePKVS zhX(i(zJi7KI~F{^d$0*Q9<{F&_o3(4bFnY|AL`2u1hPj*AJ!{hm(G>z2xDYOa`5+%qzTX0VfB!kh_Jlh?z2^c#aj)k?Liw2gZ~V*k`IS1y zkC$U}_=RT^La~!)H$pMab1b1)rzdZ8@1?$+1A%x;e2!E@;psrj;pBd!7N#alC_k>=3a;L%N z5dLOx?4>1rjM;=@87O`gtcGn~rCf&|7z_8p9O(5Legf}@Qc?=bH9W- zI0CMMtKnUE5AJ@$vCEr0Pq+tW!!~bmJ$N71zzuKX-@y0ZaXd%39IjkNn*g?1jXvB9 zb71#(Ne`R=C&B|T7rqAFTffpixeZ_ttRrr(_Z$}yiZ9_SIP`tm9dH&*hQ>9Nb5QdE z?IO4YrbFq6qzl?$EL;jR;Bj~szJP7H)@ac0Lq2lMArw^~m*%QrCd`7bVI6F=)=~B; zKh&E@_0ne$TPv_emRk1M@id8VLS@et!Y- z6*O+7-@P|-3?LN4J*N`NcaYDZbB#h*=)S_AblQHLyg&N?9+(Z6{K)Ys;b-u-jr-*G zhG{Sz9`&pt6kBs1q4R~mxqWlFO*Y8o{s=ok_&a87pWGF24Lk_p@8wB-a(_94`-j6J z{Pn-KPwqmv6lO#C`@^h0xhmKjnr=zrJ3irA!`<)+gufj=>y!I8oD2^_`1}5soCAiz zSrGnKd`(_i*C)3b90uX<>Tmnx-hwr-`G1o5j$VCpv*2#{1j66eC4I?feaUBibGJbF zTc4l*@PU1Edmq#{Hw;dN@VEBRzPU|@_s#td4utUc=x_Su?tG+AK|4TkPkP=Yd<*{k zD9;HRp$S^xNVwdym~aVf@R;K$!cLe5)8T1P@5jk^PzBX65=OyIa5F6Nd_^e!^hBRr zfA|;t8)m{Rc*`^3N%Aio;8{Q@UVW-h?lt%bLi_S#e*bP^pWN@^aL8|ODsBw4!4)tS zR(O8$bf28q$8#*<1b7}+z%Gl(3ve^s2|s_vF@#V|@;po^yN@q(+`4I>oVfgVj)8yd zldFVLe{x*epZW=wZ|?Z{mYf&%-pVoQ&zu|PZtZ9o&?hJMu5fHzP1yy@2RXj~mp(c1 z`gVPCZ@}l{U!2CM$2TVP} zF}Df3!ITjk2jfQZTwv&tyw|XNjN`2KKDo)T`%#qt(5s7l3=elZKI@^pgFlbu{-7B~ zfXb+58Qc&W2-y+omeI(GACc@FS**awb+PFM^};APJmLh-FWlr-&-$td_(vx{P1~4+Y7w+@Z?L5EmmSDIQ2Eht8Y-A zz&3A_Z{f^Uj#~(Cg2*rM$34~&n=TpMZ;4f=5VZ#rw@jTjH5Zc!p5x1G= zFNE8{fp8E^gh{Z#^9tcg`0EWdvyjC+Q0Qu%JbEp_ZkD9!|pX=yx5zi)wCVJR$wJ^DCC6Sl%xFd2IHb^L+w zkFY1~1#>*F5UzxhetmPLFxGQ1;U#b%+z;Dq(AWCBiuHF5KsFSH!8jNXt34Zjhjc?Z zRKT$?0nUQS@X&^Ra}UE(SO(wQxNq+J@GJN=9Qd=oxr1ONjDpL4**AAN+ypnn@=f|$ z-=q2*LqA&>KbCC!-}TMy0BxRWgwx^jKRRwAycyp2Z1ShRIkAUlG@&@g^CqGAL4U{p z5Q)?7=<@x32Jb&2LGm=n@^}I?bzPE+r$AqHEb3Wk(a1~q)Z+d>VWnbo) z^v&()X(SY#o|gzkzpWfUAQVS<&L%turonW0)wA)R`{u-Uo;pI&=6Rk_lx*$z9-$cS zIfL*_xC}0bmpmJ7LwFg~r{SHgDf zZai`Zzu)lm8`w9u0qo%!P1p)IdY&d+1V5~F3?!_CZqJ2;p?~!velPcYO}Gwru5vUJ zj)1#7pAm|!s~y#Z;(tAJ3B^b7G0doOyiO>7Rm=MbzxT8dikm&J5UzxC205M~6yMpl zZ|=LWvFAWSajEAi!i6y5FOCNY#pmz^NI&0oDi7@9Oao#C{}rXxhMApzxC`&C|W$16N)Q6HxP=Io*(ViH}=Ep z_s*ccebxE;JreTAbrjbM>f=}6ei-Ko{oG+ZK{}T~nCGD1^E_`8ieLZDaU`MULTC<@ybo*O6IcuXvv=Rz&*3+)3G4(r!|t#LjD}V?3OeC@xB#ZW zrSJgEg@@o_SmoLF?|pNk*>eG*co4$4jDBytk7Ij6(dN0BP^|WBx-a<_hIsx(C>DCY zBotfh=h&Z6To6dK!mNH^WSr1z&i! zAIg2fLeKvW>zn&0ob0)a@NSsv`RV@D-%#fH2jLJn#50p{7R>g%OZXnF_3U{7{u~VR zoJ4psoauR;@C{hw`7M2ZWzgSqG@-_FG=`(!*L!Xy)YzNG+%(5Vb(!MSPpG*h8o$x+ z$)0xz#ij@2PeToad4T$T7EFfo;R2Wfm%=od4%fi7a1-1Nx54djk7o^``0XL2ANqR^ zBNS(PE+7<7c-9e$(nB4C2*r_}PC{{wXE~u*3F{m83iFSoQ#YLahizmd{T}OCOt=JI z^t?qV)_DGUSl^sDz*BNKeiR(xnN28uQs-zR6pwl~I|4hyKRtI4-U&~@ld#M)u)c3j zw0a&R6ulc9b%f#~&!>c9>qf_kgyKHX*j##CPI*9Ok7R#pay&{XzVZB|ne@X^o}Z7P z{DBtFY(nw9k&fYn;wI0xgktMaj>8GXC7#C##kUZ~|MYuki{oR$@Yi(|1w-vefn|KMNU+n z{Qkjw>KCYm@V76&zkC$`_c8J=gug-jZg`SB0%t+^yN2ICSj2f?I|zSUze$-0cf+#~ z{?_uWw|$Ox>KAT~QkbWtxREeQvgMAgp6{CzYdu@9Ab-Iq&vZg@qvtNdyJ3ar8^Zs# z-sD?!HhO_;kgo09X@|n0yVWlJ0zEaN$YY%kq|JSzyrZ(pPwhtoY%U*UdWF)V=>J$+YFf5S$ep@hTWaL*54 zCEvr2o|6bqhKD@g5Pl1rzQ+B+!Egw)c+MphQ#|hxiVa`qnlK20|FSK=&+^PA6t8=J z@kZaA*wk|Xp*Y+#icnnanL{XkN&Xe-eEdJFd8X^gGjJe;zjl8AJJ&CF23!N-?*V>a z*SBBpZg>{L-zt88x?#Uu=|=r>n?U%h;&<<#^vnGSwuJDv3%_swdB5BP@Djv7(sTdB ze#RGsqGFPxmGHm42Vvc1nEw~nU+UO#Fdj~UQ{h}V55Dq@JfmN36m&s191A(pBfA^~ zvct8Y`xd%Sq5IkjbRRFn&cAZ}ev^KT3-rr<_*=(0oA%3z!N2R5+YLVcz2mAs_RC!j zL;5@VZqY9%Zr##x)Svt1I$@J-9Lomu%ZcMF99viR%ZU|Lj+1Np% z$Jp)r<;0jB`sLc-(7*P}4TnQ^bo^r{(g9!X?D%LG(g~LgcAUK%_W|4P;V9pe>%fY= z9MAj>`@k`OcO113>4%nm`{j;=`u+Ol8sOl6a6S0P5UvM%4(*rQ3w9cY{h(rh$BF|u zFFbP~=YX(}<4tCsgNWa`{Xd3P~?67{h!(oFuN3SEugK%TL;;pKa{RlqUrzj?yI<~)@Kg`a1A4~sJm5D+^L*j{V;s%@?3Wt>8~(drZX>wk zSk4cJ9moB_`EUU|1qeuR8+*W?%EZ5!``r?Vq@*H_VV4one*h#TLS3rb$$Obm&Z3ZGjryY z=RBuB=Of(5xB(}&4hG_e|2=Yx_?26FQsi2kOgY6(KBaXq=d9Mj6*&F<_fuO3C*iI< zt#z;vcjM`iyWKx<*w_sl6-r)C5T*q@GSI)2866etGvPH=JGPFGl8Xq%7bjqy+>6t6vT!-+SEP4_J9h%VavwOi-J`7oxv|bw@hkTrZW-<^+}pS> zo!j=Y)`8q0=W6+tyTZAr`Tf7Oe^lQd`gr7?<5%v{CnC3oU%8%7whr=eU7w2F2!2Q6 z?!jp<>7Ut0s(0SOy^9<4H1Y1pJ{jEl?$^~jqn#_}S8nI!k?YK_Tp6w$cbRjq^7|TY zyJwM0TnVlecRg+~?hD+PxSZ$6zn=f!+#i}t+^U@G@M7ygZm4r>UM8P$d%Qw9!%cPW z4SwImy@y+g`^vdJU*$R60_QH}_cGjrxMjExajS5Lz81M7`IS2jr@f>9H};L{+c)8s z;GW05fb0KyIQRdD{hW&PH@I(c zi&jMLLw@D9dxP}A72!tV#yfWvzgOdyICtcm$P4ar+!eTYoZI3p+VQwy&W-0+?g{5! z<5#ZV+vF?mMCY#MSMGV-|7suT3;4C^JCV!bSMCU$_F!h(e`)=*|F`VJR9q^Y`xn1* z@8dqeb$&N;d|4zoo+Tnse>mZyjui>)_lD{K_5T z++qC6&2?@*zj9YN_dLIHyM7S4S^OS@J0926{>(YtI~n(9_g(6p51iZl!`6XZjdNG= zt38;#8zL9qf2n&PJJ)3uZLL|E_h0JXZ0DZkxAE#)H)|T_Oa)yrlpp<0w!bD-&*(b2 zq;7K1wR~1Jk43-rwLw?iai_c_K{59@N>k5Af7Ddf2VE%j_WA=nO1|uSc68Dh3VKx zB64{>(x2On`|WUg?xabPE1VPgf%lQEOfy(-akF3tpHveZ-rH(ZJ&SBEOcA(w_Wz#Bl!n=uEn>_0=aod*i$~7m9gg*9@0FJ z8+@Vp5(G*+UDI*q-=`llwt?#y-iiKaZ_IGdDZZUPLFeN-5D&XLxj&BMSN`d_+$}Z; z-J^N#CO_uinfz_f{WxrcKrVMh`g0e=p4)r(Hi2BjA?eRu@19eBcAC>BbRGO-jo)1w zdoGvf51s-j4p*apaF?EwdlhHTtvRPnAXgO=7<=yB*mD!EZxhI6itj3()4%Jx zb9iPl?rZ+GtCRcVGk*2mZjSYw%Aj4kC-<%UwST{W=jKt)?b0*4viQUP7Vq|Mn_%4Y zlAh?Ydz+;^H|ORJgG~=n2}*u$`;_PA{kmmPe!4y7zttt>xiu$m6)Y&XuleVCy5}su zXRO{jxPO6CDZ2EATz~hR`{&@G6FLUF&d{CcvcHCuxg(&G_FUy#0zSYHo9og8tr*GRfI;%E*+dN$(L z;jeUQ#F0cxZ$_+G5GQn_uI51`C(?ltt7Zv8T^I3&Axo!492H>cuZTAef2E@$-X#2$ zE{a$w7lb+|V&w|@4_%-OYk1+WbV$UThriMl5l5OzdLiO1!e5pBh_?)XRmLNZ3cAX2 z#9N2IDz_0+5yh_N!L~6{cvK7fiyiqM_^WxaeF}T|)D5gtrrht8!aJq#&MCZ0qg?6+ z);#`Dj%@RJ>+s*Rf{lW`;Eu2)L)?eoPBB&$uxpG{N!fm;!E-x! z)G?l=pT|%3ycInA6wlJjhtKuA3;g_i&wIg_UgUXS_>PM`cZU-vdfpG-@?6jT;D2A> z`5^ej3q4Ek&OXU=5p0*q-z#4w%ijt(E`M*wSn{_r#*)8*G5M4Jw#)qg&zfZae}v=y z{~BZYANG!+XYfPx!zXcZCm)aV|V9#=YT_V%!(LG{%GA@Bi@M8wzh4_AsOPj)XhMcnmx+ z#z(?O#<(1Ai18HoycpNOOJY0|emusr;df$uBD^NXr@`&mhaX+-g0tbBV>}=372``_ zyClylPujmem4CGIbli|}9j;H^B}o$@`E*X@2w_P-+>_rFt& z<^QfRE``U(_1Gw}&@88Do12OIZe;MO#;jLofQTgi|<4)YS%k=N> zDd`imPEMZ|F;@C)5aUv~DwaNbW$^!jO_I}p85~dlM`JAipNz5muZj8J6aVj6cai0wFpRj3i{LA5Z z{3pj)@vn-p;$I(&e|`r4>r?!1h`q1!IXlMke@={tXYlXq4$0wN1INSrV~pkBuQ675 z8)D%-GR1$jKdKfdw?A?itBSWjcBQ|@V~y>fJ>mHG_l~i?-z~`VGrP33ah zJK_7ywhq*kwEkCCvv2;m8Mgjc7WeOg^9aALtY+g}?;9dAs?EUK!|A#-V zzbF4N$sO8E8qNKk`TkTsEIS3i!tLQJVc92B4^;PmgtZ?)Jwb67svhlU&U0J~YyaYY zJg56?u@CL-?C1PrOK)gXNnLc^?@!Tq@}xFleX&!}Azz*F~%bsgb$MfM=SZ8<#toQ!`YtF*nr|BDXqW)4pZ8Q3NTN6>) z47K=<7K8F zwU5;9FBIR4Em`+|ONJ1xU|nx-#~sBFx8}>y_xFRfCS-5N$H20`ln*Pu7s6VXGOJm{ z%V60GwfMEhBiY&0SeoL~8`fUOsgAFPwZF!~cMGhwJjTz!ve#JQo?ioN|3rb~gBjwG zUGoyh55d~E+1c?Mu4x#$6iSxC)ltjg6dqJO=9= z3`?&!VC@&`&a;YNONLame+n@Sc_OSmjZ+=J1Z&TS$;a=o_7hupcG!gY-^m&T_r2p` zyA+-Uux$I7y!;E6ZK4~wr|^H0^1KGWWJk;LYb$tYUu@j*yuw=_4e^AZkpCyecr*BF zSo_7T{4ImEkD-@)|2Wpo$6=jQV)^kVti6i|xcfaf55oDoBOKoj%TAZ+i}zsd z^R9LG|A1v{_`rD&))*;e1H#Oh5tXW?3390J5rFfKX9;n zei$r!8djdBz_O=n^79ZZJ5YB2efamSTLv0;kpImXGLwz2_Kx?5Wplrq^S=Ss-oxD- zKM!l)i246Bto`|xpIf77HNS7+n*_^-&DGBT3t{cUweqwI)_yR{k8QR_Mv(V|-1iQG z`_ca<8-ktCGDpC&vvL`~3jg{1Y8|}E?*p)1dS?YJJ2cJQ_y2&k&PnpC^dHO+pzOhD z$V2|s!mAjsP=7$*I}euKbqnu{uF+Y$e#us7rQXxJ{r=QLP$P&E#u@GgOMj*P~k#LuNX{}C)Z1E&94 z5;5)Lw(>e2F7NEi$EmRFDD=cXrO&@$oyD=c%)6bpZ`{uR@ zP?MxzhoBIa9R@4Ei(%Of8|}jTD6BQ=y`BH>z;-FV4Er};;!PQ1)%wQmTz>r>mc4=Q zuKs=*)*ft&?@Czv6fAt57?RZ*o7?eA;V*%;{!Ymzz7&>Czcb-5Jz?#AHT|(wry#V? zmgUmF7}lE4T^-*B+okY6(uwl(cI%)of6M@tbK3x{x`5}j9Pgt*cqL^usMDDDE!s1_O;vhFN3wG>Q#3?3q>QF+V;I7So>;?=fFDG!Q^)(ESq);mcI7~to6E3bt!?PoImQUhxbgz3-A;m^^BG!sJov=r7kF!EdR{~Xp@ z>;BHa?t7uH24ge9-MLqwx12VYJT)bB1Ah90_YZq2>2O zu=c04bN+t?U(R@_(nIgJL2>G=70a(_u+HTf;PU@&ShgG6y60EG+H3Ixtn_c*mHa!9 zwN;M0!P=uc+C6^?tUZfYI#$BSuGm}7|1PlhVVHl_;#R&ra5gNPa2GU-zIO+#y%ayW z?|lGkAFzdQV=_wi94tMD!m{0O@_QLPbjy~(Soi%$VcCbNb>UkD%Ub`#>JmEstFFU>(mK}Nv&-<|UYV~mbZI=^-Ht+dNn7^mO+J|raDJ)w8#)(|Y1NzI- zYX)prd;XXU>)fvM_*?n?Dy;K6Zigj59SOM3`IzeXVp!|oNyhO03RvqeP2P7RpgKQl zsgu`YST0Z&r<8&Z_SHZgKzEe$?&JK{^Z|aozZ0V(F3sT$v*mF zbjf4>CYyDmF8@nmoy)^d7~b3XL;DfOwus(e4QqdtJ>Q{M5bnGA*7-jOmQ64FeiWTmZ`UUX_xc>_{2v9&28iW%9c-84a~iCDY&4m|@Z1G=BL9@$^8Z6v_H(H= z!uwn7Pk6CCayzW>mBDt&zY}2DD7XB)1(r=I)M)s8s{`nt9_sZ;E-ZUz`kwq73Cq4e z$rAD;SoUJ?hm~Fz!P>j^ljFx>?fDtz_$^qrA%AtOjMYA^7kaHeIg~?SNQt$rM#mrCcsLck+AGbjDU6jR9N=o4|4nhEW7O%|F-?O;jT{n zZj-|Mz_aia4M{QtUI~qZ-YN2 zy(~Tt!*(hBZ@?>QUn6E=e6t499y}#VFXi`Eu+C}Ph5L%%Kv;VXe}Hv=IxKs|mOf|0 zve!s88a{s$y!0gWlY9OXST-`OeC=v0k*TXuW!sPjMSauM*xc5JUWlOmwtoUq%;OJ~HC6oN00?Q^X)k4T~ zVcBf7^tcw*zCNa||&J8OFB(mfezb-Sc&E;W z0LwO?^smBO4r||_>62No*0`HKcnS{w-X_$iDxaUjI-m2J)=_xd^P=on-R}I`57wCo z7QQ24*^{#HH^4gg^*x?f{$C60ye`#G3eR`2_9R$-Z#kU$h4J_f+*kSR3fB;x4MXk_ zjDTmuN^ga)0+u~WlgA~n_DR|Ie};ALiLzba-|Z0c=W+H~!1{hEye8MD{}Zt63z~nQ zz&eLyA+lU8cW|T5medQGCl+zw2ZSWod5U2 zvMJm*D+=$suw9bZ?_k-7*Y^~kT}IMgrM+$GKOTN^LF?c`cYhA7^EEex72l<>&iria z_*q!{=`1}u6p>%(m-g=dKCtYAT6)yO;XZL5(DxU?T3f5DJ-KuvEIZMrPalP456RN& zMOf$mQ9Q!%Y&(i^@mTxtX!x?zeg4jcW#7fh*R8PjV}9<^=Ob7)H!c3H_^|9HWV`!a zVA3f&KI?vJcal0{$f6^Zx$33NgZ&>!gEPnG~ z+0nD_t%h~(gr)axhofH(^Yz({uxzec`aJ{7KJR?|SNOkxWy3*YDBg#HAp3EaA5&r3 z-85bZ%l6LA?)k4^*$?gJctZj%JLCC|cZYShrM3Sqg|9+iCEWd&VeQMh-SH0Nk@pHe zUo#Y5?B=sJW1)IAtUZ6d-19HNvdcl05~kM{WUOo)b$9m*VA)-=^e=`d(jK?;x*3-J zSquNuuDR^Q$M>l``hAKm{QmYt6s9Cs*2-_>DX)yZo= zSa!o#J3b1QJ-j^k{7rCpW|`yvz_PJx>AA-Q+DE%!;|BTE_a?x0NuExbKzr@2=0R)c z|Mjr;_y5E_$>lUGYysvEtAKqVA+;D(7pdVEE{B&{<9|1 zUVR?BithgNu+FS8|2s~ieNF$`{5uHNK1EB9%V60k8{yvn7Pd?AZ9ziIK6@LNUISp+ zue9&ahIOW%$@`bE>|bv9>bHFNm^FY52p>s(lS%1wS=gmtc1Tlc+frXY_S z5&jlY`c}fSM{4OYAJ$nWh(VYi55Tg2*b-KL?1v@<&xUnwqlNEjSauyyi{bO1 z!KcyRwD!dxu+BX(|M#k54LtRi`9BPnJx1f%u?UH|w!i!@1 z_+wahrOp3sj-o$Ad&l0N4(kj8leY!%!;BZmFSY-cz_PK1+6}|ASuN|;7_aHGg>}Y3C*)cDI4t`zi(USI1M3_Pd%x`r${+oWm9G5cz_L3< z7cP9i1eR@_yWIO{!a76Q>d)U`*W8P>TVcVtEKGziunZA+gsVV%c0jOQh9|A2J{$vN9a z{0S_3ZKe-4`8(l{)vtTP%h0b&cwg<^ez49YHhH}h*4b1WxbJUp661M{ms$LBVA;v; z>%vpYpw-Sa2Ivc>hF<6Gcg=|8G$ss8#D)>)tC{|+aUK36nn&2SX{p|I>*{owfT zu+ICo^nM-g#C+IJ+?Rg;71r4bL_hR@`%@_YulxFDBAoYX^FZsMm0lOZIxoAgdww-+ zm%{ifEZcWmHjn%pcPi=iZ@=E-6jF$I_)vmJMF?feXkd+Gv_yOd=9KL;_d!Bu+Gag{TZA=eNyA=%VDt2 zWNXd)N{`vFUHbk4SoX>X@wdMB7_2j3EdSnxb+)8^ui2SFXpiO*@>|a@fFCEmlimBz zitFkBIC=OE{+as!WAa1K=g*}*a94}q0{475taGugeB1=ZHsqehd#-^8E%xgt9)o4?t{wLkp6_9u^`7rI?`-PZXT82U z7?$np-Q4}FV4Z(z`r_a4gm--ZXv1@8-+v#iAJ_W51gvvCOr9se*@yV?uj^o)Gi~|% zDcp_zg;?SL1@5}HPygI`%pbB|$)2x;Yqs~}P5*#(CP!x%-dEt0Snp=~p~Jb_`Y&Y*~@56Q}y;s3ok7*w4;mX%$=h+J}g^2)v)rT=zQ`U{oKv*aj?#GNH|^$>%2k>?+RG#m^bzf8 z`~D!nfMr|VzW*&O`zn2TU*R2qAnUwoZE;on z#=)}T(bC<&5!N}7&%nCB0@nG&7TpUJypD$pYGilu7Qsi%U zKb~I?>%0)F&##2LG5=)xiR0jCSvT4(lA~O&sT6NqIottUTTX>-_T(+*kM?hHE>} zUUK^2Q&{Jr-tX>jzL4=c%B#uS{;=%en0%fF%ci=8r}uD}_#F$& zUa0jq9)x8dU-4Idtbuiwf_;DMYY5-B?6h|8_k(30e5`x^99VXJOrGzDbq0BkyZ;TW zGlVTYau!iuiSKsq{wP>BZ7jc*!Lr|L`Tqy3b4>cW=l8i5`Jp{#?@xhsE*(QQVg8*B z>-;YJ{td9~rCWU7g>}xMjh{Ea4v*cX{OADd%(`V+k$<^x-eh0?8erL@@5Qr9ug757 z#L9P^bv^k#%df8-4&PDf-=7OFkJYCSz_JNs<*n^vO;z(m4_qY4jcOQOpD;p+j#y2mW_6kzx*ZC2b8bYd{6Rz z46O6Xth`i*GQ-bUZd`1nh$f?u3}e}`pz$iDX&d|7W_UVn#Q zh{ZRpqg{CrD8mj(ZX?*%JN%R(_Sims03@n zI~CUX*QJgxgk@9rJ;%4eqiB!yg_Yi)!IvKH+g}6kVtfUCYUOnVEc>UXPv^lpKc~BU z|6cg&DZV^s-OYH(i;Uk`=CM$W%uVV&)3;kgFZ*>e{D7j>Wfw*1Jx7kdV@7c71M22Y^9(+U5i zKj*`;N!;7{e;q8Fz+GW||8-dQk8S+rcUb2eUg(}5cOUJ)*L?hLg>`P5$@|Z+U6T0? z)o^f^cu#m_Ousb1I=9{O_k4IT<0+PY@4;OtehGmo9_JRoi&zN?7NfD!lsMZ?Mk%?B;mq zWt8Xd{rGi}pR-_{7i8)21-t?E&4)a%{O$HI`%gF<)51R#mL2YeS<(IDV4X)~-#-I>m-^rM z9av{&?dHCp`w04cqEF9bVY@ctkDK9li`bvz%J&8hHqco2J7q;if!osFR*MhT6m{EX1>cm#x7x< zS8VeE|Ack!pvl|&@NKd2&909J;n{Ba&i}#i?Z}JC6J+ zi^loQPhp*XYwIgpK0*60*52<0AJEM2|Eqvy2e#JvcR4J3g-1Bv`AOt&TfhHfG_12q zd%62Z!8$wJE@73+DeD z@VF{pe)oQw{sHs3yEy-jfOYOwOUEa`k2d)6n|ooM9q|bAT-UZ-sTHoypJZug{`14WJ$yK< z^UHd;@0||sRqpfae)zukz5aX?mfilJT>9+zJndKN4@=)Mu+DC^@7KdRXW$F>{ME3| zaIbdXdj?(^t8e}dhvzxF`)yxfzb^ed%kM$3&R5x=2r2#N!Lp;9=bm2;%f_e0x5tZo z4}ETYG^}%yEqn{%CCE<~_x}5^T`Ird!8(uN-~6rbb$SVVM~vqk2TQ-qgTH;SdGKMg z=>EO1&avLx-CqIAM!3o2<}cHpe$|(+(Xh_JOt|MCgmsSGZjQHkh5qj<&hf^7eXj>B z`{M)L{j=boncv&T@pW*pk1sDWcz4k+%>(U|SNQYbP44vTwZ_4NWA)=qSm)afcK%%p zkEMNL;r|wXJIl{kZSxxW$NC>j-(j%qrkXsQ3+ud(L!A8I58KtAKc0u@HS_hyj;|9x z&LFk)c>vbA<9b*3Ux$}zJvZf7?T_zaodsy&*>(l()$>|}>x~ru1ib9n z7QxTFFD}si6McJRK5Uo1u^9elrq?$cy%7Wpk7*Hfa{6KySZ60%ejNkrO!t|*EB~*D zb#ChjSmAvcE~ouD#PJ$fXMtM!HhYuwdXe*d-1~X(BF5J&{%6BFBfYbG|9iOhqt?Mg z?)eShLjD zJ`&!N&ce^GyqpGiyafHr2lW0Yu&iEVc{M-0_{JWg|KEXiCauZOYFKBqnY<1DfcoeB*5UZP z!e0rW9V_oA!8(uH-d_Yy8Sc~bD_G}A+Wp_)U3c*Mt>cI2&*QLcLR{p3A6RGS*!ais z@PQ;=R#tTXN%*B#y}tMnzTcd#MuES*8~Mv*5{$-nqMhaeOSSGwiN+{64Jn(JlRQKc;;)$d`v=SZ8()ch5fppR$|pPq+Gn^g|!n z_wr%8l%I#gIEnM1T#mjr`Mn#~ITDlI_gBLwb@cal`;7YRdmsNn@ZWyK(qZ!`f9AkC^Xo+S{_U{N zAGY$m8eWOLj)B~lzWoi>xlrxg^VzHUUYXY)BVe8D^Aq zPry1O>~_b$!5hWKJ9*-ybO z81EhDp8p!wIquSb`u@(JQ=V>V(OBP)frn=M^;h#@or7rQ>sz?<(3U|9C*S+f&{_DL zZ=W9szk9x4A2Sx#S&^nc7sK;o{lkZ0onK|;=PNxwpk=U$^MCU%Xrx!a85s((g+66^x`D;0?Yh17Su!jjMX;4f*9=ezq~!LK~x>!;q|(jH(w zXtVJ7jedjGP&Lw};&m%02I2J2i9OP_1uvRM0}%^KP} zjBk%|{*}Tyr)gLB{&Dc?hy48b0$Aq-baBr=4?oHLS~tg^!*`*tYaJ*4!+LGTCq_6v z1-|uM-~PEA)>*wK@4vyaH*e|F`+MethIxH+BdqhS%3b*0f{!ot?T;;gpuXOdGp*eH zzOc>`PPp$^!OtDoGFU(XRQ{g_4?n3z@SA&nDXg<=t-O8#>&$q2f9oHqUzoo({Zk0* zoKcJ4QdoBW?fIWzoz-LM(-p;2cx{W|9{2r2;YAm;2yP>M3ePccYvxPr{o7%k;l7It z|ATOiv)`oo`Bm_FwC5~;clnw28~a^M-h07)sDDf!9}0J*|Juv>HyfVIeT&Z{u+GWS z_Z6NsaAz9JmL5ZYq5c1eUvGI2{KCV2JoZP}uJ-)V_E*aH_kO;+H>`7|hPw2g0^i-+ zw^#nq{n-3l=iitQVm#R5KM5Yp_?z*C@EG*{-h4~t?Ou5K6D`rK5x)-4B|R;^-@$e% zd@X+`KQC+%oWtLW?;fzu!?gC%I9TUgO?LNBg|FY!&$s>rH>du!^x6Io^IiU78wb|8 z^>%+6yf^w~ckU^?m%utN-tMn}S1?|-Dfgu>K7%LG|7z);KRCz=&rg}^_+j|SSovH7 z>m1w>?*2#`Iyz%*Fs%4h!geWqb6}n0Yw2?(tTUcX{Su?yH3@?$Y~X zV4dG=;lD)BGv3{ndy3z!@TkKog>Mo(2mLXCzZIUx;YDvW58DfR{wKH->uvgQU+KMh z%dFtmBYb;r5WKRL?+=~|>&##)&tJegck?die?J=fAN<>|w;Br%qv!(ho|XX&N|mS13R5uh_HLjek@6UIp*@g4f^g z!qu_yxlI^o-#Rv4xD%{%XRW*)4!5FyI>Pz)2CVbHEqq(G%L>oCy#fJJ{11V3mae7m zRQTuE{LH1W&h$3+<$;3j|H!8)(q%FA9GW(7}g@7E($z&ex5=0Dzmbxym=lj7T& zh2}a3r@Q09u+EzN30C-K!pqh3RvS28|e>HPyd>Z|6#X0CqNW8I~oB@31N9&G@#(|3G+8@_T2VSNe>D&!xX) z@mmBhqW>%XC;#7pyT;~weumf3AL!}c-wT7(wT!=(IPMSsV~CfBk6@iI_@VQ!3x=X} zhGK8`{2+MXn=L|ncM8wH;1j>}<8AN3cm3+uyR~Lv=);u9t(<>*!#Y>8gX8}2NwNB7 z3aqoBUvuA^4ZqF)VSE2txRCzoiSGV0u+FvL!tn+jvVsM#`}W){cr@cpCZCVMca8G$ z+l+-pLtpmo>u&Jd&`a}RiXEs3qs-;;d#m=2#Dn_qbh z);aH1{x{zyE7R&!&?pZ-y761EBKN2 zvgOAWa4W{QO@BQBPi8#b-&VudG5>7gxfuQkw({3} z2jqkKkV^bge(eem9O=Jb4zFPR+4S43@VAVYS$^!jBlQRJV&5MR>)a~S&sV^g4D{do z1=ji0N4oD-b;=4(p?zWc_$K%{)^l0@wA(2wJg?IHKM4N(rRI(0X&T&lrPrrtz~i%8 zHO|+50^6nZ+HvQsV3*f?dtofxbw$hIc^95p@Rr-Q3@&x~btU{X>rYIczlAIF{QSX& zyHNi!-fQvg3U9by%iwkQei0m?uiwG{P=CVXnUA;pcoxoX)2eYk<_mby{(e8p_+6== zmihH=*TExK`ugc5crN1uCf}RyMt)Mik8$xi20jISVfpd{1&V;uRn10(e``5qd4=L_Z7dP@FmP2T6oTc-=+O`xbuG{ z{51V1yWew9>c3aKypDjIGyY)mbtb%YTl5JPlEU{E{Ag>xeyHPKS-}hRUtV_K-w)Qg zeb&Dj0qdOl8dtxa4(nW0x{0BGABkV}`+Zw9 z{~a7}+J*X+_MoNbO!(*6{)Ka3yQFU)g{%Ad^;BQMI%D4YU%7kx=Ow>~z?Y&=?f$i} z&dvSK<;S=1BE};uJvQ4XEBNktKVQ)YZs^4LkCV@0cqZ*VOYf`URa^S?k59s@(I>a_ zZKeNaU9-ZomQ@~<-+RDk#PV|@Jcst5)wjpMD|ToVkR{>!&%v)SpSUIOYd-N)c+5tA z{l_l*(%zswU+cbC0(Tnc?I)ZAUpCI?&qMJ1=mUi??{po5dA(eUB}{CM5p;Q4p^_SW67 z&iu3Y{|(zEc};YuJwSWL+Jl$Fk5d2H_rHR>t@ieqw(OA=ob#dASL5L9>Hd4C!JVf0 z_U206AMMA-H||OM_nYRy1Sj7Iz&iiZ>aV%*0r&d#f)B%4F@4%HkMa?lkJ=9I5Ni(| z3V%&{nZCaZ);YOWKl}oJhW;~sThWW}-S789oer08+Q2lg7@9k zk7v9FFC#xpo;TkgX1~E~yixzJ7kpi*w?{Au{@_{Pzc>!=n(N1l?|^kqyp@M%;peD7 zZMlMTzb~wFv~%3^rSOxn z{@|JL;=eKfw__BZN8rA(@s?lVk$d~`sqP2S--+4ZX@J+n#@jA{Z;Fjyy$!E!wgEX6 z<<}Rm&gHiB*?(}dydMegO8?#J*BW?F)_+cM@i__pbr(OscON{vk00;ap@8&a{!ZgX z%8%agD#l+-zDB|G*xzOOdn!D4A77sCgP)AeueUvz@st)mee>XdzU%uphr!Rh;mhaM z@E7QFlmG38WCay{{dn(25GQO((SNwB^Wd-M*;`jd^1naD1^ZzRNZS<}D*7I+}!x?|D@MR50AJV?I z_#6gLW&X?FKL@r;ek_H@Ge2SPzYD*>`k`^o|3Bc}X^&WaIph%f6V#vP-(~Rkn|XW0 zcfoyQ<@0^Gb|0^A+ZCc8SWjf*lew^63eS=7)&snLSPCzqK9oGD{5=M5+uZl3b{~-y zeEK8jLvvsHr2xK_^#oRaXTX0OY>{J@)jzWo+B zc!aZ(`JU4MX?W)QK760R=k4Uz$8A3Vl-J>yJM0sy=@I@B~=rYg_)_4u5=Ab8L=B>A4El8S7Sm zCq~mAc!u>(u6=w0+!6h3_2WX=E`{eG@NTjG(i?Dp#{2AhAH%tfPg?t`_o1|pXwO=F zC&N24zq1W~DLqeyClJ3qV8!oNcw5?+R)4(*FQL2{??^@0C#D|^5t;?956p^`tTIE*#zIdxdVQ(z29H@f}VGm z;`=3BeSlw2xXoDFV~6qv=HuxvF<)!xHxZusaLdN| z<5S?>TX}iA4*s0^Xq$g{72cBdq4vG+;Hygf_{7vB=wCeO_m|9vA6VKlc*^xR?uOr+ z;`?K5k7T?k);~zVSB>}nT?aq+t)I_)0=^9WYyJOE;dzC=Ke=16Pfz97{&3A=KOWK_ zKI2Z`K0geu`Z20cmEI@97jNXpd#-|S{Kn6hehM#)t>YjD7MK)d3%^xN-Zcjd)3K zgR^;F@fizWjXpO2&V@f=ywB3-6ZlKg$NHbcr=d^i|8B_ps-J4%-zWL`-xctejHlWA zEvl2{trI+!{RLJZO@ntn+UM`r@S5+uKHp_J>6h#Gzm9h2 zj;4NUAT(Fk6G_?ko(?&@CMWe)*d+$ zZr4y{*XBEqhTmko(DdhWxFh=9!n5zO=xf&ZS^KpNz9iqL-`Vg6 zule<}|A4oQtta>veueRT8?Wd$oAi#YUo91rJ|^$S!sBB4|3l%1Hit< zyT`N&_@~gn+u-|R?f>`ShxhR78FoFH@kPd;PQ*Wz_la=*-&+N@kbnx$;OoZw_V=Uk_sENVufyr+pGw~z90qqh+>hr!5C82MKc4h8{PugkzTDvq^z|Zd zPqQ38g!MHhFOR|R_V)9$-@uP=b@+SqYqq=p18kS_Z|igL zpZMjw`vveC_Fq`}ITW@_&o6=Rx{&j;_-D!c&hx0hs9%*HVSM1_kGE(Xe|ZEx_)yM6z>EIw_nUP%pZU$heEl>9E}iesXSoXQ+|jR(dlO#8_w9SX!wcEJo$vZX+s~(b za-Q$bgk9~G0r03_eEnSl-xv%3T)6y8KfnJV+-D^9vrFGkVY`$bEiOpr9pR3wXR!LU z7XF(0$mH`RxcY0~-n|zdP5PTYpRj=T^7-B#!7=bHjK7(@{R1wieX$weR{k!BXHfrG z`51U1;|r`8dw~0rhhq4E-@Ltti{WG5_WIyixZCHggY9`<>GLIQm*UszBF2v%^7=au ze(nT+K3O?D9(!pP-}B-6J^gsYbMX9&(9Z~r!n4W6l&>TF_}2dLr=_62@{hciBH@8@04cuH(O??U*BZN0vI6|QFf#?t2# z*e;cc=2v6|n+@{wA?L%p#?~9%4R>OG&dS%1@SCyupte_1UykziO#)ud`j&%|{ENwZ`-RBU<9>Zi13YR+-(S23J}lNgdIet2`gAK#tKs+PKU?_ry$X4s z-YVGMmH#7QyCe?{@Y6AUdNO?D>0bXVNqPQJ_-^vU*{{nfl*&@!Nz6Uxg_ zcxbk7FFgXUV0_-j2Y-a^Qh3^4!}#7RpFW-8IkEAtX|P>-el9$p_NCPacfuFN*7Lpu zpLQGTdtLh_Sj76`o?f3H1uvQG^~rMhG}`Ot-w*J=XpfijE#=?F*JcHMu^(jj4}d4m z_Wj2h@Y&3NT6_LkcnrpeTf6l55^l%(0?VKF*HM4G=GQ+Z;Of}?-tq7~%x_wGd<*Wn zIrg~;kILiE@SbfId;0mr@8RCee^~o`tHrdh zo@y0LYaaREAD&75Wb1>c!C$ub-=7O_O?%4X^9=lcti8VqwoCc-1NPG61 z*M0oQi|J3(1r5Ky1pb}xTYj#FpX9tG#YgR@W;gLXiHq?wz2eTfU9oy>!I(1?NWNQSweo@Ye*Y5Q;3>bJwf!yVW9nyHPq8mN=Dy~^B`$o2!9QRxV=vM|->Zf% zqkUxU$A7}FGaq5{^&j~6_kH=>=T_>gZ%}UG`)z_8*e<2-Ao#^zem&pc;FYoZ=3@Bj znEtvSzHEYT|M$6#{_xdZ=^73|M?6#L#Q@MW>~Q~Nv6m;3qo z*Xi)sm_EA*Uc~sm$@3fVMc5Ns$VZj`Kf*zKzy5HCf6{+oyybRxe+qmx<=^zdDew~3 zkC=bU;T*ze@f-Fp+9&t5Xk34C7yJd|$(Fxg!z23p^-tT~Nq?)>>(im|U6lWw-S;QM zt779Br@^nVUm@4sza8F*@hThN-SaN=L#>yeW8hZU=eF{4DSRvayB_ZS@lkq<<=DuzwmmG?7^yZicU_pqLY)Y=;UN88FR9g z6gk;Sik$55-R$t)?C{;3@ZBEayFJ2pdxY=y2;c1yzS|>ww@3JHkMP|d;k!MV1l*Z$pGJu-tEj6h zuB?w#L2-S_qmj;lOerGq3RjMWep99p2fx0 z(`%=dR5b>sps=a$7uENeSy6`SDk#b+E-KOg2~(~LWd2MS^X%f{X^K{{{Ncn(jq^nxVSXGs9P9*@8?8(oS2{Aqq3STuFoI7jzCT?^9zD;_At zHGP({DlMSdR1%?*lo*5_7v*LYl|)XFmB2=c89Ah=uCOlrN+K^gxDw8Sb>+&CN}r*X zCwZ#N&mWi_6Q%eQ8DiQkEFX1YJoS1u z0)e~@L$8-qRZTBb{aMJrPSGb*(D6`hbv?`L%1Wv#>cWB-Mmt_UGWef}$0}A_ax&*(Qf~JgNgJcFG_Iontt&w@ z7WSH&FndN#uFfq^OVUKnVYSn%Cykm_Q=vQ=NKC@aE-5H)I$xFXv=U>t~!%^r3g_YEnWDB35ycYGB4r-Li0WP|!>7pWT*@d>P zoL(0ysoc7X`ia!ysF(u&Y0$L#iB%<&>KZGu|FYIcexmA~x|JF})Y(P#xf3g^%hxH2 z8Du3bd%U#bw`3<*36rm%NHzCU^%K_0NmWdP>L$`MDrhF1PU5@MfT8wZR|b`Zm1|LV z=ZPj^ zrmLunOJ+1gjR2H#*z2J3URR=Js!F5!Jgh|jYFQz)UbS1bo1Y&Q7j9-MFMKOgYrtY2 zR_@6Wk5}EUWoMa}THMmq5aqIPcu{d8S=~h~qMT9vh7s1B;+#3&dOJl2=Pt%FDwmpB}Ms}#4A$$jjcA* zzkj+y&g)OdH)`JaEQ>Y0scZ-xnxe9rlrLYF9WUu z?p|6ksj}LYq#j|*xf=C3g|?gGj$*LJ>0cDQ^itj{EL*AZOHxj~8q1ZbZdBs>wM5rX z&O^~PzcaLG(woSYg{4vElln8$6E;=48}l%c8>?*#>s^J7*7{4GhKWdD#hE&lVMU*x=w4o_HdaM- zSw(R@?L^6Mp6cZ=Nka|Z*m0)PEt`6DNo{$kW!IwzO~o8j&ZYFDQ!^uMyjwjR??$hy zzt`iC+Da=P>CMe@60qF&T~fblwM!yr!1Ssa)2fSRl!kGSIv&*URaN9&a!0v{;ugwT zs%QVhwyJB)r=?P|;!X7ES6^RJHaXVW3bk{*pQ7AL4QOMmySZ3Pu)>W6y0$;Se{dU~LZ5RKC3%m7a8ov6?wu0@5m*s4o{a4;wpkq3GvONDC0> z?gdiAVbi`4O_(XJ*(3Y+WWc5W^yyXkWtkpG^e7rJw6Q{v$_sTjzN;1r>liAMW1>=E z@l6kXTw}TpZ%l7Tf*EhOud9hMWTLMJTrHg+j#arany@Dm%4V!!xmM4#b*L3{$2wRD%11IsIWEui%qVfov!Y)y=leLZ0Rn}!e zH)hbfO-p`Vdi+KexzW4EK4jRsX)4xv#QM;Zsu>hA#HvR@!KlLvQFTLRhGj1Gtwf?I z8r797CXXA0{WCSz(ki!9PppWCgNL)znA*Cs$fVGx|!imfUn*!9=&> z0UFBELl-XtHU-#)C4YL zI*chcO&4WnlAm}IS_i-BjI(BYk-8I=X@!B{B#&V_1!|)T;r;kv-@0_pkW9^~*r19j z)*o4wG zsvxc0rs{q2alM=TnMUNIer=iS)~>y|uew;RddW`M1YoSOu>;U34s|s{GxYkHO({Wt zRWwxAJ5edl?waVDlTi*+2WJv_Q5(zpo?)Npzv}}x7E3n_|EDKJDrxJ46D(ocGR;bBt;*=ldkNZ-hIO$Wtw2EnE(`vlX zGFO^mNIktuNzEuX_4Zd;2$7uE4f5p86vC)cIxGifTuVzVVLJ*iwZsFR^jH0_lV#%A*jW0{@ z#b%P}%vFHZ*36@&%CQR3|59l&SeK@>nh>$5VI6g~qA3m)c6BirZJ#(@C1Pz|pbXm1 z4_~Eai`VbTUGmiI3l}e}w?y@1WMKG=sw(R$hsobKEV9<%H{FA)Y{<{Ju7V-nin8hD z6@JN4`XZ!}LlQX!MFWaQ6%5NSVnU~oR9xRie!9&2L}PN+I-^vek|#IEscD4droVqO z-NOudwqAo+IQd3q<2})2>ZD(WajpJ42nZ~Q| zHMx&#or%cn&{tN#@0)Iy##M3;LQt0&UbZ>HL9NCrVm z>d41SNm>OLP0^&p=W1*c^iDYLU z*-Q#+qF2-vYV5}Qu64=)b0|2hKdGKEt)iCo!c~iu^sMp-eHhme+*Jl?k=X z;$uz3L?W6B8dXwS<>rFYBrQ`qxf-Og|LRs{xb86FNmf#B320O64J4NjhmryGO<@B~w=kN#lU& zGpgPEfg8k6NmZx%n@ZJ`#ec5pX|;M`rt!(Nyie_S{JFxL>Ye4YZ+d5#CP)dks*g;A zl1Vxxs$){6E?w=$8z$CAYn0SjpFAv3u`apmi4K`qI5DkjlN!mC@Hg27VM$wTtxIFJ zqz5{K0#0A-8Wy=^p=9kJ{Q_ItSP&A9xzj%q`dB^DrG=f zZDlEwAhc~VRYZ+#C`xFC5$5opQp;f|@M|4&%`EB}hHi=JUs>UqE&`45h&N}FT43pG zht}TJNlstuji$}7$5*zc2P=6{G_{#b-e>MKC4HN*y@}IVHJ`HNtgw{V$UA&lvt1H?40qjvnp!nl zHn0B_flUytN9rUCLt|P>^*5!cQ^IEhb#Cxm3tq!GHa!HBE|R{a2sR$zD;*aQKXAq36O>eon0bE4^obrt0GkOv>4a1UhYeXhoF zX)0E7W%9z*ZZx1VvWW?$WFcSs*mlxXEzOI}(iUrXBoa2yZL6x$Dw$gT85-4b^^w-i zp_lYmM+lSJb4|8TQ^yF2YGa!wY0=-h)TrxT$7d-JnxeV^iFNKW$vhJs3A3Aqju#Np zFcQ1efmL*8+QPZwNz>R_UsqpJTdy6}2hS`X$tHt3wv`R2qV!gTO2=v2+yZtkNA0(m zmQ5L3VSRAQ*h*1d_cFe~YWhqIv~t7YEB1s(OU~8aoHk7YmDYH(f$j9Ur_^GsMxAd1 z+UUM(poRTi)zRS!m<$UWZE2LfXM1C!sY9zuZGg)KHN!C8pW05I8U^2+uyMoqTKKfI zNmtkLVqg%f78{AYlJ4tQik4MOr>VZ9^aa@8b3BCR;T%`RFTS5L8kU;LJ^Ks zr&{Z%PHNX^uY1#SP5sf+4V`G5LD`;~XNhDT>hx`>sFlzuqt=y-TD#pmvF4{$ENhv8 zO!-W9B5IGsT8xcNk|y$(e_nT!&L@TTk)#Wlm(W;!8yo)r3)@WCDw<51xo*vQCY zKuQZCsyC&$BNGI?&5TLBb%Rc+sMa`qaplB{3)uRR!Q;7IQQwcYIyl^gC3^4*QbD~VeBR!yl#l)v-2{!Qd(zaogU+8pg;|f z^flcn9oO`iQ{-~pJ;bntrjIu_S+l2xOQsd7WU3{3&ES90TuRn$P3rgz?WK6BbL;ey zb0aDkwphn1R_)Wb%{*QgrEW*ckk+wwr{D1P=S6&7`YkpAlqRQ1ZOa}5!Zq*Ch()wD zlKfr2dApbz$yDW7+dg+tZG~I#9ZRcB9pLl?TW47&BPeCjaz|fQZ3$y~3rd69@djyJ zr)3IKd77vB`6%VI-kPtn-Oya5iPINK+TOCHCWY1_L`65(7NNz|M0&C`QD%LG5qsP9 zUGvIHYD&tmW)#&ZQFz1NS;Bf}nJSN_S5uhMQOiA&gw%x;nc6$?0+2~)8YP?2APjW4 z9$_pxmbDpIcE@v8yUU|~bt2xc4%JLm6?>B_!))ZHGh2|>X7a+9NaRwtak0ff|dMrW;cd+1jTz01&n@y<9s1SzpUZ zkue4=C#`d{R!U>gr$TzMjE2tAQXt;W$s1T%S5v}hX(Yft{7odBRb;(tX4Fj%Hv>yk zM8jx*S`TFyV9MCEh^wQR&?1V_aB+N4W^FO>qSu&e==8FwF7BD6#Y>wlQi?U*UH>$u zAX0@beT!)mMymeoW`=cX*o>PjdG$pn7q_0S_-YUErtfd;*25+?F@ti zX(%SCnDnv6|DC;_UX$3NQl_;JnH%a>2SyVy<-yFl>Y7@ttxZgB`=kqghT+hpZFOrk z;uWdp3?nM({X#w3BeICp*yK(Vjie?6Q!U|Q-ijvrCOHFTk*IOG^4g@liE1oUiS^#N zArD(X>Tft1Utc|zyg?}~&dF`co`tm)H6^t+*3GzHIGjllPwB1Fw+Qc0-YXl+VNBhm zj7X*Hu2{*Bck085HuY4JOMt59b&k;u$uvk;SeCLJ!D@-T0=Ko-RTETmNxSf-#d@yM zz(iWwr4DK&vIm6I3nRnXg|MU~)zl4UBxtSU-|9qU@; z6mG?1B1EQu`u4@_Prv>J#oCI;IAL{J&8(E7i(>kpE-qiYNVtW1jRS6)yO~~7QN5O3 ztI3kweUN4xBW*?Z)Qw`L4pot^e`WA3qogHop^TcjjV4o~42;=)TvrD23D(oZWH4&` zhwWMGrg;&Lchtg9#V1N#!^f>Pqqg+6y0kq?J^j>liG>d9WEn=D(%L*3n+f@tFVptC z3_Xs>(n)^NKn*sf*9)mdD{1s68khbv#Uaz8j&NThtGzL@8Lj#I^La#j$H&{+K1sP2 z8M#}VVhnRM-H55nt-mOs>4j^pEjxN+wA*640FtFQNr{z5-NK~KMyv(a*aYlRTM_Oj zoe~bzqCM8WuzsEW#8?_;5GR%aHRgn?Ak$2)#>+%wFq>W>&_vSn&+9pC5G7(b{3sds z0dylU@kVlb4O^bJc|1d=ChNS$Hgwqk`Y-18*VaRp#SUBI7w+_lZz+iN1u}#-seR=G zo3V?Vt`d^t+Bgeam~s7{vn$%2d9a-9u!i^YlUbuhjmbtKif?NB>uM}smnX7WR#01y zDK&NiL@g}3X_ao|t^df-DqEEDO%JD}@3TrC=h09AYuqJoKEmrUV3(Y@`DsnEnDpB8 zNNlrD)HcY_WLwML?=m89ou9_hAPU!iH;YGqis+~9vt8#HM!W~^B{OZZ?oTCZyosA` zuqo90jWxJc&Z%uo>sXmltkE^)OqZ2Mjqj!&PFW78(JvK&Or_c;P0)uJ{){^BvGLB4 zZrUV1bi2-`eNtMcPDOhySjy+H7iIkzvOal2jG>pvgj{2pbZ?~DXh?t6*`C8ZO+^is zS%$ITXCet#lA)!Onuv|#3)c2deISusRvHzNn*oVdltn#qKl<2{lL}ZvMCpvGg1=_Y zu5Jo#!nDoMHc6gTg_4z;T#s43ZNeIHh90=t@Q7)1m-O)}YkktC3DfFiSW0iJJl4`p zn+DEQ-=|kEvEufBdi&PqHjX6Q{Y4vsNb2!Xmg!xFEe(d$%-sEi06|cKhpz!ZnVw(2 zCo?OnvMRH>8>IF|IA&}KXjE6_AKs7|Z8>vlH2C*=pYe zUn}34evOjg$wB<(`qTa0UPBl2h-e$CGtY_w(@MHb2KH2OM0;G+9E&PU9uj@A2U+tK zW1`dGkdYq^(6kVOT(%L=;aR6n|6_mu_<%bHn&7fsT+^>1PiMK%o(yY=qm7BU0IJ^> zWjk7AHij$0>6b+esQe&G5e)`+Q^r^xgc-)pyf;?Ap;)2O2eK)VFuCy2 zpJz8!oOE;Om?1Pi_Z*&*_05qcc&jx4b=6v6i(UrA;HVlPh5;#a znEq<2wezbfCf7Ja7w1p!_HR&6#>fU>_>j#c=%&u@5_qJLMmysVy{4qS_M|biTIcNX zeJhVXU8IM`Mx<`;!|f$7ncCbpW~~A*3aO@)yWakxyO8(-@ULa!2j9#zjTWc5+<5Zo zeD`~Pcroqc!J!rG*{kZ-kRwU!R;;9ysGcH?lS)UFaetrhy;iO6xtl2liXz=KPm0}kZ`Kse zac|?R@|x>^oz0&+Hev#W#ATPxhUSe&ixO_CVzP^n#(N+}j9<7%vIfu{|GQq^_OB5+ z!UuxbmzAc6J(j-|7wTLy*`kTgB zi(&D+4C`L< z==5W%n8@Cujtv97s6y2Y$O_bJHolbDoauJ6+jCy-#NAu+tGoaDOl_#)QuyRq=IGPkb4B-{}zm~ZEW3=ccaBNJt0h%C^rGosKgE1?$dk0KdD&e$-`wAi)1Vde zh$PS>Zfo5(4awZW$Vxv@#u2`BM+169xo2flHse4#E#B+4J}IaQiEmC6g|{=v)9B(> zLDh<@IbdjJ;qRDL3IxBFP>Urk*=~_8TkYHveW@TKZv{t*{w&+p@l8-21u)ht+DKBIYZfsNA1|Pin;MPH2{y6&#!(Xxw{QU}r&f0J zWEHcg2&$nd z%F#ljjvvbB$wk%3)--oqmt`IzHBsSmtNXLsFlyk`ehhQ<-XZ>!8+ph^ zMAVHIDNyn(Mb|{Q<}bHRiI~@IY^6mVIJ6y+ zfYb@_GY-^vPJmfmZ-2;^u$+7>7YDbW(*+e7u5Oh|@JMlUB#~Cw^mD1$LZid>M~}Y3 z;U<>ju&Qvc!UWW$&7`6=%%N9x2(cWM zALZhB$o4gaJr)5p6-rJ4gIB8!d49W|#R7roUO0HW;(4??yl&hq{?RjZv9COhb5reN zvh6y-p5Q*p`Rvty{d}?b>)$^7yx4~_7VJqEP!;r<1(8}8FyHh02A6sM>v6sPQ+`B+ zDKZMYw};g4;^JKxVls*;1a04XRu5-8{^Q#+wQ`k%NOjX%yks0>`kNZ-L3W;#83=Z> z7LT$Zpk#uX-MaL_(yV;WSz?ntC7L;pd$_*-@#7v~*51g~+HJ~($KmmvLkp@#qyUA4 zp)c#3MyNXjD%9iAgTw9M=r8&lnN}CsI0*_8{ zqsz%|}_0Xawts&{ppRPYmY1&y3 zfY&4Q^^5v>sVXigpT2HrYjD)pzSWB51`7eMLbXvPbg-?Ha)tyRj0w>GZtXjfXabM- zV08G++Cd&T<=)%9Iot7-A-bCOa~2mI{{~JMo&JTw&@B*$PeU)icUk$FAizitlPYGbsQ-|Du&`3l zAlSb&@=5mX6K|-WcvZ?E}p&m>2H`FdVco#<9WgR7X2oZ!zyI!a-Jy3Y164e z4CfAt?p0&@qrTLYA)?*e=vme;7_O6RZM6JHEB8d&75;cttc5oP)*1_qkutlYUMZTl z9De!B;Afozkb$BS>N1g_+R|Mxb;FpWLw=zIfvp@=x0{^scwuJxX%k(9(D3s?0DW&2t-{Dsru4wd5ttm(kYzm84YyJKvM<{*-f5SpxcCl zMQlAH?xn?}u)-wdu%%IpJ&L@2(Cy_c*i92rxDB;Q>US649`{%Gf84Pc%PNn4zW5}! zZhgBuUIcROV4VIMN!*I^q&JK8cZgh?&&%L5_g0<$$K&n>bGehK;KErhJ+42La7<@k z#*Q$Y8jp%Y;VI2c2n8(nAa*T%8E2I*TJ*{9aD27D-<)pH0ts z2>?MG|IKPVP3)z^UAyKpDoQX8RJ?4Srl>wMpl)R=JJK>{tRGDUCb`+|?&$ZF^CHl3 z{684q$5lEI7K}tF(^+xYexK|bwG@y zD@kA@>We8uA&Amb9L^xxr`~K_MZ_Mt?Y|O7SBffAYXo0V(?nc>} zwKTR-L(}R0L!}FQvDr-C4F(*&;uWgrZ;Dr7#9vm(;XOxlmOw6Z9zkHczs#P-Awb6n z71cIn1cJ8`IH(~A-Io31A&6Kn$r}x&Lu>2ZqXuUYdKBF1)WjkGY&b(~6C6&%lLq9T zfRnoXiEKJ*+tJY##z}UIqx0;=1Ias-VSh0HG^7(6nb`y$)5)0vx~Xg}myd|;R?;K^ zkLNao$w)Ye>83PE|AP03No5TW18duwn|}v#6NT= zLQmM#s#Lc-J0H-fa6LdHxtXR53{q2}YvnWZRl$-r)<1^VB{?Ss^Gm`+~CArmBNhLG|Z(dgTima+Ug7g zlM`bDM2sKZbGy8uiRYMYBpocRDc9c=-J+`OY~vne=p@;=-nUe}hqs{)NiuiR#Bs&4 z5Umf>o3#V~4%CmN)RZOwF1)0j8BJ$MzrB-Yd^bvdwbIEyCvag zC!aq2ztg{;e*W?qSDgKJ`dLD#vW>n%!#%DtVQtl@R+^1@)j;sujVVXzY6mK&G>C2@ z{(|4sz08lBt7~jycdp0Su0*Xer*n)xFyy11(T0A;JQgYSsuO)j3GtRpr*JX4GiB;c z-&HepQ+nDLpC@(iHkumSg<$@lFqo3c8`O>(wq_MEbh#Mx%qT1T!1iQgo%q}ij!$Gl z`8Wk!$*2jZz*dAST?>-n?#gG{11GhW3eMihjWu#B+8Guj0jPB(kv*=rYIY(|;gKBq zAqFOLbQW5<0Y}7&q!ayNkD3qF5*xt*uthh(O)zkA zBnf8KkEQv!2Spjd4_muT88BtZ^)_ecK&u2|BI*G^1cOZXK*fdCl?0I80do53cNuxi zY&J&88FIPcgJk=Vl! z)yk}%LIy$=^wzpu4i#xw(VjQw>m?Y|Q!zeAAAbI#7nC{?9ovgA9yRitjAX^CwJmOj zSu3`iD$k=qCeJTBw-{f^$sVuue*LssKkPU6cZn9TUrr2Et%IufUO39W7YCp>fDuyN z-rwAiWgfXZ)Kbmu2>G&YVK<&%QyYC5aW!;Mw_UFlcLmXA9ZlfU3&s2tkB;dW6U5#R zm^X94O)O5%13Jf6W5870ol471Q>)+Jf{Ih!3+ZE7NowuS1$`M?c3&cS`KUj}0FWGI zWb`K*UMpO}7#0IX{-A#OP7ROWvQt%@pO(Az?Cs^_=H}{jyS?9jTJLttuhh-TNg5+6 z5zpoKbehN-22)ZJhZy!AG1!M_Cb!ux{vtjW z2Heq5Wr*c-@LC7{PXTImbF;kfu4*T$>nrBB*bbX+BT$c z;!zlOzb~5B%wfNYR;5O$|}>xy4zgJ$-IA>xui&;g6r1Y}T+m*J6}0f4`W= zBC)Z945rs)Hi~KQ)cv#A++E+3kfVG~1C=(ww7=D8l-JUVA849~V1jt~mH4RFwtc=F zN@1v`5n5eC09mcfX<@ZqE3G99aaW{A^{G6%dk$OmVgzX^)-CSfr_*sxO&Ym$Tm|-WtC%6&$umFY3B*Ypm5;3$aQqjIq8t zJznE!1u9VikF7cLyq>EEczf}$n>=KbFk6i?bd1RkrI~dlNHC_nF$toVIqJ({TXV%I zgMlD9rj$gI^55>iZdUn#^e>HEN1!TD@CL?|N88BvNU0vrby%ttkD@QV+9Pw z;)n0RcH&dL$LvHVoemSplI{*`)ZOFjH=Vu8v$)c%8fevR<`&H5~Mgun{h^)6e@PK-gZu^C@+}S(HSpdIq11_g6xMP9Uh9sd4MS+@8;Oct2IU~IZ`SpR zl<+tnu9T%xsdE3A-e+uk!p7Ct`MMS~2n6PuBCLg^kLB5-$wWH^(`4k!oBcfo3qTt< za4twUF^W=lk%z@Ka3FnXz}<6~OnY=q^Or{RZtZ@{t5CC~Hc8ffQj)P7$rqGipp%%| zdhNG^Yo8l$8xd;W|JPpQHKD#a4A&p`+jLER<1FI3&3Rkq*EiV7uug}mC61dela3n4-0U4I?B)^p+sgtvxZtl^o z{vbTv#eQ`U38=?pGB)g%8%%O2v<8?pA!OS7&?g{&C%jhc@o-J^YQwU@k|Xj9K7e($ z@6vBec|igc{*hG}#p~fjUS5t{e+>$>#_2ojzx%KQu|i@bjc)9Nby^ZN2)Zx=#)etwD{Tir+88a}|$d0qG6ANiv2%)~=vGR4ViEYk97o+wuB% zX0Jb7U*npd^%V+76CND<%Bzp}H;=b>7n^@erGM0B;?6ZJA42oaF+V2klxgxO$EG{B zs`SQKMk@~xTt{FH`EL>M7rWq-~WjkC>grEUN>o`N2ep68Cx zH^cMV!y*q>-Ng-;TH_AjGi=Be1|)Cq|D>Wn13(+&-#)xRNBNG7ZWQG?uA@42y6q#H zLoF@E;x&@-KFWi@S#Gc_9pQ_v=pko&xtU*{Uura?)pYadaHg(oo~CC2?6FK-e&=hg z*q2bTr>^W`k?!bgKuoI6)iAd}pah-r?XVJj4i%AH>~Mvwl*cQ7Y3oSb8`%sz&1EBu zu(NYFtg9w+1^OU-iMPB1X^IMo5MiP`jab_%OEHD>sZdxS{1K~;G-G?sJV0p%>T zyy6722iM!>ZoOFUF#`b5Rfg1;PkM`Ad&rchu<;VEd-9ivyThst^II{pVv;ZR7>YX0 z%F)1p3NBq{3kRHZNk@!btL6&Osq5iid<-w4&Q1+24A=L-^Cc%yc1A0`47FAMGT638 zY|_RlYH?>f`3bpPzzZ>h_~qy%@$gdf`ATn;bMS%JdJw|_H7@9&lvP>nUsK!v_|S`YNHO=ubNVu_CDH%lRyfFdy9EwL#&Pr`k>I?jXda)QeTMdSnUMaoMj>{U}P z@}-&rpQ`>wIdfUfe5Y4^2kNi2bjjDJ+-kt9E31@X4cwPC8}prsm!-gi#aG%5O=y(- z93>v9GJzA2ae!5OmC0An5%0L%ZkK<`6q_4NY!v#ZOZ)Zu1A`#iz{sd>mtUEas3%|U zua+n@&CHYCEQ|Rg>(?pt%;l_NU|Uhu1DYLu+Q5?BedT$~>V{*+FRF{M3AdEq-9NH4_*mTzi^|Lj$dx)or+}obEtsO_GGsTrMGNKbwVUrqueJE*&E=Hl$p#A1lY0}|0VZo=@jR9N%a zpc}uA=8Q{kYq7;gACPW{eN0fJn5rmnYEh2Ft}W8>Xk&4c&ul@HMR0R9T0O+s=Spa! zR*CB_)oeaxW++zaA%;n!=-Yp?71G%;wgHz*VTkdPitV){0s4JL3$*h$IBP_Ic=Y9q zOf58~ZS+W3Gzvfag7K*BJ6J4kQ)TG;Af^lO<^zZ@NYgOYLi>gDeOYNSim@hemX^oB z)$r^6#h-Vpe{8|L@E%`s|Lnu#;J7`t$wEl;X(1g*t06Y0?>! zQyYnCJ405N%3LI`g@@-m?ti30%rRj{)PNBworkC^l@4}px!@0A3;+6CuD?*G4ojtNgwmeaoFLT+{eWRU0HCJ(8gS}ES%9%MK`|q`O0od${`-S0Vx5ai zqsAcP9z#@M7NKFe#Kf?_1mRY=`FFprAY8EQjJLhFuQ?dlCwK)`s{E~ATm^1*2Mkd2-w5QS{Fc?BxlDS5gB{d}igud4A_{A>+izACl_*9)jfeH{ z+CjN?qY)N~m(ryqHjDsR9@mZl;RPR5>1dax&w(nEn zl})O(%j2dzTH`*MoatG9?p%R8)wG5sr?hM?c2Rt-)#WvS4iCPGsCA519A-8z&!TG# zW#Sf!<8tEDtJ5M2Mm3j3+NenfPP65r5^)`CsJAASVLfqay2&i3e?{C$e2XTIFWM;>OA<6?S(-SHF5Y!I z*{;_NGODP}tDX;FO=sZ)qIy#_-A0-CiDzN=T?yxv zU^y)}Bq$2*y+Clf6;2Gq7lDuljQ-n|g|QaI$%SE?PyYJ2eiYGjCtbyd;~8{H>+8KG zZiqd$exunL|3fIkbyrr62~8I%N+?$hc;Th0baJBW3t@uIEf=Zn*86LyIl$%F{ciEg z?f&{^`BkzY`3TYz-pF;Em7$Al$`z9%0Gylz_lI(|F3s@C}70M@}^u1(M-E;qEIMY>is%)pm2aChzFL`pjXsH-Fh~ zzHaWs7P+@;UFW;=G_5O{;};|nYg6BK8i%J5-AmN<7*^mR!ZmZ^rN$tv6#n7?9PpaM zYH;^eQ>J%S#sg?SkxlL54Cg7khd3R^mdYi!>+AIv6Cu{C@0ZC@6!VsVP>SK{==CDp z9gPx-qOHnY@dzrnPgPuNwc z9!3ey7V}OVznE^RqCn^5zwaN>_P`e4;{Immn}4x*1&nlSgk#g65&4bnc#rrNV}NbEwHElQY0-q`kqCDLZtOu$8wDr{}YyGc+oEIs0^a@#Vv(xl&#wvBoS& zc{4BG4Lw`pOW{R2Y7`Vz1`vgE;i4V6Ns`rm80!SNuYnqGMuX)JCDAEPQ$9Ry*Ng9) z?f!9j(^6t%HcDMujjdia$( z6TcZ=We9PrPYb5 zx@1P}0aBE@zIa%!v?`MA0@)g=RJ#AZUg+qtA2++g2taVz*Og<~+6ccfnit?7aZH@WbN>J`Q8KlQR5@%dOQasNZv&OUw~$ zm_)pNwAo$oHMU1hQmK_+^}6-r&}+SWlaJT*8sRr#h?OFRaq zMn^HA9$>%zdcXa1w9`8{noh)HaTMxO_Op}xYp9yo>~|C%&)()g)FqHO{}dKudEuNe z`cJf?j+fN6UF_hgE+8{^;F2C!`_Z?L$s`>9mo!%iFAedzzTdKI%I~&cekBuA?5CCw z`E-5QF_h}&X0Lu;ua0Jm*}M1eLmgL}^@f{naJe`AYOyaOM&hRVPnD{zw+G!!g|3xv z6H-tGaW=yUK=s4rbzz_2UXgmMD?>S*Vmw~xAw$AN@2y~MIIJ9lU&y1-?Car=X|WVD zp4z(sv=jp(GsH&*l!rxpL_G3Py7AVBpfIp7rKC_b%F;gf{HL~Z&3)SIaam=~8M*#C zXE+FUPUPF1z+3jmYZ)YW zw{!uM&FYT@t482+QW;T|m>6OerJv!DM@DvQk;JYlLWWLdNhc$v75a zNYP_cF;%=qoU}I9tXOs{s8jRRWg*^VIzW=+Kt&iH1G?;Vb+;UB`XSydDx9%$CLm^ z@9qUD0doNK3kj}E%Tl7th*k;X z0|hqFHS4bC)y&~ExwnxA1*4FJ0r`=%Vh^;;zbsJFiaf`A3Q1_A`tPx7G8!u`;;2 zS4ORa7;msLcd_jFzTwwz@W>tR3diKEulsNO&>f0SmX|j$I~C^t&yx_8bejzf@{pOx z<$V%ilE+Oloz?Cbc)a&$HoVwxG5Y9qb?)vCwC5d3PvmM5xO=pZQX`sSUK%;`40D!W z4drv{&fK}#E;`c$XS>gjH#hN=lH#vGJHX-G(Tsep3TcDS>7wU-20_S>NGoC%j2oPvLHZ4}$%gM{ZfM2Fat3HFXDbG$EKz7AEW4PTY^=c()*~SR09mR6A(Xf95AK*8L z85WW;b9pG4qY6pb-_L3od^aC1*|54(?A z_4!^X3NWJTPCZij!D@7OE1*uA!ZN=AVMIoqJdV|B7-46>Vx-Y7E9(s>x(VgVe*0)O z&5xCXvyQuc>kuXjBexqwzBB>4-vS21gk^(Au72hUj zSVVd{n7(yjj57)4;|d;+tfLNh9rsj-7g1a8oEliW17eh zv#xAE(!?Jyjae%XqWH}T=5(N$5D-sg_Dygu_g~tx^yPfAqJp09s0aY#Ba-kG$TNVg2L)#-!LEXA{wMl99I?Al9&+BJ1&+k4yBQ-N@M(XQWU*b~a-R4q}m{ za+-1oNO%b$L-PGSf<+ANxJQ)=SSI}MdU<<>A8BX#!yn;JBl1)gC&qHOn*o~uA*50l zqDGz>sKL<)J4OBYV)L$1IlSgLnttmL$*Am0Z)k^igLS`S^34%UOv#5Dk^?5f{|aKm zD_}YZb`B^}JX3%9aQ1Z?a_AQWsjadNfR4E!TX3pbgtJLCk()!Dc}Pk&wiEQ-4hMgy zqh}5~pvnfEhPe%m3P$txmuv0Ah1`VPSoaa$-{xZce{ApXzVc;PW75Y4+138!bljL; z2S?f#f^Wo02=iT9~1OL8q5#5j2bCM7Z(+LxN&Ia zDRF&Gt+*XodIooZVyV+67uZ?AM0tM68hT=tjoLZSU~NBEGg76%k(>Cm*m|*kTCE>w z99PrIhjVT6;PfZetvyWcajud518fQNg?Cq@e$s1l1{n4X;c5_NNns)VA5hdsLgOuM7YV0l^CrM z6@G0J8zqX$Sce==1-F_I1gQatjx3OYg|#5*q|!VEQc1b@xr*{fWDj~Fa4RpAvr)#e z{kzH$&R(6biKK^FS>{VAx8x7i`!QylKgrmDY!3X_9wT;n`VpDE&+za@zA4IO1P5QE7L(nOqDCm ziomVcoJOmQlHrkrM&O4|Gao4K9yieM0>ozn$K2 z70Ei>i#A}+#uP@2^IQ>#*1OaVTIypnN0#cy;CSm22*1r*<=gMuZneFJtlDQ@7D0x8 zvYn2IZ$IhSnFh5a(a}Q#6a=hr3pxTYuEJ6@IgaRAG8}!-<#=*G4s@a!X_ele0-hDIIe|&44JSQ` znE)w!qxf&diGsx?hT*Uf4-E#=Ak2mA->K=Aw}b98oNv~7J08h zvJ&Rrrupq2Ah3sgP5Yo^tW#%la+PvH+@kGFEjIjx0T6B*nRBT;EGS}}G+DUItTOISDy4kit(KTmvf0bst}Htx`qOKDgWssr z22`)o8meI#=!hEjYX*E4a)adn@2^lqXNgDPWNo1xP zR6^PXG(b?8K~4c;A2Q(K(qe@{L5o)GU&HY^hiHNXuKpVMa&~D=*@kDU2_>xCIOI6| z>*pP!mewhTVm1@v^TW+%xr-YeDltw|OAdMO#+|jGCWY?pyA$J;asm{gruWn8Fr@;2vVUvdgPTik!*rIgQdkTZty zr+7>%V)~2pGEfAm+(KMVTVps+UT~6)tq=DOBzttVocnkfm`1|a+EYHjnPR4Dg2qtU zvG^&v_dUv6#gL%9R5}Z$DFmTYvFce-y2K7@M5ee3geO1We{D6 z^u#c#!Dmr8DDe{ImQ7M6wKj|tGj4`^7m`(gVZc8fPwYa%h*)2Bzh_}6f=Boo7g zF5yD!3LYNTcQAti9&>6I{LW?`+NIWG5>(OK(rl}+T1T&6+3Dw!#k^>j>+;`je%~g{ z+J3{-`dc*ZYde==Fyv?QT; z+lTnlOf80QKZ%z+zF6O+F-~sh#u6QYFjllgMg$0cX&-drQO@Yf#G`gqE|u9^zU~&f zhjyEPuT%HYh?(+>HvTOLUjrLdVMJZr57@mTmcezZG{nUY{Et1&x@YfhzuQ9P@P~(8mgX%;GBojuE32ULYW@4fth0Bt#oI(FtIuFi zx8f!o5n!#PKas3F8J^+Iq7E_p77f$YQ`~XzGTy#TXMUUlW0>6{=II;^Lm35>ZX@~P zc3gr)S4EK>XUA4sq1L_xb}l~s_KS_QE0^+x=WZk$sAfLBjvWm$@;Ejvn|WyGiB8Bj zQ^zFutk4(vx8x_F^!UQBp#lkma5PFXzl@12M1uZfwrA8qx07$eqSu#r3!Qs+Xi z|ATqt4$l#b?1l)jm<4;u;=@ zlX_Y;QcYS{A@;jw)J(BhZ^Kd=1~K*a`)0NN1+(HY@|vewAZoxr^4k613LI&U3r4iq zFFf=?G89OM?ZD_pCp@YZD^hb2)u?GkQ@~|}u2*4OflLfGmI6N{p%z{BL&26^M~zkk ztcvs`?B@674K5q~vdB04LX)_h8-etINZ)#i=I|D=ER8N-eXB#%pP49KBst1NC+9~F{@|n~pA<6MeQ2N|Y;}Zdl0uJe3 zo*?#R`)e8sePU?OzdV&(wv*7Ji!eL~C1Z^zZ{mO_9SvY$`h+(~GGkIhRx_I4=*Ajt zUwW@V)Fnq;jVKT5r9oMX+sB*z=HaFU3NJjnray7w(ZRtV+6)-5!mWFZZ-8jB7F4y9 zaDX;-iDC>vS!ybLUM{XUBh6u2_iCjgvPz+?riCEGLubl&-_rfJo4f0KSW7KfkO>2v zXXN-@{yY2Q9nVn2IMBrk={j7E@;eP4p-!SYofid@$z~+Nn+~$B7E|tx1`c7RY|7KP z9bSsoP~)6O#y;exT8Pw-sNnP5l6h|n_g+f#MAX9JkT4keQDJY- znFV$qwb5hA!1e>{MyoSvgjXn+@s+@(!#foxlplZ^`%kzkT*m`|80vP*n{ShqU|(!Y zL^GZunr9F`qO`ogN4GbXsjB8|w;xrAk+xcvWM~m1jV{P&pVjVg!KcS07iV*+&8mv= z)Buj*)^Kva&<{@$Qx^9pAp{QhJs~u$+AUyL|3pUL(szrVC2RQHa_Va~4hY1rOEIYvDMQ>%RI@Y<+d$*=^Ip)gk%b6-*EBZ-2r>7@=>5MH7G zG6ZGjcw&NwzGd)FfVW!x%U;2VD|PPV!owi-+^S7uM`trMR4vILX{a-SUbwdYq1dc3 zC1YRqlELF%4$V^xTx)i=NE_lDl^TuL z()PIAqg{lJzGkyuHrw?FTotT0TIl$X*>T;7!=%~-X>0;e6-mX6eG@!%G(kUtdHqpy z5$_AjWr=SRIv)uguZV zYZU41427#7)Co5c!XJ9!MjlJo@l3?-Feb4qdXoE;yh9K#v6dN;=ysxP-0a6WW zUOS`kMuD@*9t!l##)VVHq5=YO{hi9{dY|>44i2%)8BP7E!tt%IfKlj;iZ6T%OH8fc zxX~tz;Iqjd9CpKo@sN}f2CjpnG1H3$bB}Rp*1;G(HR;;Ibwn-3^$Z%Jo}ZUl-<6+F zsS!=I?^2{IuwGN^#=fPD?Q=uYD-B7na!5+eax2PI`$Vnte($-^Tf)0D+&{BK>BDGC zZotf*o|-i0)y@3-g;Y)K()}L4I}9y$u1*`J@*ZJUbEOVv=uBIE^v!-DmpUZ%y+I8Z z9ynA?ckmTS3|xp{Rj!m-T=`+G;V=@A9ZNJ{hpA~+P^D$ll|H3Fbn*4+Y4Naz3%Xn0 zU>3mQ`>Uqzffm$cKr7wJP5aP=@by|{)fS4@`S^-5)ywcH^51-P%oc)&m<+tI`P_7WF!#_G3T`S zwz=Yq<>xEFr7$IJUcK3+ostJEf~H0`#SfDE7kiwO|901-WLd;z7UyD=IYl42VWbyQ z)|2ndWxq}>YVZGgCQ4zlsRgXG8Z=-y=zWllt$FgH^&(d8~ z#|vi$7dd=wd47v2=2va*)vf6Wjq}GJLGm+~6qV7}d&}EpLa7vs8_(7esb8;Xrn87t zHlgkyqx&N`;o7}@+XW8u@5rp=&_mM~bp4&&(Ov#-{j^>^?$?XW?d|$%gTKzu)s~C- zLudf+m>g(;Z0PrIcgKs*t`Z<>fwwFQb`~nF4wJa*^;cA3j$)DVH^_7cm|S`t>1Tvj zIDAA1=7QX@2#{m@<#vC4v;4Yaqyq?nlFS;-l&VM!72^_Gt%G4O1E(uzqWQ0c9JLM` zKU9y*9g8c1t*AJmcq0tzmheNOf&Kn|bGqU7Ze+Rf53SXSbZy1NaqKpT_}-kIefgg` zG9^-4Bap41X^e{51AQ$48@BFmcYk1<*#cdpXg379`1o;g^q;TlEgd662qecZmPf#l zb!`}dhO$fN=1e6g0*q@IJDN%etnwb8uZ%j&_^4nAC{@KXEUo^VOXx^1hQ9;g0nu777fMCP zmw`TBKcj5y*Id z?X=-P0VkA%=f&B0W9YpsRW3)%6*wN;-ajme&!?ILHiMREaEVNSTIz3I6OWV^ic}UjoC=8-)bJPF1Jx*fVvk{mfft#6tnAb z3Nydv;q+zu-WkY%VnmvrqKWeaed^r>sft`ig#~WcV-;vQ{~xlb6?2 zGzLTpmdzDn@6J-sb+T9i<)g>V=O{3js*BVFv?r?{$7qe4$J@K)7TQncZ9gpUuSA_T z*&aG7RdTiHE#OI8MVA6k;r&#?7m2Y_RA?*FX^YgLO`6L{{J1{v?+GR3>W?u+u;i^X z)eawNF7GGI$QMa2gkTxJ?Y+a^RyqalfGV zl1Hg{K4}_iyfp8s9{^u6&McMDt@8090jtcrms`dcs7*Sr!4hQ_1P+)waTZibaup%77}t-d0tr*_Y{NJKB?vk1>GXguxkf@1 zFk(_g7n8m+5|+ieU0)Hdw|s>tYLPiBbU2)E08a(_7+coY?ol;QP4SND}d9^`0`EIoyO}&Pmgkp`8oW|X6fvtD&*Qc`+RKcT% zXpJZHjqs-z?-umWpaq6D_6jJ1Y`U9xQ9)x;swj**TxGE8pM*q-D2^gcd0ZRzq)p0r z6PJyzRPol`;Kj5g@R`F{#*$8wD#xkKG{(F8pKpXOvpt}RqOpnXFY?rGY1*0ewrxs+ za(LA%<0&wRS&}9f1RN%nZ`}`&j=kg4^$7UKRZG631{bOqXG=3=PAN4Goy`}TTaTk? zpLVGJUwOy&I_8ACKY_L$lC zl4}&MsL3!t0ZkaCKkZ|c-0im}8Qn_AxtZzA@=Qgcd>K#{Y)N#r=f^1Id8Npa{9|%W MN)sb<1%~E-0jCCFQvd(} literal 0 HcmV?d00001 diff --git a/stglibs/ibpp.lib/row.cpp b/stglibs/ibpp.lib/row.cpp new file mode 100644 index 00000000..fa6939ad --- /dev/null +++ b/stglibs/ibpp.lib/row.cpp @@ -0,0 +1,1580 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: row.cpp,v 1.2 2009/03/19 20:00:28 faust Exp $ +// Subject : IBPP, Row class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include +#include +#include + +using namespace ibpp_internals; + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void RowImpl::SetNull(int param) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::SetNull", _("The row is not initialized.")); + if (param < 1 || param > mDescrArea->sqld) + throw LogicExceptionImpl("Row::SetNull", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[param-1]); + if (! (var->sqltype & 1)) + throw LogicExceptionImpl("Row::SetNull", _("This column can't be null.")); + + *var->sqlind = -1; // Set the column to SQL NULL + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, bool value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[bool]", _("The row is not initialized.")); + + SetValue(param, ivBool, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const char* cstring) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[char*]", _("The row is not initialized.")); + if (cstring == 0) + throw LogicExceptionImpl("Row::Set[char*]", _("null char* pointer detected.")); + + SetValue(param, ivByte, cstring, (int)strlen(cstring)); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const void* bindata, int len) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[void*]", _("The row is not initialized.")); + if (bindata == 0) + throw LogicExceptionImpl("Row::Set[void*]", _("null char* pointer detected.")); + if (len < 0) + throw LogicExceptionImpl("Row::Set[void*]", _("Length must be >= 0")); + + SetValue(param, ivByte, bindata, len); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const std::string& s) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[string]", _("The row is not initialized.")); + + SetValue(param, ivString, (void*)&s); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, int16_t value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[int16_t]", _("The row is not initialized.")); + + SetValue(param, ivInt16, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, int32_t value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[int32_t]", _("The row is not initialized.")); + + SetValue(param, ivInt32, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, int64_t value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[int64_t]", _("The row is not initialized.")); + + SetValue(param, ivInt64, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, float value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[float]", _("The row is not initialized.")); + + SetValue(param, ivFloat, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, double value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[double]", _("The row is not initialized.")); + + SetValue(param, ivDouble, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const IBPP::Timestamp& value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[Timestamp]", _("The row is not initialized.")); + + SetValue(param, ivTimestamp, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const IBPP::Date& value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[Date]", _("The row is not initialized.")); + + if (mDialect == 1) + { + // In dialect 1, IBPP::Date is supposed to work with old 'DATE' + // fields which are actually ISC_TIMESTAMP. + IBPP::Timestamp timestamp(value); + SetValue(param, ivTimestamp, ×tamp); + } + else + { + // Dialect 3 + SetValue(param, ivDate, (void*)&value); + } + + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const IBPP::Time& value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[Time]", _("The row is not initialized.")); + if (mDialect == 1) + throw LogicExceptionImpl("Row::Set[Time]", _("Requires use of a dialect 3 database.")); + + SetValue(param, ivTime, &value); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const IBPP::Blob& blob) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[Blob]", _("The row is not initialized.")); + if (mDatabase != 0 && blob->DatabasePtr() != mDatabase) + throw LogicExceptionImpl("Row::Set[Blob]", + _("IBlob and Row attached to different databases")); + if (mTransaction != 0 && blob->TransactionPtr() != mTransaction) + throw LogicExceptionImpl("Row::Set[Blob]", + _("IBlob and Row attached to different transactions")); + + SetValue(param, ivBlob, blob.intf()); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const IBPP::Array& array) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[Array]", _("The row is not initialized.")); + if (mDatabase != 0 && array->DatabasePtr() != mDatabase) + throw LogicExceptionImpl("Row::Set[Array]", + _("IArray and Row attached to different databases")); + if (mTransaction != 0 && array->TransactionPtr() != mTransaction) + throw LogicExceptionImpl("Row::Set[Array]", + _("IArray and Row attached to different transactions")); + + SetValue(param, ivArray, (void*)array.intf()); + mUpdated[param-1] = true; +} + +void RowImpl::Set(int param, const IBPP::DBKey& key) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[DBKey]", _("The row is not initialized.")); + + SetValue(param, ivDBKey, (void*)&key); + mUpdated[param-1] = true; +} + +/* +void RowImpl::Set(int param, const IBPP::Value& value) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Set[Value]", _("The row is not initialized.")); + + //SetValue(param, ivDBKey, (void*)&key); + //mUpdated[param-1] = true; +} +*/ + +bool RowImpl::IsNull(int column) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::IsNull", _("The row is not initialized.")); + if (column < 1 || column > mDescrArea->sqld) + throw LogicExceptionImpl("Row::IsNull", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[column-1]); + return ((var->sqltype & 1) && *(var->sqlind) != 0) ? true : false; +} + +bool RowImpl::Get(int column, bool& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivBool); + if (pvalue != 0) + retvalue = (*(char*)pvalue == 0 ? false : true); + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, char* retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Row::Get", _("Null pointer detected")); + + int sqllen; + void* pvalue = GetValue(column, ivByte, &sqllen); + if (pvalue != 0) + { + memcpy(retvalue, pvalue, sqllen); + retvalue[sqllen] = '\0'; + } + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, void* bindata, int& userlen) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + if (bindata == 0) + throw LogicExceptionImpl("Row::Get", _("Null pointer detected")); + if (userlen < 0) + throw LogicExceptionImpl("Row::Get", _("Length must be >= 0")); + + int sqllen; + void* pvalue = GetValue(column, ivByte, &sqllen); + if (pvalue != 0) + { + // userlen says how much bytes the user can accept + // let's shorten it, if there is less bytes available + if (sqllen < userlen) userlen = sqllen; + memcpy(bindata, pvalue, userlen); + } + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, std::string& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivString, &retvalue); + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, int16_t& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivInt16); + if (pvalue != 0) + retvalue = *(int16_t*)pvalue; + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, int32_t& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivInt32); + if (pvalue != 0) + retvalue = *(int32_t*)pvalue; + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, int64_t& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivInt64); + if (pvalue != 0) + retvalue = *(int64_t*)pvalue; + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, float& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivFloat); + if (pvalue != 0) + retvalue = *(float*)pvalue; + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, double& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivDouble); + if (pvalue != 0) + retvalue = *(double*)pvalue; + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, IBPP::Timestamp& timestamp) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivTimestamp, (void*)×tamp); + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, IBPP::Date& date) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + if (mDialect == 1) + { + // Dialect 1. IBPP::Date is supposed to work with old 'DATE' + // fields which are actually ISC_TIMESTAMP. + IBPP::Timestamp timestamp; + void* pvalue = GetValue(column, ivTimestamp, (void*)×tamp); + if (pvalue != 0) date = timestamp; + return pvalue == 0 ? true : false; + } + else + { + void* pvalue = GetValue(column, ivDate, (void*)&date); + return pvalue == 0 ? true : false; + } +} + +bool RowImpl::Get(int column, IBPP::Time& time) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivTime, (void*)&time); + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, IBPP::Blob& retblob) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivBlob, (void*)retblob.intf()); + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, IBPP::DBKey& retkey) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivDBKey, (void*)&retkey); + return pvalue == 0 ? true : false; +} + +bool RowImpl::Get(int column, IBPP::Array& retarray) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + void* pvalue = GetValue(column, ivArray, (void*)retarray.intf()); + return pvalue == 0 ? true : false; +} + +/* +const IBPP::Value RowImpl::Get(int column) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + //void* value = GetValue(column, ivArray, (void*)retarray.intf()); + //return value == 0 ? true : false; + return IBPP::Value(); +} +*/ + +bool RowImpl::IsNull(const std::string& name) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::IsNull", _("The row is not initialized.")); + + return IsNull(ColumnNum(name)); +} + +bool RowImpl::Get(const std::string& name, bool& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, char* retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get[char*]", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, void* retvalue, int& count) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get[void*,int]", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue, count); +} + +bool RowImpl::Get(const std::string& name, std::string& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::GetString", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, int16_t& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, int32_t& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, int64_t& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, float& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, double& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, IBPP::Timestamp& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, IBPP::Date& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, IBPP::Time& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string&name, IBPP::Blob& retblob) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retblob); +} + +bool RowImpl::Get(const std::string& name, IBPP::DBKey& retvalue) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retvalue); +} + +bool RowImpl::Get(const std::string& name, IBPP::Array& retarray) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name), retarray); +} + +/* +const IBPP::Value RowImpl::Get(const std::string& name) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Get", _("The row is not initialized.")); + + return Get(ColumnNum(name)); +} +*/ + +int RowImpl::Columns() +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::Columns", _("The row is not initialized.")); + + return mDescrArea->sqld; +} + +int RowImpl::ColumnNum(const std::string& name) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnNum", _("The row is not initialized.")); + if (name.empty()) + throw LogicExceptionImpl("Row::ColumnNum", _("Column name not found.")); + + XSQLVAR* var; + char Uname[sizeof(var->sqlname)+1]; // Max size of sqlname + '\0' + + // Local upper case copy of the column name + size_t len = name.length(); + if (len > sizeof(var->sqlname)) len = sizeof(var->sqlname); + strncpy(Uname, name.c_str(), len); + Uname[len] = '\0'; + char* p = Uname; + while (*p != '\0') { *p = char(toupper(*p)); ++p; } + + // Loop through the columns of the descriptor + for (int i = 0; i < mDescrArea->sqld; i++) + { + var = &(mDescrArea->sqlvar[i]); + if (var->sqlname_length != (int16_t)len) continue; + if (strncmp(Uname, var->sqlname, len) == 0) return i+1; + } + + // Failed finding the column name, let's retry using the aliases + char Ualias[sizeof(var->aliasname)+1]; // Max size of aliasname + '\0' + + // Local upper case copy of the column name + len = name.length(); + if (len > sizeof(var->aliasname)) len = sizeof(var->aliasname); + strncpy(Ualias, name.c_str(), len); + Ualias[len] = '\0'; + p = Ualias; + while (*p != '\0') { *p = char(toupper(*p)); ++p; } + + // Loop through the columns of the descriptor + for (int i = 0; i < mDescrArea->sqld; i++) + { + var = &(mDescrArea->sqlvar[i]); + if (var->aliasname_length != (int16_t)len) continue; + if (strncmp(Ualias, var->aliasname, len) == 0) return i+1; + } + + throw LogicExceptionImpl("Row::ColumnNum", _("Could not find matching column.")); +#ifdef __DMC__ + return 0; // DMC errronously warns here about a missing return +#endif +} + +/* +ColumnName, ColumnAlias, ColumnTable : all these 3 have a mistake. +Ideally, the strings should be stored elsewhere (like _Numerics and so on) to +take into account the final '\0' which needs to be added. For now, we insert +the '\0' in the original data, which will cut the 32th character. Not terribly +bad, but should be cleanly rewritten. +*/ + +const char* RowImpl::ColumnName(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnName", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumName", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + if (var->sqlname_length >= 31) var->sqlname_length = 31; + var->sqlname[var->sqlname_length] = '\0'; + return var->sqlname; +} + +const char* RowImpl::ColumnAlias(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnAlias", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumnAlias", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + if (var->aliasname_length >= 31) var->aliasname_length = 31; + var->aliasname[var->aliasname_length] = '\0'; + return var->aliasname; +} + +const char* RowImpl::ColumnTable(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnTable", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumnTable", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + if (var->relname_length >= 31) var->relname_length = 31; + var->relname[var->relname_length] = '\0'; + return var->relname; +} + +IBPP::SDT RowImpl::ColumnType(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnType", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumnType", _("Variable index out of range.")); + + IBPP::SDT value; + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + + switch (var->sqltype & ~1) + { + case SQL_TEXT : value = IBPP::sdString; break; + case SQL_VARYING : value = IBPP::sdString; break; + case SQL_SHORT : value = IBPP::sdSmallint; break; + case SQL_LONG : value = IBPP::sdInteger; break; + case SQL_INT64 : value = IBPP::sdLargeint; break; + case SQL_FLOAT : value = IBPP::sdFloat; break; + case SQL_DOUBLE : value = IBPP::sdDouble; break; + case SQL_TIMESTAMP : value = IBPP::sdTimestamp; break; + case SQL_TYPE_DATE : value = IBPP::sdDate; break; + case SQL_TYPE_TIME : value = IBPP::sdTime; break; + case SQL_BLOB : value = IBPP::sdBlob; break; + case SQL_ARRAY : value = IBPP::sdArray; break; + default : throw LogicExceptionImpl("Row::ColumnType", + _("Found an unknown sqltype !")); + } + + return value; +} + +int RowImpl::ColumnSubtype(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnSubtype", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumnSubtype", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + return (int)var->sqlsubtype; +} + +int RowImpl::ColumnSize(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnSize", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumnSize", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + return var->sqllen; +} + +int RowImpl::ColumnScale(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnScale", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumnScale", _("Variable index out of range.")); + + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + return -var->sqlscale; +} + +bool RowImpl::ColumnUpdated(int varnum) +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnUpdated", _("The row is not initialized.")); + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("Row::ColumnUpdated", _("Variable index out of range.")); + + return mUpdated[varnum-1]; +} + +bool RowImpl::Updated() +{ + if (mDescrArea == 0) + throw LogicExceptionImpl("Row::ColumnUpdated", _("The row is not initialized.")); + + for (int i = 0; i < mDescrArea->sqld; i++) + if (mUpdated[i]) return true; + return false; +} + +IBPP::Database RowImpl::DatabasePtr() const +{ + return mDatabase; +} + +IBPP::Transaction RowImpl::TransactionPtr() const +{ + return mTransaction; +} + +IBPP::IRow* RowImpl::Clone() +{ + // By definition the clone of an IBPP Row is a new row (so refcount=0). + + RowImpl* clone = new RowImpl(*this); + return clone; +} + +IBPP::IRow* RowImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + return this; +} + +void RowImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void RowImpl::SetValue(int varnum, IITYPE ivType, const void* value, int userlen) +{ + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("RowImpl::SetValue", _("Variable index out of range.")); + if (value == 0) + throw LogicExceptionImpl("RowImpl::SetValue", _("Unexpected null pointer detected.")); + + int16_t len; + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + switch (var->sqltype & ~1) + { + case SQL_TEXT : + if (ivType == ivString) + { + std::string* svalue = (std::string*)value; + len = (int16_t)svalue->length(); + if (len > var->sqllen) len = var->sqllen; + strncpy(var->sqldata, svalue->c_str(), len); + while (len < var->sqllen) var->sqldata[len++] = ' '; + } + else if (ivType == ivByte) + { + if (userlen > var->sqllen) userlen = var->sqllen; + memcpy(var->sqldata, value, userlen); + while (userlen < var->sqllen) var->sqldata[userlen++] = ' '; + } + else if (ivType == ivDBKey) + { + IBPP::DBKey* key = (IBPP::DBKey*)value; + key->GetKey(var->sqldata, var->sqllen); + } + else if (ivType == ivBool) + { + var->sqldata[0] = *(bool*)value ? 'T' : 'F'; + len = 1; + while (len < var->sqllen) var->sqldata[len++] = ' '; + } + else throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_VARYING : + if (ivType == ivString) + { + std::string* svalue = (std::string*)value; + len = (int16_t)svalue->length(); + if (len > var->sqllen) len = var->sqllen; + *(int16_t*)var->sqldata = (int16_t)len; + strncpy(var->sqldata+2, svalue->c_str(), len); + } + else if (ivType == ivByte) + { + if (userlen > var->sqllen) userlen = var->sqllen; + *(int16_t*)var->sqldata = (int16_t)userlen; + memcpy(var->sqldata+2, value, userlen); + } + else if (ivType == ivBool) + { + *(int16_t*)var->sqldata = (int16_t)1; + var->sqldata[2] = *(bool*)value ? 'T' : 'F'; + } + else throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_SHORT : + if (ivType == ivBool) + { + *(int16_t*)var->sqldata = int16_t(*(bool*)value ? 1 : 0); + } + else if (ivType == ivInt16) + { + *(int16_t*)var->sqldata = *(int16_t*)value; + } + else if (ivType == ivInt32) + { + if (*(int32_t*)value < consts::min16 || *(int32_t*)value > consts::max16) + throw LogicExceptionImpl("RowImpl::SetValue", + _("Out of range numeric conversion !")); + *(int16_t*)var->sqldata = (int16_t)*(int32_t*)value; + } + else if (ivType == ivInt64) + { + if (*(int64_t*)value < consts::min16 || *(int64_t*)value > consts::max16) + throw LogicExceptionImpl("RowImpl::SetValue", + _("Out of range numeric conversion !")); + *(int16_t*)var->sqldata = (int16_t)*(int64_t*)value; + } + else if (ivType == ivFloat) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-var->sqlscale]; + *(int16_t*)var->sqldata = + (int16_t)floor(*(float*)value * multiplier + 0.5); + } + else if (ivType == ivDouble) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-var->sqlscale]; + *(int16_t*)var->sqldata = + (int16_t)floor(*(double*)value * multiplier + 0.5); + } + else throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_LONG : + if (ivType == ivBool) + { + *(ISC_LONG*)var->sqldata = *(bool*)value ? 1 : 0; + } + else if (ivType == ivInt16) + { + *(ISC_LONG*)var->sqldata = *(int16_t*)value; + } + else if (ivType == ivInt32) + { + *(ISC_LONG*)var->sqldata = *(ISC_LONG*)value; + } + else if (ivType == ivInt64) + { + if (*(int64_t*)value < consts::min32 || *(int64_t*)value > consts::max32) + throw LogicExceptionImpl("RowImpl::SetValue", + _("Out of range numeric conversion !")); + *(ISC_LONG*)var->sqldata = (ISC_LONG)*(int64_t*)value; + } + else if (ivType == ivFloat) + { + // This SQL_LONG is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-var->sqlscale]; + *(ISC_LONG*)var->sqldata = + (ISC_LONG)floor(*(float*)value * multiplier + 0.5); + } + else if (ivType == ivDouble) + { + // This SQL_LONG is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-var->sqlscale]; + *(ISC_LONG*)var->sqldata = + (ISC_LONG)floor(*(double*)value * multiplier + 0.5); + } + else throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_INT64 : + if (ivType == ivBool) + { + *(int64_t*)var->sqldata = *(bool*)value ? 1 : 0; + } + else if (ivType == ivInt16) + { + *(int64_t*)var->sqldata = *(int16_t*)value; + } + else if (ivType == ivInt32) + { + *(int64_t*)var->sqldata = *(int32_t*)value; + } + else if (ivType == ivInt64) + { + *(int64_t*)var->sqldata = *(int64_t*)value; + } + else if (ivType == ivFloat) + { + // This SQL_INT64 is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-var->sqlscale]; + *(int64_t*)var->sqldata = + (int64_t)floor(*(float*)value * multiplier + 0.5); + } + else if (ivType == ivDouble) + { + // This SQL_INT64 is a NUMERIC(x,y), scale it ! + double multiplier = consts::dscales[-var->sqlscale]; + *(int64_t*)var->sqldata = + (int64_t)floor(*(double*)value * multiplier + 0.5); + } + else throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_FLOAT : + if (ivType != ivFloat || var->sqlscale != 0) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + *(float*)var->sqldata = *(float*)value; + break; + + case SQL_DOUBLE : + if (ivType != ivDouble) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + if (var->sqlscale != 0) + { + // Round to scale of NUMERIC(x,y) + double multiplier = consts::dscales[-var->sqlscale]; + *(double*)var->sqldata = + floor(*(double*)value * multiplier + 0.5) / multiplier; + } + else *(double*)var->sqldata = *(double*)value; + break; + + case SQL_TIMESTAMP : + if (ivType != ivTimestamp) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + encodeTimestamp(*(ISC_TIMESTAMP*)var->sqldata, *(IBPP::Timestamp*)value); + break; + + case SQL_TYPE_DATE : + if (ivType != ivDate) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + encodeDate(*(ISC_DATE*)var->sqldata, *(IBPP::Date*)value); + break; + + case SQL_TYPE_TIME : + if (ivType != ivTime) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + encodeTime(*(ISC_TIME*)var->sqldata, *(IBPP::Time*)value); + break; + + case SQL_BLOB : + if (ivType == ivBlob) + { + BlobImpl* blob = (BlobImpl*)value; + blob->GetId((ISC_QUAD*)var->sqldata); + } + else if (ivType == ivString) + { + BlobImpl blob(mDatabase, mTransaction); + blob.Save(*(std::string*)value); + blob.GetId((ISC_QUAD*)var->sqldata); + } + else throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_ARRAY : + if (ivType != ivArray) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + { + ArrayImpl* array = (ArrayImpl*)value; + array->GetId((ISC_QUAD*)var->sqldata); + // When an array has been affected to a column, we want to reset + // its ID. This way, the next WriteFrom() on the same Array object + // will allocate a new ID. This protects against storing the same + // array ID in multiple columns or rows. + array->ResetId(); + } + break; + + default : throw LogicExceptionImpl("RowImpl::SetValue", + _("The field uses an unsupported SQL type !")); + } + + if (var->sqltype & 1) *var->sqlind = 0; // Remove the 0 flag +} + +void* RowImpl::GetValue(int varnum, IITYPE ivType, void* retvalue) +{ + if (varnum < 1 || varnum > mDescrArea->sqld) + throw LogicExceptionImpl("RowImpl::GetValue", _("Variable index out of range.")); + + void* value; + int len; + XSQLVAR* var = &(mDescrArea->sqlvar[varnum-1]); + + // When there is no value (SQL NULL) + if ((var->sqltype & 1) && *(var->sqlind) != 0) return 0; + + switch (var->sqltype & ~1) + { + case SQL_TEXT : + if (ivType == ivString) + { + // In case of ivString, 'void* retvalue' points to a std::string where we + // will directly store the data. + std::string* str = (std::string*)retvalue; + str->erase(); + str->append(var->sqldata, var->sqllen); + value = retvalue; // value != 0 means 'not null' + } + else if (ivType == ivByte) + { + // In case of ivByte, void* retvalue points to an int where we + // will store the len of the available data + if (retvalue != 0) *(int*)retvalue = var->sqllen; + value = var->sqldata; + } + else if (ivType == ivDBKey) + { + IBPP::DBKey* key = (IBPP::DBKey*)retvalue; + key->SetKey(var->sqldata, var->sqllen); + value = retvalue; + } + else if (ivType == ivBool) + { + mBools[varnum-1] = 0; + if (var->sqllen >= 1) + { + char c = var->sqldata[0]; + if (c == 't' || c == 'T' || c == 'y' || c == 'Y' || c == '1') + mBools[varnum-1] = 1; + } + value = &mBools[varnum-1]; + } + else throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_VARYING : + if (ivType == ivString) + { + // In case of ivString, 'void* retvalue' points to a std::string where we + // will directly store the data. + std::string* str = (std::string*)retvalue; + str->erase(); + str->append(var->sqldata+2, (int32_t)*(int16_t*)var->sqldata); + value = retvalue; + } + else if (ivType == ivByte) + { + // In case of ivByte, void* retvalue points to an int where we + // will store the len of the available data + if (retvalue != 0) *(int*)retvalue = (int)*(int16_t*)var->sqldata; + value = var->sqldata+2; + } + else if (ivType == ivBool) + { + mBools[varnum-1] = 0; + len = *(int16_t*)var->sqldata; + if (len >= 1) + { + char c = var->sqldata[2]; + if (c == 't' || c == 'T' || c == 'y' || c == 'Y' || c == '1') + mBools[varnum-1] = 1; + } + value = &mBools[varnum-1]; + } + else throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_SHORT : + if (ivType == ivInt16) + { + value = var->sqldata; + } + else if (ivType == ivBool) + { + if (*(int16_t*)var->sqldata == 0) mBools[varnum-1] = 0; + else mBools[varnum-1] = 1; + value = &mBools[varnum-1]; + } + else if (ivType == ivInt32) + { + mInt32s[varnum-1] = *(int16_t*)var->sqldata; + value = &mInt32s[varnum-1]; + } + else if (ivType == ivInt64) + { + mInt64s[varnum-1] = *(int16_t*)var->sqldata; + value = &mInt64s[varnum-1]; + } + else if (ivType == ivFloat) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-var->sqlscale]; + mFloats[varnum-1] = (float)(*(int16_t*)var->sqldata / divisor); + + value = &mFloats[varnum-1]; + } + else if (ivType == ivDouble) + { + // This SQL_SHORT is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-var->sqlscale]; + mNumerics[varnum-1] = *(int16_t*)var->sqldata / divisor; + value = &mNumerics[varnum-1]; + } + else throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_LONG : + if (ivType == ivInt32) + { + value = var->sqldata; + } + else if (ivType == ivBool) + { + if (*(int32_t*)var->sqldata == 0) mBools[varnum-1] = 0; + else mBools[varnum-1] = 1; + value = &mBools[varnum-1]; + } + else if (ivType == ivInt16) + { + int32_t tmp = *(int32_t*)var->sqldata; + if (tmp < consts::min16 || tmp > consts::max16) + throw LogicExceptionImpl("RowImpl::GetValue", + _("Out of range numeric conversion !")); + mInt16s[varnum-1] = (int16_t)tmp; + value = &mInt16s[varnum-1]; + } + else if (ivType == ivInt64) + { + mInt64s[varnum-1] = *(int32_t*)var->sqldata; + value = &mInt64s[varnum-1]; + } + else if (ivType == ivFloat) + { + // This SQL_LONG is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-var->sqlscale]; + mFloats[varnum-1] = (float)(*(int32_t*)var->sqldata / divisor); + value = &mFloats[varnum-1]; + } + else if (ivType == ivDouble) + { + // This SQL_LONG is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-var->sqlscale]; + mNumerics[varnum-1] = *(int32_t*)var->sqldata / divisor; + value = &mNumerics[varnum-1]; + } + else throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_INT64 : + if (ivType == ivInt64) + { + value = var->sqldata; + } + else if (ivType == ivBool) + { + if (*(int64_t*)var->sqldata == 0) mBools[varnum-1] = 0; + else mBools[varnum-1] = 1; + value = &mBools[varnum-1]; + } + else if (ivType == ivInt16) + { + int64_t tmp = *(int64_t*)var->sqldata; + if (tmp < consts::min16 || tmp > consts::max16) + throw LogicExceptionImpl("RowImpl::GetValue", + _("Out of range numeric conversion !")); + mInt16s[varnum-1] = (int16_t)tmp; + value = &mInt16s[varnum-1]; + } + else if (ivType == ivInt32) + { + int64_t tmp = *(int64_t*)var->sqldata; + if (tmp < consts::min32 || tmp > consts::max32) + throw LogicExceptionImpl("RowImpl::GetValue", + _("Out of range numeric conversion !")); + mInt32s[varnum-1] = (int32_t)tmp; + value = &mInt32s[varnum-1]; + } + else if (ivType == ivFloat) + { + // This SQL_INT64 is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-var->sqlscale]; + mFloats[varnum-1] = (float)(*(int64_t*)var->sqldata / divisor); + value = &mFloats[varnum-1]; + } + else if (ivType == ivDouble) + { + // This SQL_INT64 is a NUMERIC(x,y), scale it ! + double divisor = consts::dscales[-var->sqlscale]; + mNumerics[varnum-1] = *(int64_t*)var->sqldata / divisor; + value = &mNumerics[varnum-1]; + } + else throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_FLOAT : + if (ivType != ivFloat) + throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + value = var->sqldata; + break; + + case SQL_DOUBLE : + if (ivType != ivDouble) + throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + if (var->sqlscale != 0) + { + // Round to scale y of NUMERIC(x,y) + double multiplier = consts::dscales[-var->sqlscale]; + mNumerics[varnum-1] = + floor(*(double*)var->sqldata * multiplier + 0.5) / multiplier; + value = &mNumerics[varnum-1]; + } + else value = var->sqldata; + break; + + case SQL_TIMESTAMP : + if (ivType != ivTimestamp) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + decodeTimestamp(*(IBPP::Timestamp*)retvalue, *(ISC_TIMESTAMP*)var->sqldata); + value = retvalue; + break; + + case SQL_TYPE_DATE : + if (ivType != ivDate) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + decodeDate(*(IBPP::Date*)retvalue, *(ISC_DATE*)var->sqldata); + value = retvalue; + break; + + case SQL_TYPE_TIME : + if (ivType != ivTime) + throw WrongTypeImpl("RowImpl::SetValue", var->sqltype, ivType, + _("Incompatible types.")); + decodeTime(*(IBPP::Time*)retvalue, *(ISC_TIME*)var->sqldata); + value = retvalue; + break; + + case SQL_BLOB : + if (ivType == ivBlob) + { + BlobImpl* blob = (BlobImpl*)retvalue; + blob->SetId((ISC_QUAD*)var->sqldata); + value = retvalue; + } + else if (ivType == ivString) + { + BlobImpl blob(mDatabase, mTransaction); + blob.SetId((ISC_QUAD*)var->sqldata); + std::string* str = (std::string*)retvalue; + blob.Load(*str); + value = retvalue; + } + else throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + break; + + case SQL_ARRAY : + if (ivType != ivArray) + throw WrongTypeImpl("RowImpl::GetValue", var->sqltype, ivType, + _("Incompatible types.")); + { + ArrayImpl* array = (ArrayImpl*)retvalue; + array->SetId((ISC_QUAD*)var->sqldata); + value = retvalue; + } + break; + + default : throw LogicExceptionImpl("RowImpl::GetValue", + _("Found an unknown sqltype !")); + } + + return value; +} + +void RowImpl::Free() +{ + if (mDescrArea != 0) + { + for (int i = 0; i < mDescrArea->sqln; i++) + { + XSQLVAR* var = &(mDescrArea->sqlvar[i]); + if (var->sqldata != 0) + { + switch (var->sqltype & ~1) + { + case SQL_ARRAY : + case SQL_BLOB : delete (ISC_QUAD*) var->sqldata; break; + case SQL_TIMESTAMP :delete (ISC_TIMESTAMP*) var->sqldata; break; + case SQL_TYPE_TIME :delete (ISC_TIME*) var->sqldata; break; + case SQL_TYPE_DATE :delete (ISC_DATE*) var->sqldata; break; + case SQL_TEXT : + case SQL_VARYING : delete [] var->sqldata; break; + case SQL_SHORT : delete (int16_t*) var->sqldata; break; + case SQL_LONG : delete (int32_t*) var->sqldata; break; + case SQL_INT64 : delete (int64_t*) var->sqldata; break; + case SQL_FLOAT : delete (float*) var->sqldata; break; + case SQL_DOUBLE : delete (double*) var->sqldata; break; + default : throw LogicExceptionImpl("RowImpl::Free", + _("Found an unknown sqltype !")); + } + } + if (var->sqlind != 0) delete var->sqlind; + } + delete [] (char*)mDescrArea; + mDescrArea = 0; + } + + mNumerics.clear(); + mFloats.clear(); + mInt64s.clear(); + mInt32s.clear(); + mInt16s.clear(); + mBools.clear(); + mStrings.clear(); + mUpdated.clear(); + + mDialect = 0; + mDatabase = 0; + mTransaction = 0; +} + +void RowImpl::Resize(int n) +{ + const int size = XSQLDA_LENGTH(n); + + Free(); + mDescrArea = (XSQLDA*) new char[size]; + + memset(mDescrArea, 0, size); + mNumerics.resize(n); + mFloats.resize(n); + mInt64s.resize(n); + mInt32s.resize(n); + mInt16s.resize(n); + mBools.resize(n); + mStrings.resize(n); + mUpdated.resize(n); + for (int i = 0; i < n; i++) + { + mNumerics[i] = 0.0; + mFloats[i] = 0.0; + mInt64s[i] = 0; + mInt32s[i] = 0; + mInt16s[i] = 0; + mBools[i] = 0; + mStrings[i].erase(); + mUpdated[i] = false; + } + + mDescrArea->version = SQLDA_VERSION1; + mDescrArea->sqln = (int16_t)n; +} + +void RowImpl::AllocVariables() +{ + int i; + for (i = 0; i < mDescrArea->sqld; i++) + { + XSQLVAR* var = &(mDescrArea->sqlvar[i]); + switch (var->sqltype & ~1) + { + case SQL_ARRAY : + case SQL_BLOB : var->sqldata = (char*) new ISC_QUAD; + memset(var->sqldata, 0, sizeof(ISC_QUAD)); + break; + case SQL_TIMESTAMP :var->sqldata = (char*) new ISC_TIMESTAMP; + memset(var->sqldata, 0, sizeof(ISC_TIMESTAMP)); + break; + case SQL_TYPE_TIME :var->sqldata = (char*) new ISC_TIME; + memset(var->sqldata, 0, sizeof(ISC_TIME)); + break; + case SQL_TYPE_DATE :var->sqldata = (char*) new ISC_DATE; + memset(var->sqldata, 0, sizeof(ISC_DATE)); + break; + case SQL_TEXT : var->sqldata = new char[var->sqllen+1]; + memset(var->sqldata, ' ', var->sqllen); + var->sqldata[var->sqllen] = '\0'; + break; + case SQL_VARYING : var->sqldata = new char[var->sqllen+3]; + memset(var->sqldata, 0, 2); + memset(var->sqldata+2, ' ', var->sqllen); + var->sqldata[var->sqllen+2] = '\0'; + break; + case SQL_SHORT : var->sqldata = (char*) new int16_t(0); break; + case SQL_LONG : var->sqldata = (char*) new int32_t(0); break; + case SQL_INT64 : var->sqldata = (char*) new int64_t(0); break; + case SQL_FLOAT : var->sqldata = (char*) new float(0.0); break; + case SQL_DOUBLE : var->sqldata = (char*) new double(0.0); break; + default : throw LogicExceptionImpl("RowImpl::AllocVariables", + _("Found an unknown sqltype !")); + } + if (var->sqltype & 1) var->sqlind = new short(-1); // 0 indicator + } +} + +bool RowImpl::MissingValues() +{ + for (int i = 0; i < mDescrArea->sqld; i++) + if (! mUpdated[i]) return true; + return false; +} + +RowImpl& RowImpl::operator=(const RowImpl& copied) +{ + Free(); + + const int n = copied.mDescrArea->sqln; + const int size = XSQLDA_LENGTH(n); + + // Initial brute copy + mDescrArea = (XSQLDA*) new char[size]; + memcpy(mDescrArea, copied.mDescrArea, size); + + // Copy of the columns data + for (int i = 0; i < mDescrArea->sqld; i++) + { + XSQLVAR* var = &(mDescrArea->sqlvar[i]); + XSQLVAR* org = &(copied.mDescrArea->sqlvar[i]); + switch (var->sqltype & ~1) + { + case SQL_ARRAY : + case SQL_BLOB : var->sqldata = (char*) new ISC_QUAD; + memcpy(var->sqldata, org->sqldata, sizeof(ISC_QUAD)); + break; + case SQL_TIMESTAMP :var->sqldata = (char*) new ISC_TIMESTAMP; + memcpy(var->sqldata, org->sqldata, sizeof(ISC_TIMESTAMP)); + break; + case SQL_TYPE_TIME :var->sqldata = (char*) new ISC_TIME; + memcpy(var->sqldata, org->sqldata, sizeof(ISC_TIME)); + break; + case SQL_TYPE_DATE :var->sqldata = (char*) new ISC_DATE; + memcpy(var->sqldata, org->sqldata, sizeof(ISC_DATE)); + break; + case SQL_TEXT : var->sqldata = new char[var->sqllen+1]; + memcpy(var->sqldata, org->sqldata, var->sqllen+1); + break; + case SQL_VARYING : var->sqldata = new char[var->sqllen+3]; + memcpy(var->sqldata, org->sqldata, var->sqllen+3); + break; + case SQL_SHORT : var->sqldata = (char*) new int16_t(*(int16_t*)org->sqldata); break; + case SQL_LONG : var->sqldata = (char*) new int32_t(*(int32_t*)org->sqldata); break; + case SQL_INT64 : var->sqldata = (char*) new int64_t(*(int64_t*)org->sqldata); break; + case SQL_FLOAT : var->sqldata = (char*) new float(*(float*)org->sqldata); break; + case SQL_DOUBLE : var->sqldata = (char*) new double(*(double*)org->sqldata); break; + default : throw LogicExceptionImpl("RowImpl::Ctor", + _("Found an unknown sqltype !")); + } + if (var->sqltype & 1) var->sqlind = new short(*org->sqlind); // 0 indicator + } + + // Pointers init, real data copy + mNumerics = copied.mNumerics; + mFloats = copied.mFloats; + mInt64s = copied.mInt64s; + mInt32s = copied.mInt32s; + mInt16s = copied.mInt16s; + mBools = copied.mBools; + mStrings = copied.mStrings; + + mDialect = copied.mDialect; + mDatabase = copied.mDatabase; + mTransaction = copied.mTransaction; + + return *this; +} + +RowImpl::RowImpl(const RowImpl& copied) + : IBPP::IRow(), mRefCount(0), mDescrArea(0) +{ + // mRefCount and mDescrArea are set to 0 before using the assignment operator + *this = copied; // The assignment operator does the real copy +} + +RowImpl::RowImpl(int dialect, int n, DatabaseImpl* db, TransactionImpl* tr) + : mRefCount(0), mDescrArea(0) +{ + Resize(n); + mDialect = dialect; + mDatabase = db; + mTransaction = tr; +} + +RowImpl::~RowImpl() +{ + try { Free(); } + catch (...) { } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/service.cpp b/stglibs/ibpp.lib/service.cpp new file mode 100644 index 00000000..26726ea1 --- /dev/null +++ b/stglibs/ibpp.lib/service.cpp @@ -0,0 +1,774 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: service.cpp,v 1.1 2007/05/05 17:00:43 faust Exp $ +// Subject : IBPP, Service class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +using namespace ibpp_internals; + +#ifdef IBPP_UNIX +#include +#define Sleep(x) usleep(x) +#endif + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void ServiceImpl::Connect() +{ + if (mHandle != 0) return; // Already connected + + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mUserName.empty()) + throw LogicExceptionImpl("Service::Connect", _("Unspecified user name.")); + if (mUserPassword.empty()) + throw LogicExceptionImpl("Service::Connect", _("Unspecified user password.")); + + // Attach to the Service Manager + IBS status; + SPB spb; + std::string connect; + + // Build a SPB based on the properties + spb.Insert(isc_spb_version); + spb.Insert(isc_spb_current_version); + spb.InsertString(isc_spb_user_name, 1, mUserName.c_str()); + spb.InsertString(isc_spb_password, 1, mUserPassword.c_str()); + + if (! mServerName.empty()) + { + connect = mServerName; + connect += ":"; + } + + connect += "service_mgr"; + + (*gds.Call()->m_service_attach)(status.Self(), (short)connect.size(), (char*)connect.c_str(), + &mHandle, spb.Size(), spb.Self()); + if (status.Errors()) + { + mHandle = 0; // Should be, but better be sure... + throw SQLExceptionImpl(status, "Service::Connect", _("isc_service_attach failed")); + } +} + +void ServiceImpl::Disconnect() +{ + if (mHandle == 0) return; // Already disconnected + + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + + IBS status; + + // Detach from the service manager + (*gds.Call()->m_service_detach)(status.Self(), &mHandle); + + // Set mHandle to 0 now, just in case we need to throw, because Disconnect() + // is called from Service destructor and we want to maintain a coherent state. + mHandle = 0; + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::Disconnect", _("isc_service_detach failed")); +} + +void ServiceImpl::GetVersion(std::string& version) +{ + // Based on a patch provided by Torsten Martinsen (SourceForge 'bullestock') + + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::GetVersion", _("Service is not connected.")); + + IBS status; + SPB spb; + RB result(250); + + spb.Insert(isc_info_svc_server_version); + + (*gds.Call()->m_service_query)(status.Self(), &mHandle, 0, 0, 0, spb.Size(), spb.Self(), + result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::GetVersion", _("isc_service_query failed")); + + result.GetString(isc_info_svc_server_version, version); +} + +void ServiceImpl::AddUser(const IBPP::User& user) +{ + if (gds.Call()->mGDSVersion >= 60 && mHandle == 0) + throw LogicExceptionImpl("Service::AddUser", _("Service is not connected.")); + if (user.username.empty()) + throw LogicExceptionImpl("Service::AddUser", _("Username required.")); + if (user.password.empty()) + throw LogicExceptionImpl("Service::AddUser", _("Password required.")); + + IBS status; + SPB spb; + spb.Insert(isc_action_svc_add_user); + spb.InsertString(isc_spb_sec_username, 2, user.username.c_str()); + spb.InsertString(isc_spb_sec_password, 2, user.password.c_str()); + if (! user.firstname.empty()) + spb.InsertString(isc_spb_sec_firstname, 2, user.firstname.c_str()); + if (! user.middlename.empty()) + spb.InsertString(isc_spb_sec_middlename, 2, user.middlename.c_str()); + if (! user.lastname.empty()) + spb.InsertString(isc_spb_sec_lastname, 2, user.lastname.c_str()); + if (user.userid != 0) + spb.InsertQuad(isc_spb_sec_userid, (int32_t)user.userid); + if (user.groupid != 0) + spb.InsertQuad(isc_spb_sec_groupid, (int32_t)user.groupid); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::AddUser", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::ModifyUser(const IBPP::User& user) +{ + if (gds.Call()->mGDSVersion >= 60 && mHandle == 0) + throw LogicExceptionImpl("Service::ModifyUser", _("Service is not connected.")); + if (user.username.empty()) + throw LogicExceptionImpl("Service::ModifyUser", _("Username required.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_modify_user); + spb.InsertString(isc_spb_sec_username, 2, user.username.c_str()); + if (! user.password.empty()) + spb.InsertString(isc_spb_sec_password, 2, user.password.c_str()); + if (! user.firstname.empty()) + spb.InsertString(isc_spb_sec_firstname, 2, user.firstname.c_str()); + if (! user.middlename.empty()) + spb.InsertString(isc_spb_sec_middlename, 2, user.middlename.c_str()); + if (! user.lastname.empty()) + spb.InsertString(isc_spb_sec_lastname, 2, user.lastname.c_str()); + if (user.userid != 0) + spb.InsertQuad(isc_spb_sec_userid, (int32_t)user.userid); + if (user.groupid != 0) + spb.InsertQuad(isc_spb_sec_groupid, (int32_t)user.groupid); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::ModifyUser", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::RemoveUser(const std::string& username) +{ + + if (gds.Call()->mGDSVersion >= 60 && mHandle == 0) + throw LogicExceptionImpl("Service::RemoveUser", _("Service is not connected.")); + if (username.empty()) + throw LogicExceptionImpl("Service::RemoveUser", _("Username required.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_delete_user); + spb.InsertString(isc_spb_sec_username, 2, username.c_str()); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::RemoveUser", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::GetUser(IBPP::User& user) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::GetUser", _("Service is not connected.")); + if (user.username.empty()) + throw LogicExceptionImpl("Service::GetUser", _("Username required.")); + + SPB spb; + spb.Insert(isc_action_svc_display_user); + spb.InsertString(isc_spb_sec_username, 2, user.username.c_str()); + + IBS status; + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::GetUser", _("isc_service_start failed")); + + RB result(8000); + char request[] = {isc_info_svc_get_users}; + status.Reset(); + (*gds.Call()->m_service_query)(status.Self(), &mHandle, 0, 0, 0, + sizeof(request), request, result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::GetUser", _("isc_service_query failed")); + + char* p = result.Self(); + if (*p != isc_info_svc_get_users) + throw SQLExceptionImpl(status, "Service::GetUser", _("isc_service_query returned unexpected answer")); + + p += 3; // Skips the 'isc_info_svc_get_users' and its total length + user.clear(); + while (*p != isc_info_end) + { + if (*p == isc_spb_sec_userid) + { + user.userid = (uint32_t)(*gds.Call()->m_vax_integer)(p+1, 4); + p += 5; + } + else if (*p == isc_spb_sec_groupid) + { + user.groupid = (uint32_t)(*gds.Call()->m_vax_integer)(p+1, 4); + p += 5; + } + else + { + unsigned short len = (unsigned short)(*gds.Call()->m_vax_integer)(p+1, 2); + switch (*p) + { + case isc_spb_sec_username : + // For each user, this is the first element returned + if (len != 0) user.username.assign(p+3, len); + break; + case isc_spb_sec_password : + if (len != 0) user.password.assign(p+3, len); + break; + case isc_spb_sec_firstname : + if (len != 0) user.firstname.assign(p+3, len); + break; + case isc_spb_sec_middlename : + if (len != 0) user.middlename.assign(p+3, len); + break; + case isc_spb_sec_lastname : + if (len != 0) user.lastname.assign(p+3, len); + break; + } + p += (3 + len); + } + } +} + +void ServiceImpl::GetUsers(std::vector& users) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::GetUsers", _("Service is not connected.")); + + SPB spb; + spb.Insert(isc_action_svc_display_user); + + IBS status; + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::GetUsers", _("isc_service_start failed")); + + RB result(8000); + char request[] = {isc_info_svc_get_users}; + status.Reset(); + (*gds.Call()->m_service_query)(status.Self(), &mHandle, 0, 0, 0, + sizeof(request), request, result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::GetUsers", _("isc_service_query failed")); + + users.clear(); + char* p = result.Self(); + if (*p != isc_info_svc_get_users) + throw SQLExceptionImpl(status, "Service::GetUsers", _("isc_service_query returned unexpected answer")); + + p += 3; // Skips the 'isc_info_svc_get_users' and its total length + IBPP::User user; + while (*p != isc_info_end) + { + if (*p == isc_spb_sec_userid) + { + user.userid = (uint32_t)(*gds.Call()->m_vax_integer)(p+1, 4); + p += 5; + } + else if (*p == isc_spb_sec_groupid) + { + user.groupid = (uint32_t)(*gds.Call()->m_vax_integer)(p+1, 4); + p += 5; + } + else + { + unsigned short len = (unsigned short)(*gds.Call()->m_vax_integer)(p+1, 2); + switch (*p) + { + case isc_spb_sec_username : + // For each user, this is the first element returned + if (! user.username.empty()) users.push_back(user); // Flush previous user + user.clear(); + if (len != 0) user.username.assign(p+3, len); + break; + case isc_spb_sec_password : + if (len != 0) user.password.assign(p+3, len); + break; + case isc_spb_sec_firstname : + if (len != 0) user.firstname.assign(p+3, len); + break; + case isc_spb_sec_middlename : + if (len != 0) user.middlename.assign(p+3, len); + break; + case isc_spb_sec_lastname : + if (len != 0) user.lastname.assign(p+3, len); + break; + } + p += (3 + len); + } + } + if (! user.username.empty()) users.push_back(user); // Flush last user +} + +void ServiceImpl::SetPageBuffers(const std::string& dbfile, int buffers) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::SetPageBuffers", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::SetPageBuffers", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_properties); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + spb.InsertQuad(isc_spb_prp_page_buffers, buffers); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::SetPageBuffers", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::SetSweepInterval(const std::string& dbfile, int sweep) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::SetSweepInterval", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::SetSweepInterval", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_properties); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + spb.InsertQuad(isc_spb_prp_sweep_interval, sweep); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::SetSweepInterval", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::SetSyncWrite(const std::string& dbfile, bool sync) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::SetSyncWrite", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::SetSyncWrite", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_properties); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + if (sync) spb.InsertByte(isc_spb_prp_write_mode, (char)isc_spb_prp_wm_sync); + else spb.InsertByte(isc_spb_prp_write_mode, (char)isc_spb_prp_wm_async); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::SetSyncWrite", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::SetReadOnly(const std::string& dbfile, bool readonly) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::SetReadOnly", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::SetReadOnly", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_properties); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + if (readonly) spb.InsertByte(isc_spb_prp_access_mode, (char)isc_spb_prp_am_readonly); + else spb.InsertByte(isc_spb_prp_access_mode, (char)isc_spb_prp_am_readwrite); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::SetReadOnly", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::SetReserveSpace(const std::string& dbfile, bool reserve) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::SetReserveSpace", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::SetReserveSpace", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_properties); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + if (reserve) spb.InsertByte(isc_spb_prp_reserve_space, (char)isc_spb_prp_res); + else spb.InsertByte(isc_spb_prp_reserve_space, (char)isc_spb_prp_res_use_full); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::SetReserveSpace", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::Shutdown(const std::string& dbfile, IBPP::DSM mode, int sectimeout) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::Shutdown", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::Shutdown", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_properties); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + switch (mode) + { + case IBPP::dsDenyAttach : + spb.InsertQuad(isc_spb_prp_deny_new_attachments, sectimeout); + break; + case IBPP::dsDenyTrans : + spb.InsertQuad(isc_spb_prp_deny_new_transactions, sectimeout); + break; + case IBPP::dsForce : + spb.InsertQuad(isc_spb_prp_shutdown_db, sectimeout); + break; + } + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::Shutdown", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::Restart(const std::string& dbfile) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::Restart", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::Restart", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_properties); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + spb.InsertQuad(isc_spb_options, isc_spb_prp_db_online); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::Restart", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::Sweep(const std::string& dbfile) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::Sweep", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::Sweep", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_repair); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + spb.InsertQuad(isc_spb_options, isc_spb_rpr_sweep_db); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::Sweep", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::Repair(const std::string& dbfile, IBPP::RPF flags) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::Repair", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::Repair", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_repair); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + + unsigned int mask; + if (flags & IBPP::rpValidateFull) mask = (isc_spb_rpr_full | isc_spb_rpr_validate_db); + else if (flags & IBPP::rpValidatePages) mask = isc_spb_rpr_validate_db; + else if (flags & IBPP::rpMendRecords) mask = isc_spb_rpr_mend_db; + else throw LogicExceptionImpl("Service::Repair", + _("One of rpMendRecords, rpValidatePages, rpValidateFull is required.")); + + if (flags & IBPP::rpReadOnly) mask |= isc_spb_rpr_check_db; + if (flags & IBPP::rpIgnoreChecksums) mask |= isc_spb_rpr_ignore_checksum; + if (flags & IBPP::rpKillShadows) mask |= isc_spb_rpr_kill_shadows; + + spb.InsertQuad(isc_spb_options, mask); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::Repair", _("isc_service_start failed")); + + Wait(); +} + +void ServiceImpl::StartBackup(const std::string& dbfile, + const std::string& bkfile, IBPP::BRF flags) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::Backup", _("Service is not connected.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::Backup", _("Main database file must be specified.")); + if (bkfile.empty()) + throw LogicExceptionImpl("Service::Backup", _("Backup file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_backup); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + spb.InsertString(isc_spb_bkp_file, 2, bkfile.c_str()); + if (flags & IBPP::brVerbose) spb.Insert(isc_spb_verbose); + + unsigned int mask = 0; + if (flags & IBPP::brIgnoreChecksums) mask |= isc_spb_bkp_ignore_checksums; + if (flags & IBPP::brIgnoreLimbo) mask |= isc_spb_bkp_ignore_limbo; + if (flags & IBPP::brMetadataOnly) mask |= isc_spb_bkp_metadata_only; + if (flags & IBPP::brNoGarbageCollect) mask |= isc_spb_bkp_no_garbage_collect; + if (flags & IBPP::brNonTransportable) mask |= isc_spb_bkp_non_transportable; + if (flags & IBPP::brConvertExtTables) mask |= isc_spb_bkp_convert; + if (mask != 0) spb.InsertQuad(isc_spb_options, mask); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::Backup", _("isc_service_start failed")); +} + +void ServiceImpl::StartRestore(const std::string& bkfile, const std::string& dbfile, + int pagesize, IBPP::BRF flags) +{ + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + if (mHandle == 0) + throw LogicExceptionImpl("Service::Restore", _("Service is not connected.")); + if (bkfile.empty()) + throw LogicExceptionImpl("Service::Restore", _("Backup file must be specified.")); + if (dbfile.empty()) + throw LogicExceptionImpl("Service::Restore", _("Main database file must be specified.")); + + IBS status; + SPB spb; + + spb.Insert(isc_action_svc_restore); + spb.InsertString(isc_spb_bkp_file, 2, bkfile.c_str()); + spb.InsertString(isc_spb_dbname, 2, dbfile.c_str()); + if (flags & IBPP::brVerbose) spb.Insert(isc_spb_verbose); + if (pagesize != 0) spb.InsertQuad(isc_spb_res_page_size, pagesize); + + unsigned int mask; + if (flags & IBPP::brReplace) mask = isc_spb_res_replace; + else mask = isc_spb_res_create; // Safe default mode + + if (flags & IBPP::brDeactivateIdx) mask |= isc_spb_res_deactivate_idx; + if (flags & IBPP::brNoShadow) mask |= isc_spb_res_no_shadow; + if (flags & IBPP::brNoValidity) mask |= isc_spb_res_no_validity; + if (flags & IBPP::brPerTableCommit) mask |= isc_spb_res_one_at_a_time; + if (flags & IBPP::brUseAllSpace) mask |= isc_spb_res_use_all_space; + if (mask != 0) spb.InsertQuad(isc_spb_options, mask); + + (*gds.Call()->m_service_start)(status.Self(), &mHandle, 0, spb.Size(), spb.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "Service::Restore", _("isc_service_start failed")); +} + +const char* ServiceImpl::WaitMsg() +{ + IBS status; + SPB req; + RB result(1024); + + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + + req.Insert(isc_info_svc_line); // Request one line of textual output + + // _service_query will only block until a line of result is available + // (or until the end of the task if it does not report information) + (*gds.Call()->m_service_query)(status.Self(), &mHandle, 0, 0, 0, + req.Size(), req.Self(), result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "ServiceImpl::Wait", _("isc_service_query failed")); + + // If message length is zero bytes, task is finished + if (result.GetString(isc_info_svc_line, mWaitMessage) == 0) return 0; + + // Task is not finished, but we have something to report + return mWaitMessage.c_str(); +} + +void ServiceImpl::Wait() +{ + IBS status; + SPB spb; + RB result(1024); + std::string msg; + + if (gds.Call()->mGDSVersion < 60) + throw LogicExceptionImpl("Service", _("Requires the version 6 of GDS32.DLL")); + + spb.Insert(isc_info_svc_line); + for (;;) + { + // Sleeps 1 millisecond upfront. This will release the remaining + // timeslot of the thread. Doing so will give a good chance for small + // services tasks to finish before we check if they are still running. + // The deal is to limit (in that particular case) the number of loops + // polling _service_query that will happen. + + Sleep(1); + + // _service_query will only block until a line of result is available + // (or until the end of the task if it does not report information) + (*gds.Call()->m_service_query)(status.Self(), &mHandle, 0, 0, 0, + spb.Size(), spb.Self(), result.Size(), result.Self()); + if (status.Errors()) + throw SQLExceptionImpl(status, "ServiceImpl::Wait", _("isc_service_query failed")); + + // If message length is zero bytes, task is finished + if (result.GetString(isc_info_svc_line, msg) == 0) return; + + status.Reset(); + result.Reset(); + } +} + +IBPP::IService* ServiceImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + return this; +} + +void ServiceImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void ServiceImpl::SetServerName(const char* newName) +{ + if (newName == 0) mServerName.erase(); + else mServerName = newName; +} + +void ServiceImpl::SetUserName(const char* newName) +{ + if (newName == 0) mUserName.erase(); + else mUserName = newName; +} + +void ServiceImpl::SetUserPassword(const char* newPassword) +{ + if (newPassword == 0) mUserPassword.erase(); + else mUserPassword = newPassword; +} + +ServiceImpl::ServiceImpl(const std::string& ServerName, + const std::string& UserName, const std::string& UserPassword) + : mRefCount(0), mHandle(0), + mServerName(ServerName), mUserName(UserName), mUserPassword(UserPassword) +{ +} + +ServiceImpl::~ServiceImpl() +{ + try { if (Connected()) Disconnect(); } + catch (...) { } +} + +// +// Eof +// diff --git a/stglibs/ibpp.lib/statement.cpp b/stglibs/ibpp.lib/statement.cpp new file mode 100644 index 00000000..1a3031fe --- /dev/null +++ b/stglibs/ibpp.lib/statement.cpp @@ -0,0 +1,1307 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: statement.cpp,v 1.2 2009/03/19 20:00:28 faust Exp $ +// Subject : IBPP, Service class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void StatementImpl::Prepare(const std::string& sql) +{ + if (mDatabase == 0) + throw LogicExceptionImpl("Statement::Prepare", _("An IDatabase must be attached.")); + if (mDatabase->GetHandle() == 0) + throw LogicExceptionImpl("Statement::Prepare", _("IDatabase must be connected.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Statement::Prepare", _("An ITransaction must be attached.")); + if (mTransaction->GetHandle() == 0) + throw LogicExceptionImpl("Statement::Prepare", _("ITransaction must be started.")); + if (sql.empty()) + throw LogicExceptionImpl("Statement::Prepare", _("SQL statement can't be 0.")); + + // Saves the SQL sentence, only for reporting reasons in case of errors + mSql = sql; + + IBS status; + + // Free all resources currently attached to this Statement, then allocate + // a new statement descriptor. + Close(); + (*gds.Call()->m_dsql_allocate_statement)(status.Self(), mDatabase->GetHandlePtr(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Statement::Prepare", + _("isc_dsql_allocate_statement failed")); + + // Empirical estimate of parameters count and output columns count. + // This is by far not an exact estimation, which would require parsing the + // SQL statement. If the SQL statement contains '?' and ',' in string + // constants, this count will obviously be wrong, but it will be exagerated. + // It won't hurt. We just try to not have to re-allocate those descriptors later. + // So we prefer to get them a little bit larger than needed than the other way. + int16_t inEstimate = 0; + int16_t outEstimate = 1; + for (size_t i = 0; i < strlen(sql.c_str()); i++) + { + if (sql[i] == '?') ++inEstimate; + if (sql[i] == ',') ++outEstimate; + } + + /* + DebugStream()<< "Prepare(\""<< sql<< "\")"<< fds; + DebugStream()<< _("Estimation: ")<< inEstimate<< _(" IN parameters and ") + << outEstimate<< _(" OUT columns")<< fds; + */ + + // Allocates output descriptor and prepares the statement + mOutRow = new RowImpl(mDatabase->Dialect(), outEstimate, mDatabase, mTransaction); + mOutRow->AddRef(); + + status.Reset(); + (*gds.Call()->m_dsql_prepare)(status.Self(), mTransaction->GetHandlePtr(), + &mHandle, (short)sql.length(), const_cast(sql.c_str()), + short(mDatabase->Dialect()), mOutRow->Self()); + if (status.Errors()) + { + Close(); + std::string context = "Statement::Prepare( "; + context.append(mSql).append(" )"); + throw SQLExceptionImpl(status, context.c_str(), + _("isc_dsql_prepare failed")); + } + + // Read what kind of statement was prepared + status.Reset(); + char itemsReq[] = {isc_info_sql_stmt_type}; + char itemsRes[8]; + (*gds.Call()->m_dsql_sql_info)(status.Self(), &mHandle, 1, itemsReq, + sizeof(itemsRes), itemsRes); + if (status.Errors()) + { + Close(); + throw SQLExceptionImpl(status, "Statement::Prepare", + _("isc_dsql_sql_info failed")); + } + if (itemsRes[0] == isc_info_sql_stmt_type) + { + switch (itemsRes[3]) + { + case isc_info_sql_stmt_select : mType = IBPP::stSelect; break; + case isc_info_sql_stmt_insert : mType = IBPP::stInsert; break; + case isc_info_sql_stmt_update : mType = IBPP::stUpdate; break; + case isc_info_sql_stmt_delete : mType = IBPP::stDelete; break; + case isc_info_sql_stmt_ddl : mType = IBPP::stDDL; break; + case isc_info_sql_stmt_exec_procedure : mType = IBPP::stExecProcedure; break; + case isc_info_sql_stmt_select_for_upd : mType = IBPP::stSelectUpdate; break; + case isc_info_sql_stmt_set_generator : mType = IBPP::stSetGenerator; break; + case isc_info_sql_stmt_savepoint : mType = IBPP::stSavePoint; break; + default : mType = IBPP::stUnsupported; + } + } + if (mType == IBPP::stUnknown || mType == IBPP::stUnsupported) + { + Close(); + throw LogicExceptionImpl("Statement::Prepare", + _("Unknown or unsupported statement type")); + } + + if (mOutRow->Columns() == 0) + { + // Get rid of the output descriptor, if it wasn't required (no output) + mOutRow->Release(); + mOutRow = 0; + /* + DebugStream()<< _("Dropped output descriptor which was not required")<< fds; + */ + } + else if (mOutRow->Columns() > mOutRow->AllocatedSize()) + { + // Resize the output descriptor (which is too small). + // The statement does not need to be prepared again, though the + // output columns must be described again. + + /* + DebugStream()<< _("Resize output descriptor from ") + << mOutRow->AllocatedSize()<< _(" to ")<< mOutRow->Columns()<< fds; + */ + + mOutRow->Resize(mOutRow->Columns()); + status.Reset(); + (*gds.Call()->m_dsql_describe)(status.Self(), &mHandle, 1, mOutRow->Self()); + if (status.Errors()) + { + Close(); + throw SQLExceptionImpl(status, "Statement::Prepare", + _("isc_dsql_describe failed")); + } + } + + if (inEstimate > 0) + { + // Ready an input descriptor + mInRow = new RowImpl(mDatabase->Dialect(), inEstimate, mDatabase, mTransaction); + mInRow->AddRef(); + + status.Reset(); + (*gds.Call()->m_dsql_describe_bind)(status.Self(), &mHandle, 1, mInRow->Self()); + if (status.Errors()) + { + Close(); + throw SQLExceptionImpl(status, "Statement::Prepare", + _("isc_dsql_describe_bind failed")); + } + + if (mInRow->Columns() == 0) + { + // Get rid of the input descriptor, if it wasn't required (no parameters) + mInRow->Release(); + mInRow = 0; + /* + DebugStream()<< _("Dropped input descriptor which was not required")<< fds; + */ + } + else if (mInRow->Columns() > mInRow->AllocatedSize()) + { + // Resize the input descriptor (which is too small). + // The statement does not need to be prepared again, though the + // parameters must be described again. + + /* + DebugStream()<< _("Resize input descriptor from ") + << mInRow->AllocatedSize()<< _(" to ") + << mInRow->Columns()<< fds; + */ + + mInRow->Resize(mInRow->Columns()); + status.Reset(); + (*gds.Call()->m_dsql_describe_bind)(status.Self(), &mHandle, 1, mInRow->Self()); + if (status.Errors()) + { + Close(); + throw SQLExceptionImpl(status, "Statement::Prepare", + _("isc_dsql_describe_bind failed")); + } + } + } + + // Allocates variables of the input descriptor + if (mInRow != 0) + { + // Turn on 'can be NULL' on each input parameter + for (int i = 0; i < mInRow->Columns(); i++) + { + XSQLVAR* var = &(mInRow->Self()->sqlvar[i]); + if (! (var->sqltype & 1)) var->sqltype += short(1); + } + mInRow->AllocVariables(); + } + + // Allocates variables of the output descriptor + if (mOutRow != 0) mOutRow->AllocVariables(); +} + +void StatementImpl::Plan(std::string& plan) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Plan", _("No statement has been prepared.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Statement::Plan", _("A Database must be attached.")); + if (mDatabase->GetHandle() == 0) + throw LogicExceptionImpl("Statement::Plan", _("Database must be connected.")); + + IBS status; + RB result(4096); + char itemsReq[] = {isc_info_sql_get_plan}; + + (*gds.Call()->m_dsql_sql_info)(status.Self(), &mHandle, 1, itemsReq, + result.Size(), result.Self()); + if (status.Errors()) throw SQLExceptionImpl(status, + "Statement::Plan", _("isc_dsql_sql_info failed.")); + + result.GetString(isc_info_sql_get_plan, plan); + if (plan[0] == '\n') plan.erase(0, 1); +} + +void StatementImpl::Execute(const std::string& sql) +{ + if (! sql.empty()) Prepare(sql); + + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Execute", + _("No statement has been prepared.")); + + // Check that a value has been set for each input parameter + if (mInRow != 0 && mInRow->MissingValues()) + throw LogicExceptionImpl("Statement::Execute", + _("All parameters must be specified.")); + + CursorFree(); // Free a previous 'cursor' if any + + IBS status; + if (mType == IBPP::stSelect) + { + // Could return a result set (none, single or multi rows) + (*gds.Call()->m_dsql_execute)(status.Self(), mTransaction->GetHandlePtr(), + &mHandle, 1, mInRow == 0 ? 0 : mInRow->Self()); + if (status.Errors()) + { + //Close(); Commented because Execute error should not free the statement + std::string context = "Statement::Execute( "; + context.append(mSql).append(" )"); + throw SQLExceptionImpl(status, context.c_str(), + _("isc_dsql_execute failed")); + } + if (mOutRow != 0) + { + mResultSetAvailable = true; + mCursorOpened = true; + } + } + else + { + // Should return at most a single row + (*gds.Call()->m_dsql_execute2)(status.Self(), mTransaction->GetHandlePtr(), + &mHandle, 1, mInRow == 0 ? 0 : mInRow->Self(), + mOutRow == 0 ? 0 : mOutRow->Self()); + if (status.Errors()) + { + //Close(); Commented because Execute error should not free the statement + std::string context = "Statement::Execute( "; + context.append(mSql).append(" )"); + throw SQLExceptionImpl(status, context.c_str(), + _("isc_dsql_execute2 failed")); + } + } +} + +void StatementImpl::CursorExecute(const std::string& cursor, const std::string& sql) +{ + if (cursor.empty()) + throw LogicExceptionImpl("Statement::CursorExecute", _("Cursor name can't be 0.")); + + if (! sql.empty()) Prepare(sql); + + if (mHandle == 0) + throw LogicExceptionImpl("Statement::CursorExecute", _("No statement has been prepared.")); + if (mType != IBPP::stSelectUpdate) + throw LogicExceptionImpl("Statement::CursorExecute", _("Statement must be a SELECT FOR UPDATE.")); + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::CursorExecute", _("Statement would return no rows.")); + + // Check that a value has been set for each input parameter + if (mInRow != 0 && mInRow->MissingValues()) + throw LogicExceptionImpl("Statement::CursorExecute", + _("All parameters must be specified.")); + + CursorFree(); // Free a previous 'cursor' if any + + IBS status; + (*gds.Call()->m_dsql_execute)(status.Self(), mTransaction->GetHandlePtr(), + &mHandle, 1, mInRow == 0 ? 0 : mInRow->Self()); + if (status.Errors()) + { + //Close(); Commented because Execute error should not free the statement + std::string context = "Statement::CursorExecute( "; + context.append(mSql).append(" )"); + throw SQLExceptionImpl(status, context.c_str(), + _("isc_dsql_execute failed")); + } + + status.Reset(); + (*gds.Call()->m_dsql_set_cursor_name)(status.Self(), &mHandle, const_cast(cursor.c_str()), 0); + if (status.Errors()) + { + //Close(); Commented because Execute error should not free the statement + throw SQLExceptionImpl(status, "Statement::CursorExecute", + _("isc_dsql_set_cursor_name failed")); + } + + mResultSetAvailable = true; + mCursorOpened = true; +} + +void StatementImpl::ExecuteImmediate(const std::string& sql) +{ + if (mDatabase == 0) + throw LogicExceptionImpl("Statement::ExecuteImmediate", _("An IDatabase must be attached.")); + if (mDatabase->GetHandle() == 0) + throw LogicExceptionImpl("Statement::ExecuteImmediate", _("IDatabase must be connected.")); + if (mTransaction == 0) + throw LogicExceptionImpl("Statement::ExecuteImmediate", _("An ITransaction must be attached.")); + if (mTransaction->GetHandle() == 0) + throw LogicExceptionImpl("Statement::ExecuteImmediate", _("ITransaction must be started.")); + if (sql.empty()) + throw LogicExceptionImpl("Statement::ExecuteImmediate", _("SQL statement can't be 0.")); + + IBS status; + Close(); + (*gds.Call()->m_dsql_execute_immediate)(status.Self(), mDatabase->GetHandlePtr(), + mTransaction->GetHandlePtr(), 0, const_cast(sql.c_str()), + short(mDatabase->Dialect()), 0); + if (status.Errors()) + { + std::string context = "Statement::ExecuteImmediate( "; + context.append(sql).append(" )"); + throw SQLExceptionImpl(status, context.c_str(), + _("isc_dsql_execute_immediate failed")); + } +} + +int StatementImpl::AffectedRows() +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::AffectedRows", _("No statement has been prepared.")); + if (mDatabase == 0) + throw LogicExceptionImpl("Statement::AffectedRows", _("A Database must be attached.")); + if (mDatabase->GetHandle() == 0) + throw LogicExceptionImpl("Statement::AffectedRows", _("Database must be connected.")); + + int count; + IBS status; + RB result; + char itemsReq[] = {isc_info_sql_records}; + + (*gds.Call()->m_dsql_sql_info)(status.Self(), &mHandle, 1, itemsReq, + result.Size(), result.Self()); + if (status.Errors()) throw SQLExceptionImpl(status, + "Statement::AffectedRows", _("isc_dsql_sql_info failed.")); + + if (mType == IBPP::stInsert) + count = result.GetValue(isc_info_sql_records, isc_info_req_insert_count); + else if (mType == IBPP::stUpdate) + count = result.GetValue(isc_info_sql_records, isc_info_req_update_count); + else if (mType == IBPP::stDelete) + count = result.GetValue(isc_info_sql_records, isc_info_req_delete_count); + else if (mType == IBPP::stSelect) + count = result.GetValue(isc_info_sql_records, isc_info_req_select_count); + else count = 0; // Returns zero count for unknown cases + + return count; +} + +bool StatementImpl::Fetch() +{ + if (! mResultSetAvailable) + throw LogicExceptionImpl("Statement::Fetch", + _("No statement has been executed or no result set available.")); + + IBS status; + int code = (*gds.Call()->m_dsql_fetch)(status.Self(), &mHandle, 1, mOutRow->Self()); + if (code == 100) // This special code means "no more rows" + { + mResultSetAvailable = false; + // Oddly enough, fetching rows up to the last one seems to open + // an 'implicit' cursor that needs to be closed. + mCursorOpened = true; + CursorFree(); // Free the explicit or implicit cursor/result-set + return false; + } + if (status.Errors()) + { + Close(); + throw SQLExceptionImpl(status, "Statement::Fetch", + _("isc_dsql_fetch failed.")); + } + + return true; +} + +bool StatementImpl::Fetch(IBPP::Row& row) +{ + if (! mResultSetAvailable) + throw LogicExceptionImpl("Statement::Fetch(row)", + _("No statement has been executed or no result set available.")); + + RowImpl* rowimpl = new RowImpl(*mOutRow); + row = rowimpl; + + IBS status; + int code = (*gds.Call()->m_dsql_fetch)(status.Self(), &mHandle, 1, + rowimpl->Self()); + if (code == 100) // This special code means "no more rows" + { + mResultSetAvailable = false; + // Oddly enough, fetching rows up to the last one seems to open + // an 'implicit' cursor that needs to be closed. + mCursorOpened = true; + CursorFree(); // Free the explicit or implicit cursor/result-set + row.clear(); + return false; + } + if (status.Errors()) + { + Close(); + row.clear(); + throw SQLExceptionImpl(status, "Statement::Fetch(row)", + _("isc_dsql_fetch failed.")); + } + + return true; +} + +void StatementImpl::Close() +{ + // Free all statement resources. + // Used before preparing a new statement or from destructor. + + if (mInRow != 0) { mInRow->Release(); mInRow = 0; } + if (mOutRow != 0) { mOutRow->Release(); mOutRow = 0; } + + mResultSetAvailable = false; + mCursorOpened = false; + mType = IBPP::stUnknown; + + if (mHandle != 0) + { + IBS status; + (*gds.Call()->m_dsql_free_statement)(status.Self(), &mHandle, DSQL_drop); + mHandle = 0; + if (status.Errors()) + throw SQLExceptionImpl(status, "Statement::Close(DSQL_drop)", + _("isc_dsql_free_statement failed.")); + } +} + +void StatementImpl::SetNull(int param) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::SetNull", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::SetNull", _("The statement does not take parameters.")); + + mInRow->SetNull(param); +} + +void StatementImpl::Set(int param, bool value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[bool]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[bool]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, const char* cstring) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[char*]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[char*]", _("The statement does not take parameters.")); + + mInRow->Set(param, cstring); +} + +void StatementImpl::Set(int param, const void* bindata, int len) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[void*]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[void*]", _("The statement does not take parameters.")); + + mInRow->Set(param, bindata, len); +} + +void StatementImpl::Set(int param, const std::string& s) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[string]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[string]", _("The statement does not take parameters.")); + + mInRow->Set(param, s); +} + +void StatementImpl::Set(int param, int16_t value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[int16_t]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[int16_t]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, int32_t value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[int32_t]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[int32_t]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, int64_t value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[int64_t]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[int64_t]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, float value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[float]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[float]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, double value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[double]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[double]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, const IBPP::Timestamp& value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[Timestamp]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[Timestamp]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, const IBPP::Date& value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[Date]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[Date]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, const IBPP::Time& value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[Time]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[Time]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} + +void StatementImpl::Set(int param, const IBPP::Blob& blob) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[Blob]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[Blob]", _("The statement does not take parameters.")); + + mInRow->Set(param, blob); +} + +void StatementImpl::Set(int param, const IBPP::Array& array) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[Array]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[Array]", _("The statement does not take parameters.")); + + mInRow->Set(param, array); +} + +void StatementImpl::Set(int param, const IBPP::DBKey& key) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[DBKey]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[DBKey]", _("The statement does not take parameters.")); + + mInRow->Set(param, key); +} + +/* +void StatementImpl::Set(int param, const IBPP::Value& value) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Set[Value]", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Set[Value]", _("The statement does not take parameters.")); + + mInRow->Set(param, value); +} +*/ + +bool StatementImpl::IsNull(int column) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::IsNull", _("The row is not initialized.")); + + return mOutRow->IsNull(column); +} + +bool StatementImpl::Get(int column, bool* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(column, *retvalue); +} + +bool StatementImpl::Get(int column, bool& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, char* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, void* bindata, int& userlen) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, bindata, userlen); +} + +bool StatementImpl::Get(int column, std::string& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, int16_t* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(column, *retvalue); +} + +bool StatementImpl::Get(int column, int16_t& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, int32_t* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(column, *retvalue); +} + +bool StatementImpl::Get(int column, int32_t& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, int64_t* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(column, *retvalue); +} + +bool StatementImpl::Get(int column, int64_t& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, float* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(column, *retvalue); +} + +bool StatementImpl::Get(int column, float& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, double* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(column, *retvalue); +} + +bool StatementImpl::Get(int column, double& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, retvalue); +} + +bool StatementImpl::Get(int column, IBPP::Timestamp& timestamp) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, timestamp); +} + +bool StatementImpl::Get(int column, IBPP::Date& date) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, date); +} + +bool StatementImpl::Get(int column, IBPP::Time& time) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, time); +} + +bool StatementImpl::Get(int column, IBPP::Blob& blob) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, blob); +} + +bool StatementImpl::Get(int column, IBPP::DBKey& key) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, key); +} + +bool StatementImpl::Get(int column, IBPP::Array& array) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column, array); +} + +/* +const IBPP::Value StatementImpl::Get(int column) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(column); +} +*/ + +bool StatementImpl::IsNull(const std::string& name) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::IsNull", _("The row is not initialized.")); + + return mOutRow->IsNull(name); +} + +bool StatementImpl::Get(const std::string& name, bool* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(name, *retvalue); +} + +bool StatementImpl::Get(const std::string& name, bool& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, char* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get[char*]", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, void* retvalue, int& count) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get[void*,int]", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue, count); +} + +bool StatementImpl::Get(const std::string& name, std::string& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::GetString", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, int16_t* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(name, *retvalue); +} + +bool StatementImpl::Get(const std::string& name, int16_t& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, int32_t* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(name, *retvalue); +} + +bool StatementImpl::Get(const std::string& name, int32_t& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, int64_t* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(name, *retvalue); +} + +bool StatementImpl::Get(const std::string& name, int64_t& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, float* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(name, *retvalue); +} + +bool StatementImpl::Get(const std::string& name, float& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, double* retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + if (retvalue == 0) + throw LogicExceptionImpl("Statement::Get", _("Null pointer detected")); + + return mOutRow->Get(name, *retvalue); +} + +bool StatementImpl::Get(const std::string& name, double& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, IBPP::Timestamp& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, IBPP::Date& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, IBPP::Time& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string&name, IBPP::Blob& retblob) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retblob); +} + +bool StatementImpl::Get(const std::string& name, IBPP::DBKey& retvalue) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retvalue); +} + +bool StatementImpl::Get(const std::string& name, IBPP::Array& retarray) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name, retarray); +} + +/* +const IBPP::Value StatementImpl::Get(const std::string& name) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Get", _("The row is not initialized.")); + + return mOutRow->Get(name); +} +*/ + +int StatementImpl::Columns() +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Columns", _("The row is not initialized.")); + + return mOutRow->Columns(); +} + +int StatementImpl::ColumnNum(const std::string& name) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::ColumnNum", _("The row is not initialized.")); + + return mOutRow->ColumnNum(name); +} + +const char* StatementImpl::ColumnName(int varnum) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Columns", _("The row is not initialized.")); + + return mOutRow->ColumnName(varnum); +} + +const char* StatementImpl::ColumnAlias(int varnum) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Columns", _("The row is not initialized.")); + + return mOutRow->ColumnAlias(varnum); +} + +const char* StatementImpl::ColumnTable(int varnum) +{ + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::Columns", _("The row is not initialized.")); + + return mOutRow->ColumnTable(varnum); +} + +IBPP::SDT StatementImpl::ColumnType(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ColumnType", _("No statement has been prepared.")); + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::ColumnType", _("The statement does not return results.")); + + return mOutRow->ColumnType(varnum); +} + +int StatementImpl::ColumnSubtype(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ColumnSubtype", _("No statement has been prepared.")); + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::ColumnSubtype", _("The statement does not return results.")); + + return mOutRow->ColumnSubtype(varnum); +} + +int StatementImpl::ColumnSize(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ColumnSize", _("No statement has been prepared.")); + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::ColumnSize", _("The row is not initialized.")); + + return mOutRow->ColumnSize(varnum); +} + +int StatementImpl::ColumnScale(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ColumnScale", _("No statement has been prepared.")); + if (mOutRow == 0) + throw LogicExceptionImpl("Statement::ColumnScale", _("The row is not initialized.")); + + return mOutRow->ColumnScale(varnum); +} + +int StatementImpl::Parameters() +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::Parameters", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::Parameters", _("The statement uses no parameters.")); + + return mInRow->Columns(); +} + +IBPP::SDT StatementImpl::ParameterType(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ParameterType", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::ParameterType", _("The statement uses no parameters.")); + + return mInRow->ColumnType(varnum); +} + +int StatementImpl::ParameterSubtype(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ParameterSubtype", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::ParameterSubtype", _("The statement uses no parameters.")); + + return mInRow->ColumnSubtype(varnum); +} + +int StatementImpl::ParameterSize(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ParameterSize", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::ParameterSize", _("The statement uses no parameters.")); + + return mInRow->ColumnSize(varnum); +} + +int StatementImpl::ParameterScale(int varnum) +{ + if (mHandle == 0) + throw LogicExceptionImpl("Statement::ParameterScale", _("No statement has been prepared.")); + if (mInRow == 0) + throw LogicExceptionImpl("Statement::ParameterScale", _("The statement uses no parameters.")); + + return mInRow->ColumnScale(varnum); +} + +IBPP::Database StatementImpl::DatabasePtr() const +{ + return mDatabase; +} + +IBPP::Transaction StatementImpl::TransactionPtr() const +{ + return mTransaction; +} + +IBPP::IStatement* StatementImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + + return this; +} + +void StatementImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void StatementImpl::AttachDatabaseImpl(DatabaseImpl* database) +{ + if (database == 0) + throw LogicExceptionImpl("Statement::AttachDatabase", + _("Can't attach a 0 IDatabase object.")); + + if (mDatabase != 0) mDatabase->DetachStatementImpl(this); + mDatabase = database; + mDatabase->AttachStatementImpl(this); +} + +void StatementImpl::DetachDatabaseImpl() +{ + if (mDatabase == 0) return; + + Close(); + mDatabase->DetachStatementImpl(this); + mDatabase = 0; +} + +void StatementImpl::AttachTransactionImpl(TransactionImpl* transaction) +{ + if (transaction == 0) + throw LogicExceptionImpl("Statement::AttachTransaction", + _("Can't attach a 0 ITransaction object.")); + + if (mTransaction != 0) mTransaction->DetachStatementImpl(this); + mTransaction = transaction; + mTransaction->AttachStatementImpl(this); +} + +void StatementImpl::DetachTransactionImpl() +{ + if (mTransaction == 0) return; + + Close(); + mTransaction->DetachStatementImpl(this); + mTransaction = 0; +} + +void StatementImpl::CursorFree() +{ + if (mCursorOpened) + { + mCursorOpened = false; + if (mHandle != 0) + { + IBS status; + (*gds.Call()->m_dsql_free_statement)(status.Self(), &mHandle, DSQL_close); + if (status.Errors()) + throw SQLExceptionImpl(status, "StatementImpl::CursorFree(DSQL_close)", + _("isc_dsql_free_statement failed.")); + } + } +} + +StatementImpl::StatementImpl(DatabaseImpl* database, TransactionImpl* transaction, + const std::string& sql) + : mRefCount(0), mHandle(0), mDatabase(0), mTransaction(0), + mInRow(0), mOutRow(0), + mResultSetAvailable(false), mCursorOpened(false), mType(IBPP::stUnknown) +{ + AttachDatabaseImpl(database); + if (transaction != 0) AttachTransactionImpl(transaction); + if (! sql.empty()) Prepare(sql); +} + +StatementImpl::~StatementImpl() +{ + try { Close(); } + catch (...) { } + try { if (mTransaction != 0) mTransaction->DetachStatementImpl(this); } + catch (...) { } + try { if (mDatabase != 0) mDatabase->DetachStatementImpl(this); } + catch (...) { } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/time.cpp b/stglibs/ibpp.lib/time.cpp new file mode 100644 index 00000000..ffdd5f6a --- /dev/null +++ b/stglibs/ibpp.lib/time.cpp @@ -0,0 +1,208 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: time.cpp,v 1.1 2007/05/05 17:00:43 faust Exp $ +// Subject : IBPP, Time class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include // Can't use thanks to MSVC6 buggy library + +using namespace ibpp_internals; + +void IBPP::Time::Now() +{ + time_t systime = time(0); + tm* loctime = localtime(&systime); + IBPP::itot(&mTime, loctime->tm_hour, loctime->tm_min, loctime->tm_sec, 0); +} + +void IBPP::Time::SetTime(int tm) +{ + if (tm < 0 || tm > 863999999) + throw LogicExceptionImpl("Time::SetTime", _("Invalid time value")); + mTime = tm; +} + +void IBPP::Time::SetTime(int hour, int minute, int second, int tenthousandths) +{ + if (hour < 0 || hour > 23 || + minute < 0 || minute > 59 || + second < 0 || second > 59 || + tenthousandths < 0 || tenthousandths > 9999) + throw LogicExceptionImpl("Time::SetTime", + _("Invalid hour, minute, second values")); + IBPP::itot(&mTime, hour, minute, second, tenthousandths); +} + +void IBPP::Time::GetTime(int& hour, int& minute, int& second) const +{ + IBPP::ttoi(mTime, &hour, &minute, &second, 0); +} + +void IBPP::Time::GetTime(int& hour, int& minute, int& second, int& tenthousandths) const +{ + IBPP::ttoi(mTime, &hour, &minute, &second, &tenthousandths); +} + +int IBPP::Time::Hours() const +{ + int hours; + IBPP::ttoi(mTime, &hours, 0, 0, 0); + return hours; +} + +int IBPP::Time::Minutes() const +{ + int minutes; + IBPP::ttoi(mTime, 0, &minutes, 0, 0); + return minutes; +} + +int IBPP::Time::Seconds() const +{ + int seconds; + IBPP::ttoi(mTime, 0, 0, &seconds, 0); + return seconds; +} + +int IBPP::Time::SubSeconds() const // Actually tenthousandths of seconds +{ + int tenthousandths; + IBPP::ttoi(mTime, 0, 0, 0, &tenthousandths); + return tenthousandths; +} + +IBPP::Time::Time(int hour, int minute, int second, int tenthousandths) +{ + SetTime(hour, minute, second, tenthousandths); +} + +IBPP::Time::Time(const IBPP::Time& copied) +{ + mTime = copied.mTime; +} + +IBPP::Time& IBPP::Time::operator=(const IBPP::Timestamp& assigned) +{ + mTime = assigned.GetTime(); + return *this; +} + +IBPP::Time& IBPP::Time::operator=(const IBPP::Time& assigned) +{ + mTime = assigned.mTime; + return *this; +} + +// Time calculations. Internal format is the number of seconds elapsed since +// midnight. Splits such a time in its hours, minutes, seconds components. + +void IBPP::ttoi(int itime, int *h, int *m, int *s, int* t) +{ + int hh, mm, ss, tt; + + hh = (int) (itime / 36000000); itime = itime - hh * 36000000; + mm = (int) (itime / 600000); itime = itime - mm * 600000; + ss = (int) (itime / 10000); + tt = (int) (itime - ss * 10000); + + if (h != 0) *h = hh; + if (m != 0) *m = mm; + if (s != 0) *s = ss; + if (t != 0) *t = tt; + + return; +} + +// Get the internal time format, given hour, minute, second. + +void IBPP::itot (int *ptime, int hour, int minute, int second, int tenthousandths) +{ + *ptime = hour * 36000000 + minute * 600000 + second * 10000 + tenthousandths; + return; +} + +namespace ibpp_internals +{ + +// +// The following functions are helper conversions functions between IBPP +// Date, Time, Timestamp and ISC_DATE, ISC_TIME and ISC_TIMESTAMP. +// (They must be maintained if the encoding used by Firebird evolve.) +// These helper functions are used from row.cpp and from array.cpp. +// + +void encodeDate(ISC_DATE& isc_dt, const IBPP::Date& dt) +{ + // There simply has a shift of 15019 between the native Firebird + // date model and the IBPP model. + isc_dt = (ISC_DATE)(dt.GetDate() + 15019); +} + +void decodeDate(IBPP::Date& dt, const ISC_DATE& isc_dt) +{ + // There simply has a shift of 15019 between the native Firebird + // date model and the IBPP model. + dt.SetDate((int)isc_dt - 15019); +} + +void encodeTime(ISC_TIME& isc_tm, const IBPP::Time& tm) +{ + isc_tm = (ISC_TIME)tm.GetTime(); +} + +void decodeTime(IBPP::Time& tm, const ISC_TIME& isc_tm) +{ + tm.SetTime((int)isc_tm); +} + +void encodeTimestamp(ISC_TIMESTAMP& isc_ts, const IBPP::Timestamp& ts) +{ + encodeDate(isc_ts.timestamp_date, ts); + encodeTime(isc_ts.timestamp_time, ts); +} + +void decodeTimestamp(IBPP::Timestamp& ts, const ISC_TIMESTAMP& isc_ts) +{ + decodeDate(ts, isc_ts.timestamp_date); + decodeTime(ts, isc_ts.timestamp_time); +} + +} + +// +// EOF +// + diff --git a/stglibs/ibpp.lib/transaction.cpp b/stglibs/ibpp.lib/transaction.cpp new file mode 100644 index 00000000..c9a38774 --- /dev/null +++ b/stglibs/ibpp.lib/transaction.cpp @@ -0,0 +1,409 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: transaction.cpp,v 1.1 2007/05/05 17:00:43 faust Exp $ +// Subject : IBPP, Database class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include + +using namespace ibpp_internals; + +// (((((((( OBJECT INTERFACE IMPLEMENTATION )))))))) + +void TransactionImpl::AttachDatabase(IBPP::Database db, + IBPP::TAM am, IBPP::TIL il, IBPP::TLR lr, IBPP::TFF flags) +{ + if (db.intf() == 0) + throw LogicExceptionImpl("Transaction::AttachDatabase", + _("Can't attach an unbound Database.")); + + AttachDatabaseImpl(dynamic_cast(db.intf()), am, il, lr, flags); +} + +void TransactionImpl::DetachDatabase(IBPP::Database db) +{ + if (db.intf() == 0) + throw LogicExceptionImpl("Transaction::DetachDatabase", + _("Can't detach an unbound Database.")); + + DetachDatabaseImpl(dynamic_cast(db.intf())); +} + +void TransactionImpl::AddReservation(IBPP::Database db, + const std::string& table, IBPP::TTR tr) +{ + if (mHandle != 0) + throw LogicExceptionImpl("Transaction::AddReservation", + _("Can't add table reservation if Transaction started.")); + if (db.intf() == 0) + throw LogicExceptionImpl("Transaction::AddReservation", + _("Can't add table reservation on an unbound Database.")); + + // Find the TPB associated with this database + std::vector::iterator pos = + std::find(mDatabases.begin(), mDatabases.end(), dynamic_cast(db.intf())); + if (pos != mDatabases.end()) + { + size_t index = pos - mDatabases.begin(); + TPB* tpb = mTPBs[index]; + + // Now add the reservations to the TPB + switch (tr) + { + case IBPP::trSharedWrite : + tpb->Insert(isc_tpb_lock_write); + tpb->Insert(table); + tpb->Insert(isc_tpb_shared); + break; + case IBPP::trSharedRead : + tpb->Insert(isc_tpb_lock_read); + tpb->Insert(table); + tpb->Insert(isc_tpb_shared); + break; + case IBPP::trProtectedWrite : + tpb->Insert(isc_tpb_lock_write); + tpb->Insert(table); + tpb->Insert(isc_tpb_protected); + break; + case IBPP::trProtectedRead : + tpb->Insert(isc_tpb_lock_read); + tpb->Insert(table); + tpb->Insert(isc_tpb_protected); + break; + default : + throw LogicExceptionImpl("Transaction::AddReservation", + _("Illegal TTR value detected.")); + } + } + else throw LogicExceptionImpl("Transaction::AddReservation", + _("The database connection you specified is not attached to this transaction.")); +} + +void TransactionImpl::Start() +{ + if (mHandle != 0) return; // Already started anyway + + if (mDatabases.empty()) + throw LogicExceptionImpl("Transaction::Start", _("No Database is attached.")); + + struct ISC_TEB + { + ISC_LONG* db_ptr; + ISC_LONG tpb_len; + char* tpb_ptr; + } * teb = new ISC_TEB[mDatabases.size()]; + + unsigned i; + for (i = 0; i < mDatabases.size(); i++) + { + if (mDatabases[i]->GetHandle() == 0) + { + // All Databases must be connected to Start the transaction ! + delete [] teb; + throw LogicExceptionImpl("Transaction::Start", + _("All attached Database should have been connected.")); + } + teb[i].db_ptr = (ISC_LONG*) mDatabases[i]->GetHandlePtr(); + teb[i].tpb_len = mTPBs[i]->Size(); + teb[i].tpb_ptr = mTPBs[i]->Self(); + } + + IBS status; + (*gds.Call()->m_start_multiple)(status.Self(), &mHandle, (short)mDatabases.size(), teb); + delete [] teb; + if (status.Errors()) + { + mHandle = 0; // Should be, but better be sure... + throw SQLExceptionImpl(status, "Transaction::Start"); + } +} + +void TransactionImpl::Commit() +{ + if (mHandle == 0) + throw LogicExceptionImpl("Transaction::Commit", _("Transaction is not started.")); + + IBS status; + + (*gds.Call()->m_commit_transaction)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Transaction::Commit"); + mHandle = 0; // Should be, better be sure +} + +void TransactionImpl::CommitRetain() +{ + if (mHandle == 0) + throw LogicExceptionImpl("Transaction::CommitRetain", _("Transaction is not started.")); + + IBS status; + + (*gds.Call()->m_commit_retaining)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Transaction::CommitRetain"); +} + +void TransactionImpl::Rollback() +{ + if (mHandle == 0) return; // Transaction not started anyway + + IBS status; + + (*gds.Call()->m_rollback_transaction)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Transaction::Rollback"); + mHandle = 0; // Should be, better be sure +} + +void TransactionImpl::RollbackRetain() +{ + if (mHandle == 0) + throw LogicExceptionImpl("Transaction::RollbackRetain", _("Transaction is not started.")); + + IBS status; + + (*gds.Call()->m_rollback_retaining)(status.Self(), &mHandle); + if (status.Errors()) + throw SQLExceptionImpl(status, "Transaction::RollbackRetain"); +} + +IBPP::ITransaction* TransactionImpl::AddRef() +{ + ASSERTION(mRefCount >= 0); + ++mRefCount; + return this; +} + +void TransactionImpl::Release() +{ + // Release cannot throw, except in DEBUG builds on assertion + ASSERTION(mRefCount >= 0); + --mRefCount; + try { if (mRefCount <= 0) delete this; } + catch (...) { } +} + +// (((((((( OBJECT INTERNAL METHODS )))))))) + +void TransactionImpl::Init() +{ + mHandle = 0; + mDatabases.clear(); + mTPBs.clear(); + mStatements.clear(); + mBlobs.clear(); + mArrays.clear(); +} + +void TransactionImpl::AttachStatementImpl(StatementImpl* st) +{ + if (st == 0) + throw LogicExceptionImpl("Transaction::AttachStatement", + _("Can't attach a 0 Statement object.")); + + mStatements.push_back(st); +} + +void TransactionImpl::DetachStatementImpl(StatementImpl* st) +{ + if (st == 0) + throw LogicExceptionImpl("Transaction::DetachStatement", + _("Can't detach a 0 Statement object.")); + + mStatements.erase(std::find(mStatements.begin(), mStatements.end(), st)); +} + +void TransactionImpl::AttachBlobImpl(BlobImpl* bb) +{ + if (bb == 0) + throw LogicExceptionImpl("Transaction::AttachBlob", + _("Can't attach a 0 BlobImpl object.")); + + mBlobs.push_back(bb); +} + +void TransactionImpl::DetachBlobImpl(BlobImpl* bb) +{ + if (bb == 0) + throw LogicExceptionImpl("Transaction::DetachBlob", + _("Can't detach a 0 BlobImpl object.")); + + mBlobs.erase(std::find(mBlobs.begin(), mBlobs.end(), bb)); +} + +void TransactionImpl::AttachArrayImpl(ArrayImpl* ar) +{ + if (ar == 0) + throw LogicExceptionImpl("Transaction::AttachArray", + _("Can't attach a 0 ArrayImpl object.")); + + mArrays.push_back(ar); +} + +void TransactionImpl::DetachArrayImpl(ArrayImpl* ar) +{ + if (ar == 0) + throw LogicExceptionImpl("Transaction::DetachArray", + _("Can't detach a 0 ArrayImpl object.")); + + mArrays.erase(std::find(mArrays.begin(), mArrays.end(), ar)); +} + +void TransactionImpl::AttachDatabaseImpl(DatabaseImpl* dbi, + IBPP::TAM am, IBPP::TIL il, IBPP::TLR lr, IBPP::TFF flags) +{ + if (mHandle != 0) + throw LogicExceptionImpl("Transaction::AttachDatabase", + _("Can't attach a Database if Transaction started.")); + if (dbi == 0) + throw LogicExceptionImpl("Transaction::AttachDatabase", + _("Can't attach a null Database.")); + + mDatabases.push_back(dbi); + + // Prepare a new TPB + TPB* tpb = new TPB; + if (am == IBPP::amRead) tpb->Insert(isc_tpb_read); + else tpb->Insert(isc_tpb_write); + + switch (il) + { + case IBPP::ilConsistency : tpb->Insert(isc_tpb_consistency); break; + case IBPP::ilReadDirty : tpb->Insert(isc_tpb_read_committed); + tpb->Insert(isc_tpb_rec_version); break; + case IBPP::ilReadCommitted : tpb->Insert(isc_tpb_read_committed); + tpb->Insert(isc_tpb_no_rec_version); break; + default : tpb->Insert(isc_tpb_concurrency); break; + } + + if (lr == IBPP::lrNoWait) tpb->Insert(isc_tpb_nowait); + else tpb->Insert(isc_tpb_wait); + + if (flags & IBPP::tfIgnoreLimbo) tpb->Insert(isc_tpb_ignore_limbo); + if (flags & IBPP::tfAutoCommit) tpb->Insert(isc_tpb_autocommit); + if (flags & IBPP::tfNoAutoUndo) tpb->Insert(isc_tpb_no_auto_undo); + + mTPBs.push_back(tpb); + + // Signals the Database object that it has been attached to the Transaction + dbi->AttachTransactionImpl(this); +} + +void TransactionImpl::DetachDatabaseImpl(DatabaseImpl* dbi) +{ + if (mHandle != 0) + throw LogicExceptionImpl("Transaction::DetachDatabase", + _("Can't detach a Database if Transaction started.")); + if (dbi == 0) + throw LogicExceptionImpl("Transaction::DetachDatabase", + _("Can't detach a null Database.")); + + std::vector::iterator pos = + std::find(mDatabases.begin(), mDatabases.end(), dbi); + if (pos != mDatabases.end()) + { + size_t index = pos - mDatabases.begin(); + TPB* tpb = mTPBs[index]; + mDatabases.erase(pos); + mTPBs.erase(mTPBs.begin()+index); + delete tpb; + } + + // Signals the Database object that it has been detached from the Transaction + dbi->DetachTransactionImpl(this); +} + +TransactionImpl::TransactionImpl(DatabaseImpl* db, + IBPP::TAM am, IBPP::TIL il, IBPP::TLR lr, IBPP::TFF flags) + : mRefCount(0) +{ + Init(); + AttachDatabaseImpl(db, am, il, lr, flags); +} + +TransactionImpl::~TransactionImpl() +{ + // Rollback the transaction if it was Started + try { if (Started()) Rollback(); } + catch (...) { } + + // Let's detach cleanly all Blobs from this Transaction. + // No Blob object can still maintain pointers to this + // Transaction which is disappearing. + // + // We use a reverse traversal of the array to avoid loops. + // The array shrinks on each loop (mBbCount decreases). + // And during the deletion, there is a packing of the array through a + // copy of elements from the end to the beginning of the array. + try { + while (mBlobs.size() > 0) + mBlobs.back()->DetachTransactionImpl(); + } catch (...) { } + + // Let's detach cleanly all Arrays from this Transaction. + // No Array object can still maintain pointers to this + // Transaction which is disappearing. + try { + while (mArrays.size() > 0) + mArrays.back()->DetachTransactionImpl(); + } catch (...) { } + + // Let's detach cleanly all Statements from this Transaction. + // No Statement object can still maintain pointers to this + // Transaction which is disappearing. + try { + while (mStatements.size() > 0) + mStatements.back()->DetachTransactionImpl(); + } catch (...) { } + + // Very important : let's detach cleanly all Databases from this + // Transaction. No Database object can still maintain pointers to this + // Transaction which is disappearing. + try { + while (mDatabases.size() > 0) + { + size_t i = mDatabases.size()-1; + DetachDatabaseImpl(mDatabases[i]); // <-- remove link to database from mTPBs + // array and destroy TPB object + // Fixed : Maxim Abrashkin on 12 Jun 2002 + //mDatabases.back()->DetachTransaction(this); + } + } catch (...) { } +} + +// +// EOF +// diff --git a/stglibs/ibpp.lib/user.cpp b/stglibs/ibpp.lib/user.cpp new file mode 100644 index 00000000..e9cc1b48 --- /dev/null +++ b/stglibs/ibpp.lib/user.cpp @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// File : $Id: user.cpp,v 1.1 2007/05/05 17:00:43 faust Exp $ +// Subject : IBPP, User class implementation +// +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org) +// +// The contents of this file are subject to the IBPP License (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at http://www.ibpp.org or in the 'license.txt' +// file which must have been distributed along with this file. +// +// This software, distributed under the License, is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations +// under the License. +// +/////////////////////////////////////////////////////////////////////////////// +// +// COMMENTS +// * Tabulations should be set every four characters when editing this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#pragma warning(disable: 4786 4996) +#ifndef _DEBUG +#pragma warning(disable: 4702) +#endif +#endif + +#include "_ibpp.h" + +#ifdef HAS_HDRSTOP +#pragma hdrstop +#endif + +#include +#include +#include + +using namespace ibpp_internals; + +// Private implementation + +void IBPP::User::copyfrom(const IBPP::User& r) +{ + username = r.username; + password = r.password; + firstname = r.firstname; + middlename = r.middlename; + lastname = r.lastname; + userid = r.userid; + groupid = r.groupid; +} + +// Public implementation + +void IBPP::User::clear() +{ + username.erase(); password.erase(); + firstname.erase(); middlename.erase(); lastname.erase(); + userid = groupid = 0; +} + +// +// EOF +// diff --git a/stglibs/pinger.lib/Makefile b/stglibs/pinger.lib/Makefile new file mode 100644 index 00000000..5f368b64 --- /dev/null +++ b/stglibs/pinger.lib/Makefile @@ -0,0 +1,18 @@ +############################################################################### +# $Id: Makefile,v 1.5 2008/12/04 17:13:14 faust Exp $ +############################################################################### + +LIB_NAME = stg_pinger +PROG = lib$(LIB_NAME) + +SRCS = pinger.cpp + +INCS = pinger.h + +LIBS = $(LIB_THREAD) + +include ../Makefile.in + +test: all + g++ -c test.cpp + g++ -o test test.o ./libstg_pinger.a -lpthread diff --git a/stglibs/pinger.lib/pinger.cpp b/stglibs/pinger.lib/pinger.cpp new file mode 100644 index 00000000..9169b4e6 --- /dev/null +++ b/stglibs/pinger.lib/pinger.cpp @@ -0,0 +1,388 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pinger.h" +#include "common.h" +#include "stg_locker.h" + +#ifdef STG_TIME +extern volatile time_t stgTime; +#endif + +//----------------------------------------------------------------------------- +STG_PINGER::STG_PINGER(time_t d) +{ + delay = d; + pthread_mutex_init(&mutex, NULL); + pid = 0; +} +//----------------------------------------------------------------------------- +STG_PINGER::~STG_PINGER() +{ + pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +int STG_PINGER::Start() +{ + struct protoent *proto = NULL; + proto = getprotobyname("ICMP"); + sendSocket = socket(PF_INET, SOCK_RAW, proto->p_proto); + recvSocket = socket(PF_INET, SOCK_RAW, proto->p_proto); + nonstop = true; + pid = (int) getpid() % 65535; + if (sendSocket < 0 || recvSocket < 0) + { + errorStr = "Cannot create socket."; + return -1; + } + + if (pthread_create(&sendThread, NULL, RunSendPing, this)) + { + errorStr = "Cannot create send thread."; + return -1; + } + + if (pthread_create(&recvThread, NULL, RunRecvPing, this)) + { + errorStr = "Cannot create recv thread."; + return -1; + } + + return 0; +} +//----------------------------------------------------------------------------- +int STG_PINGER::Stop() +{ + close(recvSocket); + nonstop = false; + if (isRunningRecver) + { + //5 seconds to thread stops itself + int i; + for (i = 0; i < 25; i++) + { + if (i % 5 == 0) + SendPing(0x0100007f);//127.0.0.1 + + if (!isRunningRecver) + break; + + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (isRunningRecver) + { + //if (pthread_kill(recvThread, SIGINT)) + // { + errorStr = "Cannot kill thread."; + return -1; + // } + //printf("recvThread killed\n"); + } + } + + if (isRunningSender) + { + //5 seconds to thread stops itself + int i; + for (i = 0; i < 25; i++) + { + if (!isRunningSender) + break; + + usleep(200000); + } + + //after 5 seconds waiting thread still running. now killing it + if (isRunningSender) + { + //if (pthread_kill(sendThread, SIGINT)) + // { + errorStr = "Cannot kill thread."; + return -1; + // } + //printf("sendThread killed\n"); + } + } + + close(sendSocket); + return 0; +} +//----------------------------------------------------------------------------- +void STG_PINGER::AddIP(uint32_t ip) +{ + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + //printf("AddIP\n"); + ipToAdd.push_back(ip); +} +//----------------------------------------------------------------------------- +void STG_PINGER::DelIP(uint32_t ip) +{ + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + //printf("DelIP\n"); + ipToDel.push_back(ip); +} +//----------------------------------------------------------------------------- +void STG_PINGER::RealAddIP() + { + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + + list::iterator iter; + iter = ipToAdd.begin(); + while (iter != ipToAdd.end()) + { + /*packets.insert(pair(rawPacket, ed));*/ + //pingIP[*iter] = 0; + pingIP.insert(pair(*iter, 0)); + iter++; + } + ipToAdd.erase(ipToAdd.begin(), ipToAdd.end()); + } +//----------------------------------------------------------------------------- +void STG_PINGER::RealDelIP() +{ + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + + list::iterator iter; + multimap::iterator treeIter; + iter = ipToDel.begin(); + while (iter != ipToDel.end()) + { + treeIter = pingIP.find(*iter); + //printf("Found %X\n", *iter); + if (treeIter != pingIP.end()) + pingIP.erase(treeIter); + + iter++; + } + ipToDel.erase(ipToDel.begin(), ipToDel.end()); +} +//----------------------------------------------------------------------------- +int STG_PINGER::GetPingIPNum() +{ + return pingIP.size(); +} +//----------------------------------------------------------------------------- +void STG_PINGER::GetAllIP(vector *) +{ + //STG_LOCKER lock(&mutex, __FILE__, __LINE__); +} +//----------------------------------------------------------------------------- +void STG_PINGER::PrintAllIP() +{ + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + multimap::iterator iter; + iter = pingIP.begin(); + while (iter != pingIP.end()) + { + uint32_t ip = iter->first; + time_t t = iter->second; + string s; + x2str(t, s); + printf("ip = %s, time = %9s\n", inet_ntostring(ip).c_str(), s.c_str()); + iter++; + } + +} +//----------------------------------------------------------------------------- +int STG_PINGER::GetIPTime(uint32_t ip, time_t * t) +{ + STG_LOCKER lock(&mutex, __FILE__, __LINE__); + multimap::iterator treeIter; + + treeIter = pingIP.find(ip); + if (treeIter == pingIP.end()) + return -1; + + *t = treeIter->second; + return 0; +} +//----------------------------------------------------------------------------- +void STG_PINGER::SetDelayTime(time_t d) +{ + delay = d; +} +//----------------------------------------------------------------------------- +time_t STG_PINGER::GetDelayTime() +{ + return delay; +} +//----------------------------------------------------------------------------- +string STG_PINGER::GetStrError() +{ + return errorStr; +} +//----------------------------------------------------------------------------- +uint16_t STG_PINGER::PingCheckSum(void * data, int len) +{ + unsigned short * buf = (unsigned short *)data; + unsigned int sum = 0; + unsigned short result; + + for ( sum = 0; len > 1; len -= 2 ) + sum += *buf++; + + if ( len == 1 ) + sum += *(unsigned char*)buf; + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + result = ~sum; + return result; +} +//----------------------------------------------------------------------------- +int STG_PINGER::SendPing(uint32_t ip) +{ + struct sockaddr_in addr; + //printf("SendPing %X \n", ip); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = ip; + + memset(&pmSend, 0, sizeof(pmSend)); + pmSend.hdr.type = ICMP_ECHO; + pmSend.hdr.un.echo.id = pid; + memcpy(pmSend.msg, &ip, sizeof(ip)); + + pmSend.hdr.checksum = PingCheckSum(&pmSend, sizeof(pmSend)); + + if (sendto(sendSocket, &pmSend, sizeof(pmSend), 0, (sockaddr *)&addr, sizeof(addr)) <= 0 ) + { + errorStr = "Send ping error: " + string(strerror(errno)); + return -1; + } + + + return 0; +} +//----------------------------------------------------------------------------- +uint32_t STG_PINGER::RecvPing() +{ + struct sockaddr_in addr; + uint32_t ipAddr = 0; + + char buf[128]; + memset(buf, 0, sizeof(buf)); + int bytes; + socklen_t len = sizeof(addr); + + bytes = recvfrom(recvSocket, &buf, sizeof(buf), 0, (struct sockaddr*)&addr, &len); + //printf("recvfrom\n"); + if (bytes > 0) + { + struct IP_HDR * ip = (struct IP_HDR *)buf; + struct ICMP_HDR *icmp = (struct ICMP_HDR *)(buf + ip->ihl * 4); + + //printf("icmp->un.echo.id=%d, pid=%d, tid: %d\n", icmp->un.echo.id, pid); + if (icmp->un.echo.id != pid) + return 0; + + ipAddr = *(uint32_t*)(buf + sizeof(ICMP_HDR) + ip->ihl * 4); + } + + return ipAddr; +} +//----------------------------------------------------------------------------- +void * STG_PINGER::RunSendPing(void * d) +{ + STG_PINGER * pinger = (STG_PINGER*)d; + + pinger->isRunningSender = true; + time_t lastPing = 0; + while (pinger->nonstop) + { + pinger->RealAddIP(); + pinger->RealDelIP(); + + multimap::iterator iter; + iter = pinger->pingIP.begin(); + while (iter != pinger->pingIP.end()) + { + uint32_t ip = iter->first; + pinger->SendPing(ip); + iter++; + } + + time_t currTime; + + #ifdef STG_TIME + lastPing = stgTime; + currTime = stgTime; + #else + currTime = lastPing = time(NULL); + #endif + + while (currTime - lastPing < pinger->delay && pinger->nonstop) + { + #ifdef STG_TIME + currTime = stgTime; + #else + currTime = time(NULL); + #endif + usleep(20000); + } + //printf("new ping cycle\n"); + } + + pinger->isRunningSender = false; + + return NULL; +} +//----------------------------------------------------------------------------- +void * STG_PINGER::RunRecvPing(void * d) +{ + STG_PINGER * pinger = (STG_PINGER*)d; + + pinger->isRunningRecver = true; + + uint32_t ip; + multimap::iterator treeIterLower; + multimap::iterator treeIterUpper; + + while (pinger->nonstop) + { + ip = pinger->RecvPing(); + + if (ip) + { + //printf("RecvPing %X\n", ip); + treeIterUpper = pinger->pingIP.upper_bound(ip); + treeIterLower = pinger->pingIP.lower_bound(ip); + int i = 0; + while (treeIterUpper != treeIterLower) + //treeIterUpper = pinger->pingIP.find(ip); + //if (treeIterUpper != pinger->pingIP.end()) + { + //printf("+++! time=%d %X i=%d !+++\n", time(NULL), ip, i); + //printf("--- time=%d ---\n", time(NULL)); + #ifdef STG_TIME + treeIterLower->second = stgTime; + #else + treeIterLower->second = time(NULL); + #endif + ++treeIterLower; + i++; + } + } + + } + pinger->isRunningRecver = false; + return NULL; +} +//----------------------------------------------------------------------------- + diff --git a/stglibs/pinger.lib/pinger.h b/stglibs/pinger.lib/pinger.h new file mode 100644 index 00000000..4349eb07 --- /dev/null +++ b/stglibs/pinger.lib/pinger.h @@ -0,0 +1,139 @@ + /* + $Revision: 1.8 $ + $Date: 2008/05/10 11:59:53 $ + $Author: nobunaga $ + */ + +#ifndef PINGER_H +#define PINGER_H + +#include +#include +#include +#include +#include + +#ifdef LINUX +#include +#include +#include +#endif + +#if defined (FREE_BSD) || defined (FREE_BSD5) +#include +#include +#include +#include +#include +#include +#endif + +#include "os_int.h" + +using namespace std; + +//----------------------------------------------------------------------------- +struct ICMP_HDR +{ +uint8_t type; +uint8_t code; +uint16_t checksum; +union + { + struct + { + uint16_t id; + uint16_t sequence; + } echo; + uint32_t gateway; + struct + { + uint16_t unused; + uint16_t mtu; + } frag; + } un; +}; +//----------------------------------------------------------------------------- +struct IP_HDR +{ + uint8_t ihl:4, + version:4; + uint8_t tos; + uint16_t tot_len; + uint16_t id; + uint16_t frag_off; + uint8_t ttl; + uint8_t protocol; + uint16_t check; + uint32_t saddr; + uint32_t daddr; +}; +//----------------------------------------------------------------------------- +struct PING_IP_TIME +{ +uint32_t ip; +time_t pingTime; +}; +//----------------------------------------------------------------------------- + +#define PING_DATA_LEN (64) +//----------------------------------------------------------------------------- +struct PING_MESSAGE +{ + ICMP_HDR hdr; + char msg[PING_DATA_LEN]; +}; +//----------------------------------------------------------------------------- +class STG_PINGER +{ +public: + STG_PINGER(time_t delay = 15); + ~STG_PINGER(); + + int Start(); + int Stop(); + void AddIP(uint32_t ip); + void DelIP(uint32_t ip); + int GetPingIPNum(); + void GetAllIP(vector * ipTime); + void PrintAllIP(); + int GetIPTime(uint32_t ip, time_t * t); + void SetDelayTime(time_t delay); + time_t GetDelayTime(); + string GetStrError(); + +private: + + int delay; + bool nonstop; + bool isRunningRecver; + bool isRunningSender; + int sendSocket; + int recvSocket; + pthread_t sendThread; + pthread_t recvThread; + + PING_MESSAGE pmSend; + uint32_t pid; + + uint16_t PingCheckSum(void * data, int len); + int SendPing(uint32_t ip); + uint32_t RecvPing(); + void RealAddIP(); + void RealDelIP(); + + static void * RunSendPing(void * d); + static void * RunRecvPing(void * d); + + string errorStr; + + multimap pingIP; + list ipToAdd; + list ipToDel; + + pthread_mutex_t mutex; +}; +//----------------------------------------------------------------------------- +#endif + + diff --git a/stglibs/pinger.lib/test.cpp b/stglibs/pinger.lib/test.cpp new file mode 100644 index 00000000..af82eb49 --- /dev/null +++ b/stglibs/pinger.lib/test.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pinger.h" + +using namespace std; + +in_addr_t addr; + +//----------------------------------------------------------------------------- +void AddRemoveTest(STG_PINGER & pinger) +{ +addr = inet_addr("192.168.1.2"); +pinger.AddIP(*(uint32_t*)&addr); + +addr = inet_addr("192.168.1.2"); +pinger.AddIP(*(uint32_t*)&addr); + +sleep(2); +pinger.PrintAllIP(); +printf("tree size=%d\n", pinger.GetPingIPNum()); +sleep(5); +pinger.PrintAllIP(); + +addr = inet_addr("192.168.1.2"); +pinger.DelIP(*(uint32_t*)&addr); +printf("DelIP\n"); +sleep(10); +//pinger.PrintAllIP(); +printf("tree size=%d\n", pinger.GetPingIPNum()); +sleep(3); +pinger.PrintAllIP(); +printf("tree size=%d\n", pinger.GetPingIPNum()); +/*addr = inet_addr("192.168.1.2"); +pinger.DelIP(*(uint32_t*)&addr); + +addr = inet_addr("192.168.1.1"); +pinger.DelIP(*(uint32_t*)&addr); + +sleep(2); + +pinger.PrintAllIP(); + +printf("tree size=%d\n", pinger.GetPingIPNum()); +sleep(5); + +addr = inet_addr("192.168.1.4"); +time_t t; +if (pinger.GetIPTime(*(uint32_t*)&addr, &t) == 0) + { + printf("192.168.1.4 t=%lu\n", t); + } +else + { + printf("192.168.1.4 not found\n"); + } + + +addr = inet_addr("192.168.1.5"); +if (pinger.GetIPTime(*(uint32_t*)&addr, &t) == 0) + { + printf("192.168.1.5 t=%lu\n", t); + } +else + { + printf("192.168.1.5 not found\n"); + } + + +pinger.PrintAllIP(); +addr = inet_addr("192.168.1.3"); +if (pinger.GetIPTime(*(uint32_t*)&addr, &t)) + { + printf("IP not present\n"); + } +else + { + printf("Ping time:\n"); + }*/ + +} +//----------------------------------------------------------------------------- +void StressTest(STG_PINGER & pinger) +{ + +for (int i = 1; i <= 200; i++) + { + char s[15]; + sprintf(s, "192.168.1.%d", i); + addr = inet_addr(s); + pinger.AddIP(*(uint32_t*)&addr); + } + +sleep(5); +pinger.PrintAllIP(); +printf("tree size=%d\n", pinger.GetPingIPNum()); + +for (int i = 1; i <= 200; i++) + { + char s[15]; + sprintf(s, "192.168.1.%d", i); + addr = inet_addr(s); + pinger.DelIP(*(uint32_t*)&addr); + } + +/*addr = inet_addr("192.168.1.2"); +pinger.AddIP(*(uint32_t*)&addr); + +addr = inet_addr("192.168.1.3"); +pinger.AddIP(*(uint32_t*)&addr);*/ + +sleep(3); +pinger.PrintAllIP(); +printf("tree size=%d\n", pinger.GetPingIPNum()); +sleep(1); +} +//----------------------------------------------------------------------------- +int main() +{ +vector pingIP; + +STG_PINGER pinger(2); + +if (pinger.Start()) + { + printf("%s\n", pinger.GetStrError().c_str()); + } + + +//AddRemoveTest(pinger); +StressTest(pinger); + +pinger.Stop(); + +return 0; +} +//----------------------------------------------------------------------------- + + diff --git a/stglibs/script_executer.lib/Makefile b/stglibs/script_executer.lib/Makefile new file mode 100644 index 00000000..90691ab3 --- /dev/null +++ b/stglibs/script_executer.lib/Makefile @@ -0,0 +1,12 @@ +############################################################################### +# $Id: Makefile,v 1.6 2010/01/21 13:02:12 faust Exp $ +############################################################################### + +LIB_NAME = script_executer +PROG = lib$(LIB_NAME) + +SRCS = script_executer.cpp + +INCS = script_executer.h + +include ../Makefile.in diff --git a/stglibs/script_executer.lib/script_executer.cpp b/stglibs/script_executer.lib/script_executer.cpp new file mode 100644 index 00000000..00ff0949 --- /dev/null +++ b/stglibs/script_executer.lib/script_executer.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "script_executer.h" + +using namespace std; + +#define MAX_SCRIPT_LEN (1100) + +static int msgid; +static bool nonstop; + +//----------------------------------------------------------------------------- +struct SCRIPT_DATA +{ + long mtype; + char script[MAX_SCRIPT_LEN]; +} sd; +//----------------------------------------------------------------------------- +static void CatchUSR1Executer(int) +{ +nonstop = false; +} +//----------------------------------------------------------------------------- +int ScriptExec(const string & str) +{ +if (str.length() >= MAX_SCRIPT_LEN) + return -1; + +int ret; +strncpy(sd.script, str.c_str(), MAX_SCRIPT_LEN); +sd.mtype = 1; +ret = msgsnd(msgid, (void *)&sd, MAX_SCRIPT_LEN, 0); +if (ret < 0) + { + return -1; + } +return 0; +} +//----------------------------------------------------------------------------- +#ifdef LINUX +void Executer(int, int msgID, pid_t pid, char * procName) +#else +void Executer(int, int msgID, pid_t pid, char *) +#endif +{ +msgid = msgID; +if (pid) + return; +nonstop = true; + +#ifdef LINUX +memset(procName, 0, strlen(procName)); +strcpy(procName, "stg-exec"); +#else +setproctitle("stg-exec"); +#endif + +struct sigaction newsa, oldsa; +sigset_t sigmask; + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGTERM); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGTERM, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGINT); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGINT, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGHUP); +newsa.sa_handler = SIG_IGN; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGHUP, &newsa, &oldsa); + +sigemptyset(&sigmask); +sigaddset(&sigmask, SIGUSR1); +newsa.sa_handler = CatchUSR1Executer; +newsa.sa_mask = sigmask; +newsa.sa_flags = 0; +sigaction(SIGUSR1, &newsa, &oldsa); + +int ret; + +SCRIPT_DATA sd; + +while (nonstop) + { + sd.mtype = 1; + ret = msgrcv(msgid, &sd, MAX_SCRIPT_LEN, 0, 0); + + if (ret < 0) + { + usleep(20000); + continue; + } + int ret = system(sd.script); + if (ret == -1) + { + // Fork failed + } + } +} +//----------------------------------------------------------------------------- + + diff --git a/stglibs/script_executer.lib/script_executer.h b/stglibs/script_executer.lib/script_executer.h new file mode 100644 index 00000000..19d458e3 --- /dev/null +++ b/stglibs/script_executer.lib/script_executer.h @@ -0,0 +1,12 @@ +#ifndef SCRIPT_EXECUTER_H +#define SCRIPT_EXECUTER_H + +#include +#include + +int ScriptExec(const std::string & str); +void Executer(int msgKey, int msgID, pid_t pid, char * procName); + +#endif //SCRIPT_EXECUTER_H + + diff --git a/stglibs/srvconf.lib/Makefile b/stglibs/srvconf.lib/Makefile new file mode 100644 index 00000000..96ad48c2 --- /dev/null +++ b/stglibs/srvconf.lib/Makefile @@ -0,0 +1,19 @@ +############################################################################### +# $Id: Makefile,v 1.9 2010/08/18 07:47:03 faust Exp $ +############################################################################### + +LIB_NAME = srvconf +PROG = lib$(LIB_NAME) + +STGLIBS = -lstg_common \ + -lstg_crypto +LIBS = -lexpat + +SRCS = netunit.cpp \ + parser.cpp \ + servconf.cpp + +INCS = servconf.h \ + netunit.h + +include ../Makefile.in diff --git a/stglibs/srvconf.lib/netunit.cpp b/stglibs/srvconf.lib/netunit.cpp new file mode 100644 index 00000000..417db9d3 --- /dev/null +++ b/stglibs/srvconf.lib/netunit.cpp @@ -0,0 +1,506 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.6 $ + $Date: 2009/02/06 10:25:54 $ + $Author: faust $ + */ + +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +#include "netunit.h" +#include "common.h" + +//--------------------------------------------------------------------------- + +#define SEND_DATA_ERROR "Send data error!" +#define RECV_DATA_ANSWER_ERROR "Recv data answer error!" +#define UNKNOWN_ERROR "Unknown error!" +#define CONNECT_FAILED "Connect failed!" +#define INCORRECT_LOGIN "Incorrect login!" +#define INCORRECT_HEADER "Incorrect header!" +#define SEND_LOGIN_ERROR "Send login error!" +#define RECV_LOGIN_ANSWER_ERROR "Recv login answer error!" +#define CREATE_SOCKET_ERROR "Create socket failed!" +#define WSASTARTUP_FAILED "WSAStartup failed!" +#define SEND_HEADER_ERROR "Send header error!" +#define RECV_HEADER_ANSWER_ERROR "Recv header answer error!" + +//--------------------------------------------------------------------------- +NETTRANSACT::NETTRANSACT() +{ +RxCallBack = NULL; +} +//----------------------------------------------------------------------------- +void NETTRANSACT::EnDecryptInit(const char * passwd, int passwdLen, BLOWFISH_CTX *ctx) +{ +unsigned char * keyL = NULL;//[PASSWD_LEN]; // ��� ������ + +keyL = new unsigned char[PASSWD_LEN]; + +memset(keyL, 0, PASSWD_LEN); + +strncpy((char *)keyL, passwd, PASSWD_LEN); + +Blowfish_Init(ctx, keyL, PASSWD_LEN); + +delete[] keyL; +} +//----------------------------------------------------------------------------- +void NETTRANSACT::Encrypt(char * d, const char * s, BLOWFISH_CTX *ctx) +{ +/*unsigned char ss[8]; + +memcpy(ss, s, 8); + +Blowfish_Encrypt(ctx, (uint32_t *)ss, (uint32_t *)(ss + 4)); + +memcpy(d, ss, 8);*/ +EncodeString(d, s, ctx); + +} +//--------------------------------------------------------------------------- +void NETTRANSACT::Decrypt(char * d, const char * s, BLOWFISH_CTX *ctx) +{ +/*unsigned char ss[8]; + +memcpy(ss, s, 8); + +Blowfish_Decrypt(ctx, (uint32_t *)ss, (uint32_t *)(ss + 4)); + +memcpy(d, ss, 8);*/ +DecodeString(d, s, ctx); + +} +//--------------------------------------------------------------------------- +int NETTRANSACT::Connect() +{ +int ret; + +outerSocket = socket(PF_INET, SOCK_STREAM, 0); +if (outerSocket < 0) + { + strcpy(errorMsg, CREATE_SOCKET_ERROR); + return st_conn_fail; + } + +memset(&outerAddr, 0, sizeof(outerAddr)); +memset(&localAddr, 0, sizeof(localAddr)); + +struct hostent he; +struct hostent * phe; + +unsigned long ip; +ip = inet_addr(server); + +if (ip == INADDR_NONE) + { + phe = gethostbyname(server); + if (phe == NULL) + { + sprintf(errorMsg, "DNS error.\nCan not reslove %s", server); + return st_dns_err; + } + + memcpy(&he, phe, sizeof(he)); + ip = *((long*)he.h_addr_list[0]); + } +outerAddr.sin_family = AF_INET; +outerAddr.sin_port = htons(port); +outerAddr.sin_addr.s_addr = ip; + +ret = connect(outerSocket, (struct sockaddr*)&outerAddr, sizeof(outerAddr)); + +if (ret < 0) + { + strcpy(errorMsg, CONNECT_FAILED); + close(outerSocket); + return st_conn_fail; + } +return st_ok; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::Disconnect() +{ +close(outerSocket); +return 0; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::Transact(const char * data) +{ +int ret; +if ((ret = TxHeader()) != st_ok) + { + Disconnect(); + return ret; + } + +if ((ret = RxHeaderAnswer()) != st_ok) + { + Disconnect(); + return ret; + } + +if ((ret = TxLogin()) != st_ok) + { + Disconnect(); + return ret; + } + +if ((ret = RxLoginAnswer()) != st_ok) + { + Disconnect(); + return ret; + } + +if ((ret = TxLoginS()) != st_ok) + { + Disconnect(); + return ret; + } + +if ((ret = RxLoginSAnswer()) != st_ok) + { + Disconnect(); + return ret; + } + +if ((ret = TxData(data)) != st_ok) + { + Disconnect(); + return ret; + } + +if ((ret = RxDataAnswer()) != st_ok) + { + Disconnect(); + return ret; + } + +return st_ok; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::TxHeader() +{ +int ret; +ret = send(outerSocket, STG_HEADER, strlen(STG_HEADER), 0); +if (ret <= 0) + { + strcpy(errorMsg, SEND_HEADER_ERROR); + return st_send_fail; + } + +return st_ok; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::RxHeaderAnswer() +{ +char buffer[sizeof(STG_HEADER)+1]; +int ret;//, we; + +ret = recv(outerSocket, buffer, strlen(OK_HEADER), 0); +if (ret <= 0) + { + //we = WSAGetLastError(); + strcpy(errorMsg, RECV_HEADER_ANSWER_ERROR); + return st_recv_fail; + } + +if (strncmp(OK_HEADER, buffer, strlen(OK_HEADER)) == 0) + { + return st_ok; + } +else + { + if (strncmp(ERR_HEADER, buffer, strlen(ERR_HEADER)) == 0) + { + strcpy(errorMsg, INCORRECT_HEADER); + return st_header_err; + } + else + { + strcpy(errorMsg, UNKNOWN_ERROR); + return st_unknown_err; + } + } +} +//--------------------------------------------------------------------------- +int NETTRANSACT::TxLogin() +{ +char loginZ[ADM_LOGIN_LEN]; +int ret; + +memset(loginZ, 0, ADM_LOGIN_LEN); +strncpy(loginZ, login, ADM_LOGIN_LEN); +ret = send(outerSocket, loginZ, ADM_LOGIN_LEN, 0); + +if (ret <= 0) + { + strcpy(errorMsg, SEND_LOGIN_ERROR); + return st_send_fail; + } + +return st_ok; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::RxLoginAnswer() +{ +char buffer[sizeof(OK_LOGIN)+1]; +int ret;//, we; + +ret = recv(outerSocket, buffer, strlen(OK_LOGIN), 0); +if (ret <= 0) + { + strcpy(errorMsg, RECV_LOGIN_ANSWER_ERROR); + return st_recv_fail; + } + +if (strncmp(OK_LOGIN, buffer, strlen(OK_LOGIN)) == 0) + { + return st_ok; + } +else + { + if (strncmp(ERR_LOGIN, buffer, strlen(ERR_LOGIN)) == 0) + { + strcpy(errorMsg, INCORRECT_LOGIN); + return st_login_err; + } + else + { + strcpy(errorMsg, UNKNOWN_ERROR); + return st_unknown_err; + } + } +} +//--------------------------------------------------------------------------- +int NETTRANSACT::TxLoginS() +{ +char loginZ[ADM_LOGIN_LEN]; +char ct[ENC_MSG_LEN]; +int ret; + +memset(loginZ, 0, ADM_LOGIN_LEN); +strncpy(loginZ, login, ADM_LOGIN_LEN); + +BLOWFISH_CTX ctx; +EnDecryptInit(password, PASSWD_LEN, &ctx); + +for (int j = 0; j < ADM_LOGIN_LEN / ENC_MSG_LEN; j++) + { + Encrypt(ct, loginZ + j*ENC_MSG_LEN, &ctx); + ret = send(outerSocket, ct, ENC_MSG_LEN, 0); + if (ret <= 0) + { + strcpy(errorMsg, SEND_LOGIN_ERROR); + return st_send_fail; + } + } + +return st_ok; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::RxLoginSAnswer() +{ +char buffer[sizeof(OK_LOGINS)+1]; +int ret; + +ret = recv(outerSocket, buffer, strlen(OK_LOGINS), 0); +if (ret <= 0) + { + strcpy(errorMsg, RECV_LOGIN_ANSWER_ERROR); + return st_recv_fail; + } + +if (strncmp(OK_LOGINS, buffer, strlen(OK_LOGINS)) == 0) + { + return st_ok; + } +else + { + if (strncmp(ERR_LOGINS, buffer, strlen(ERR_LOGINS)) == 0) + { + strcpy(errorMsg, INCORRECT_LOGIN); + return st_logins_err; + } + else + { + strcpy(errorMsg, UNKNOWN_ERROR); + return st_unknown_err; + } + } +} +//--------------------------------------------------------------------------- +int NETTRANSACT::TxData(const char * text) +{ +char textZ[ENC_MSG_LEN]; +char ct[ENC_MSG_LEN]; +int ret; +int j; + +int n = strlen(text) / ENC_MSG_LEN; +int r = strlen(text) % ENC_MSG_LEN; + +BLOWFISH_CTX ctx; +EnDecryptInit(password, PASSWD_LEN, &ctx); + +for (j = 0; j < n; j++) + { + strncpy(textZ, text + j*ENC_MSG_LEN, ENC_MSG_LEN); + Encrypt(ct, textZ, &ctx); + ret = send(outerSocket, ct, ENC_MSG_LEN, 0); + if (ret <= 0) + { + strcpy(errorMsg, SEND_DATA_ERROR); + return st_send_fail; + } + } + +memset(textZ, 0, ENC_MSG_LEN); +if (r) + strncpy(textZ, text + j*ENC_MSG_LEN, ENC_MSG_LEN); + +EnDecryptInit(password, PASSWD_LEN, &ctx); + +Encrypt(ct, textZ, &ctx); +ret = send(outerSocket, ct, ENC_MSG_LEN, 0); +if (ret <= 0) + { + strcpy(errorMsg, SEND_DATA_ERROR); + return st_send_fail; + } + +return st_ok; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::TxData(char * data) +{ +char buff[ENC_MSG_LEN]; +char buffS[ENC_MSG_LEN]; +char passwd[ADM_PASSWD_LEN]; + +strncpy(passwd, password, ADM_PASSWD_LEN); +memset(buff, 0, ENC_MSG_LEN); + +int l = strlen(data)/ENC_MSG_LEN; +if (strlen(data)%ENC_MSG_LEN) + l++; + +BLOWFISH_CTX ctx; +EnDecryptInit(passwd, PASSWD_LEN, &ctx); + +for (int j = 0; j < l; j++) + { + strncpy(buff, &data[j*ENC_MSG_LEN], ENC_MSG_LEN); + Encrypt(buffS, buff, &ctx); + send(outerSocket, buffS, ENC_MSG_LEN, 0); + } + +return 0; +} +//--------------------------------------------------------------------------- +int NETTRANSACT::RxDataAnswer() +{ +int n = 0; +int ret; +char bufferS[ENC_MSG_LEN]; +char buffer[ENC_MSG_LEN + 1]; + +BLOWFISH_CTX ctx; +EnDecryptInit(password, PASSWD_LEN, &ctx); + +while (1) + { + ret = recv(outerSocket, &bufferS[n++], 1, 0); + if (ret <= 0) + { + close(outerSocket); + strcpy(errorMsg, RECV_DATA_ANSWER_ERROR); + return st_recv_fail; + } + + if (n == ENC_MSG_LEN) + { + + n = 0; + Decrypt(buffer, bufferS, &ctx); + buffer[ENC_MSG_LEN] = 0; + + answerList.push_back(buffer); + + for (int j = 0; j < ENC_MSG_LEN; j++) + { + if (buffer[j] == 0) + { + if (RxCallBack) + if (st_ok != RxCallBack(dataRxCallBack, &answerList)) + { + return st_xml_parse_error; + } + return st_ok; + } + } + } + } +} +//--------------------------------------------------------------------------- +void NETTRANSACT::SetLogin(const char * l) +{ +strncpy(login, l, ADM_LOGIN_LEN); +} +//--------------------------------------------------------------------------- +void NETTRANSACT::SetPassword(const char * p) +{ +strncpy(password, p, ADM_PASSWD_LEN); +} +//--------------------------------------------------------------------------- +void NETTRANSACT::SetServer(const char * serverName) +{ +strncpy(server, serverName, SERVER_NAME_LEN); +} +//--------------------------------------------------------------------------- +void NETTRANSACT::SetServerPort(short unsigned p) +{ +port = p; +} +//--------------------------------------------------------------------------- +void NETTRANSACT::SetRxCallback(void * data, RxCallback_t cb) +{ +RxCallBack = cb; +dataRxCallBack = data; +} +//--------------------------------------------------------------------------- +char * NETTRANSACT::GetError() +{ +return errorMsg; +} +//--------------------------------------------------------------------------- +void NETTRANSACT::Reset() +{ +answerList.clear(); +} +//--------------------------------------------------------------------------- + diff --git a/stglibs/srvconf.lib/netunit.h b/stglibs/srvconf.lib/netunit.h new file mode 100644 index 00000000..72d3b721 --- /dev/null +++ b/stglibs/srvconf.lib/netunit.h @@ -0,0 +1,130 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.6 $ + $Date: 2010/02/11 12:32:53 $ + $Author: faust $ + */ + +#ifndef NetUnitH +#define NetUnitH + +#include +#include +#include +#include +#include + +#include "common.h" +#include "blowfish.h" + +#define STG_HEADER "SG04" +#define OK_HEADER "OKHD" +#define ERR_HEADER "ERHD" +#define OK_LOGIN "OKLG" +#define ERR_LOGIN "ERLG" +#define OK_LOGINS "OKLS" +#define ERR_LOGINS "ERLS" + +// äÌÉÎÎÁ ÛÉÆÒÕÅÍÏÇÏ É ÐÅÒÅÄÁ×ÁÅÍÏÇ ÚÁ ÏÄÉÎ ÒÁÚ ÂÌÏËÁ ÉÎÆÏÒÍÁÃÉÉ +#define ENC_MSG_LEN (8) + +#define MAX_ERR_STR_LEN (64) + +typedef int(*RxCallback_t)(void *, std::list *); + +enum status +{ +st_ok = 0, +st_conn_fail, +st_send_fail, +st_recv_fail, +st_header_err, +st_login_err, +st_logins_err, +st_data_err, +st_unknown_err, +st_dns_err, +st_xml_parse_error, +st_data_error +}; + +enum CONF_STATE +{ +confHdr = 0, +confLogin, +confLoginCipher, +confData +}; +//--------------------------------------------------------------------------- +class NETTRANSACT +{ +public: + NETTRANSACT(); + int Transact(const char * data); + char *GetError(); + + void SetRxCallback(void * data, RxCallback_t); + + void SetServer(const char * serverName); + void SetServerPort(short unsigned p); + + void SetLogin(const char * l); + void SetPassword(const char * p); + //////////////////////////////////////////// + int Connect(); + int Disconnect(); + void Reset(); +private: + int TxHeader(); + int RxHeaderAnswer(); + + int TxLogin(); + int RxLoginAnswer(); + + int TxLoginS(); + int RxLoginSAnswer(); + + int TxData(const char * text); + int TxData(char * data); + int RxDataAnswer(); + + void Encrypt(char * d, const char * s, BLOWFISH_CTX *ctx); + void EnDecryptInit(const char * passwd, int passwdLen, BLOWFISH_CTX *ctx); + void Decrypt(char * d, const char * s, BLOWFISH_CTX *ctx); + + //unsigned ip; + char server[SERVER_NAME_LEN]; + short unsigned port; + char login[ADM_LOGIN_LEN]; + char password[ADM_PASSWD_LEN]; + int outerSocket; + int localSocket; + struct sockaddr_in outerAddr; + struct sockaddr_in localAddr; + int error; + std::list answerList; + RxCallback_t RxCallBack; + void * dataRxCallBack; + char errorMsg[MAX_ERR_STR_LEN]; +}; +//--------------------------------------------------------------------------- +#endif diff --git a/stglibs/srvconf.lib/parser.cpp b/stglibs/srvconf.lib/parser.cpp new file mode 100644 index 00000000..7c96b2ed --- /dev/null +++ b/stglibs/srvconf.lib/parser.cpp @@ -0,0 +1,961 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.18 $ + $Date: 2010/08/04 00:40:00 $ + $Author: faust $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +//#include "srvconf_common.h" +#include "stg_const.h" +#include "servconf.h" + +using namespace std; + +//----------------------------------------------------------------------------- +PARSER::PARSER() +{ +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PARSER_GET_USERS::PARSER_GET_USERS() +{ +depth = 0; +error = false; +RecvUserDataCb = NULL; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::Reset() +{ + +} +//----------------------------------------------------------------------------- +int PARSER_GET_USERS::ParseStart(const char *el, const char **attr) +{ +depth++; +if (depth == 1) + { + ParseUsers(el, attr); + } + +if (depth == 2) + { + ParseUser(el, attr); + } + +if (depth == 3) + { + ParseUserParams(el, attr); + } +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::ParseEnd(const char *) +{ +depth--; +if (depth == 1) + { + if (RecvUserDataCb) + { + RecvUserDataCb(&user, userDataCb); + } + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::ParseUsers(const char * el, const char ** attr) +{ +if (strcasecmp(el, "users") == 0) + { + if (*attr != NULL) + return; + return; + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::ParseUser(const char * el, const char ** attr) +{ +if (el && attr[0]) + { + if (strcasecmp(el, "user") != 0) + { + return; + } + + if (strcasecmp(attr[0], "login") != 0) + { + return; + } + user.login = attr[1]; + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::ParseUserParams(const char * el, const char ** attr) +{ +if (strcasecmp(el, "cash") == 0) + { + if (strtodouble2(attr[1], user.cash) < 0) + { + return; + } + } + +/*if (strcasecmp(el, "LastCash") == 0) + { + if (strtodouble2(attr[1], user.lastCash) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + }*/ + +/*if (strcasecmp(el, "LastActivityTime") == 0) + { + if (strtol(attr[1], user.lastActivityTime) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + }*/ + + +/*if (strcasecmp(el, "LastTimeCash") == 0) + { + if (strtol(attr[1], user.lastTimeCash) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + }*/ + +/*if (strcasecmp(el, "CashExpire") == 0) + { + if (strtol(attr[1], user.cashExpire) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + }*/ + +if (strcasecmp(el, "credit") == 0) + { + if (strtodouble2(attr[1], user.credit) < 0) + { + return; + } + } + +/*if (strcasecmp(el, "freemb") == 0) + { + if (strtodouble2(attr[1], user.freeMb) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + }*/ + +if (strcasecmp(el, "down") == 0) + { + if (str2x(attr[1], user.down) < 0) + { + return; + } + } + +if (strcasecmp(el, "passive") == 0) + { + if (str2x(attr[1], user.passive) < 0) + { + return; + } + } + +if (strcasecmp(el, "disableDetailStat") == 0) + { + if (str2x(attr[1], user.disableDetailStat) < 0) + { + return; + } + } + + +if (strcasecmp(el, "status") == 0) + { + if (str2x(attr[1], user.connected) < 0) + { + return; + } + } + +if (strcasecmp(el, "aonline") == 0) + { + if (str2x(attr[1], user.alwaysOnline) < 0) + { + return; + } + } + +if (strcasecmp(el, "currip") == 0) + { + user.ip = inet_addr(attr[1]); + } + +if (strcasecmp(el, "ip") == 0) + { + user.ips = attr[1]; + } + + +if (strcasecmp(el, "tariff") == 0) + { + //KOIToWin(user.tariff, *(attr+1), TARIFF_LEN); + user.tariff = attr[1]; + return; + } + +if (strcasecmp(el, "password") == 0) + { + user.password = *(attr+1); + return; + } + +if (strcasecmp(el, "iface") == 0) + { + user.iface = attr[1]; + return; + } + +/*if (strcasecmp(el, "name") == 0) + { + / *char nameEnc[REALNM_LEN * 2 + 1]; + char name[REALNM_LEN]; + strncpy(nameEnc, attr[1], REALNM_LEN * 2 + 1); + Decode21(name, nameEnc); + KOIToWin(user.realName, name, REALNM_LEN);* / + Decode21str(user.realName, attr[1]); + return; + }*/ + +if (strcasecmp(el, "address") == 0) + { + /*char addressEnc[ADDR_LEN * 2 + 1]; + char address[ADDR_LEN]; + strncpy(addressEnc, attr[1], ADDR_LEN * 2 + 1); + Decode21(address, addressEnc); + KOIToWin(user.address, address, ADDR_LEN);*/ + Decode21str(user.address, attr[1]); + return; + } + +if (strcasecmp(el, "phone") == 0) + { + /*char phoneEnc[PHONE_LEN * 2 + 1]; + char phone[PHONE_LEN]; + strncpy(phoneEnc, attr[1], PHONE_LEN * 2 + 1); + Decode21(phone, phoneEnc); + KOIToWin(user.phone, phone, PHONE_LEN);*/ + Decode21str(user.phone, attr[1]); + return; + } + +if (strcasecmp(el, "note") == 0) + { + /*char noteEnc[NOTE_LEN * 2 + 1]; + char note[NOTE_LEN]; + strncpy(noteEnc, attr[1], NOTE_LEN * 2 + 1);*/ + //KOIToWin(user.note, note, NOTE_LEN); + //user.note = note; + Decode21str(user.note, attr[1]); + return; + } + +if (strcasecmp(el, "email") == 0) + { + /*char emailEnc[EMAIL_LEN * 2 + 1]; + char email[EMAIL_LEN]; + strncpy(emailEnc, attr[1], EMAIL_LEN * 2 + 1); + Decode21(email, emailEnc); + //KOIToWin(user.email, email, EMAIL_LEN); + user.email = email;*/ + Decode21str(user.email, attr[1]); + return; + } + +if (strcasecmp(el, "group") == 0) + { + /*char groupEnc[GROUP_LEN * 2 + 1]; + char group[GROUP_LEN]; + strncpy(groupEnc, attr[1], GROUP_LEN * 2 + 1); + Decode21(group, groupEnc); + //KOIToWin(user.group, group, GROUP_LEN); + user.group = group;*/ + Decode21str(user.group, attr[1]); + return; + } + +if (strcasecmp(el, "traff") == 0) + { + ParseUserLoadStat(el, attr); + return; + } + +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::ParseUserLoadStat(const char *, const char ** attr) +{ +int i = 0; +char dir[6]; +while (attr[i]) + { + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "MU%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.mu[j]); + break; + } + } + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "MD%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.md[j]); + break; + } + } + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "SU%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.su[j]); + break; + } + } + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "SD%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.sd[j]); + break; + } + } + i+=2; + } +return; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USERS::SetUserDataRecvCb(RecvUserDataCb_t f, void * data) +{ +RecvUserDataCb = f; +userDataCb = data; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PARSER_GET_USER::PARSER_GET_USER() +{ +depth = 0; +error = false; +RecvUserDataCb = NULL; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USER::Reset() +{ + +} +//----------------------------------------------------------------------------- +int PARSER_GET_USER::ParseStart(const char *el, const char **attr) +{ +depth++; +if (depth == 1) + { + ParseUser(el, attr); + } + +if (depth == 2) + { + ParseUserParams(el, attr); + } +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USER::ParseEnd(const char *) +{ +depth--; +if (depth == 0) + { + if (RecvUserDataCb) + { + RecvUserDataCb(&user, userDataCb); + } + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_USER::ParseUser(const char * el, const char ** attr) +{ +if (strcasecmp(el, "user") == 0) + { + if (strcasecmp(attr[1], "error") == 0) + user.login = ""; + return; + } +} +//----------------------------------------------------------------------------- +struct ParsedStringParams +{ +string * param; +string paramName; +}; +//----------------------------------------------------------------------------- +struct ParsedDoubleParams +{ +double * param; +string paramName; +}; +//----------------------------------------------------------------------------- +void PARSER_GET_USER::ParseUserParams(const char * el, const char ** attr) +{ +//printf("PARSER_GET_USER::ParseUserParams el=%s attr[1]=%s\n", el, attr[1]); + +if (strcasecmp(el, "login") == 0) + { + user.login = attr[1]; + } + + +/*if (strcasecmp(el, "LastActivityTime") == 0) + { + if (strtol(attr[1], user.lastActivityTime) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + } + + +if (strcasecmp(el, "LastTimeCash") == 0) + { + if (strtol(attr[1], user.lastTimeCash) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + } + +if (strcasecmp(el, "CashExpire") == 0) + { + if (strtol(attr[1], user.cashExpire) < 0) + { + MessageDlg("Error in answer", mtError, TMsgDlgButtons() << mbOK, 0); + return 0; + } + } +*/ + +if (strcasecmp(el, "down") == 0) + { + if (str2x(attr[1], user.down) < 0) + { + return; + } + } + +if (strcasecmp(el, "passive") == 0) + { + if (str2x(attr[1], user.passive) < 0) + { + return; + } + } + +if (strcasecmp(el, "disableDetailStat") == 0) + { + if (str2x(attr[1], user.disableDetailStat) < 0) + { + return; + } + } + + +if (strcasecmp(el, "status") == 0) + { + if (str2x(attr[1], user.connected) < 0) + { + return; + } + } + +if (strcasecmp(el, "aonline") == 0) + { + if (str2x(attr[1], user.alwaysOnline) < 0) + { + return; + } + } + +if (strcasecmp(el, "currip") == 0) + { + user.ip = inet_addr(attr[1]); + } + +for (int i = 0; i < USERDATA_NUM; i++) + { + string num; + x2str(i, num); + string udName = "UserData" + num; + if (strcasecmp(el, udName.c_str()) == 0) + { + Decode21str(user.userData[i], attr[1]); + return; + } + } + +ParsedStringParams psp[] = +{ + {&user.ips, "ip"}, + {&user.tariff, "tariff"}, + {&user.password, "password"}, + {&user.iface, "iface"}, +}; + +for (unsigned i = 0; i < sizeof(psp)/sizeof(ParsedStringParams); i++) + { + if (strcasecmp(el, psp[i].paramName.c_str()) == 0) + { + *psp[i].param = attr[1]; + return; + } + } + +ParsedStringParams pspEnc[] = +{ + {&user.note, "note"}, + {&user.email, "email"}, + {&user.group, "group"}, + {&user.name, "name"}, + {&user.address, "address"}, + {&user.phone, "phone"} +}; + +for (unsigned i = 0; i < sizeof(pspEnc)/sizeof(ParsedStringParams); i++) + { + if (strcasecmp(el, pspEnc[i].paramName.c_str()) == 0) + { + Decode21str(*pspEnc[i].param, attr[1]); + return; + } + } + +ParsedDoubleParams pdp[] = +{ + {&user.cash, "cash"}, + {&user.credit, "credit"}, + {&user.lastCash, "lastCash"}, + {&user.prepaidTraff, "freemb"}, +}; + +for (unsigned i = 0; i < sizeof(pdp)/sizeof(ParsedDoubleParams); i++) + { + if (strcasecmp(el, pdp[i].paramName.c_str()) == 0) + { + strtodouble2(attr[1], *pdp[i].param); + return; + } + } + +if (strcasecmp(el, "traff") == 0) + { + ParseUserLoadStat(el, attr); + return; + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_USER::ParseUserLoadStat(const char *, const char ** attr) +{ +int i = 0; +char dir[6]; +while (attr[i]) + { + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "MU%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.mu[j]); + break; + } + } + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "MD%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.md[j]); + break; + } + } + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "SU%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.su[j]); + break; + } + } + for (int j = 0; j < DIR_NUM; j++) + { + sprintf(dir, "SD%d", j); + if (strcasecmp(dir, attr[i]) == 0) + { + str2x(attr[i+1], user.stat.sd[j]); + break; + } + } + i+=2; + } +return; +} +//----------------------------------------------------------------------------- +void PARSER_GET_USER::SetUserDataRecvCb(RecvUserDataCb_t f, void * data) +{ +RecvUserDataCb = f; +userDataCb = data; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PARSER_GET_SERVER_INFO::PARSER_GET_SERVER_INFO() +{ +depth = 0; +error = false; +RecvServerInfoDataCb = NULL; +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::Reset() +{ + +} +//----------------------------------------------------------------------------- +int PARSER_GET_SERVER_INFO::ParseStart(const char *el, const char **attr) +{ +depth++; +if (depth == 1) + { + if (strcasecmp(el, "ServerInfo") != 0) + { + //printf("%s\n", el); + } + } +else + { + if (depth == 2) + { + if (strcasecmp(el, "uname") == 0) + { + ParseUname(attr); + return 0; + } + if (strcasecmp(el, "version") == 0) + { + ParseServerVersion(attr); + return 0; + } + if (strcasecmp(el, "tariff") == 0) + { + ParseTariffType(attr); + return 0; + } + if (strcasecmp(el, "dir_num") == 0) + { + ParseDirNum(attr); + return 0; + } + if (strcasecmp(el, "users_num") == 0) + { + ParseUsersNum(attr); + return 0; + } + if (strcasecmp(el, "tariff_num") == 0) + { + ParseTariffsNum(attr); + return 0; + } + + for (int j = 0; j < DIR_NUM; j++) + { + char str[16]; + sprintf(str, "dir_name_%d", j); + if (strcasecmp(el, str) == 0) + { + ParseDirName(attr, j); + } + } + + } + } +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseEnd(const char *) +{ +depth--; +if (depth == 0) + { + RecvServerInfoDataCb(&serverInfo, serverInfoDataCb); + } +} +//----------------------------------------------------------------------------- +/*void PARSER_GET_SERVER_INFO::ParseServerInfo(const char * el, const char ** attr) + { + }*/ +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::SetServerInfoRecvCb(RecvServerInfoDataCb_t f, void * data) +{ +RecvServerInfoDataCb = f; +serverInfoDataCb = data; +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseUname(const char ** attr) +{ +if (strcmp(*attr, "value") == 0) + serverInfo.uname = attr[1]; +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseServerVersion(const char ** attr) +{ +if (strcmp(*attr, "value") == 0) + serverInfo.version = attr[1]; +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseUsersNum(const char ** attr) +{ +if (strcmp(*attr, "value") == 0) + { + if (str2x(attr[1], serverInfo.usersNum) < 0) + { + serverInfo.usersNum = -1; + return; + } + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseTariffsNum(const char ** attr) +{ +if (strcmp(*attr, "value") == 0) + { + if (str2x(attr[1], serverInfo.tariffNum) < 0) + { + serverInfo.tariffNum = -1; + return; + } + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseTariffType(const char ** attr) +{ +if (strcmp(*attr, "value") == 0) + { + if (str2x(attr[1], serverInfo.tariffType) < 0) + { + serverInfo.tariffType = -1; + return; + } + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseDirNum(const char **attr) +{ +if (strcasecmp(*attr, "value") == 0) + { + if (str2x(attr[1], serverInfo.dirNum) < 0) + { + serverInfo.dirNum = -1; + return; + } + } +} +//----------------------------------------------------------------------------- +void PARSER_GET_SERVER_INFO::ParseDirName(const char **attr, int d) +{ +if (strcmp(attr[0], "value") == 0) + { + char str[2*DIRNAME_LEN + 1]; + Decode21(str, attr[1]); + serverInfo.dirName[d] = str; + } +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PARSER_CHG_USER::PARSER_CHG_USER() +{ +depth = 0; +error = false; +RecvChgUserCb = NULL; +} +//----------------------------------------------------------------------------- +int PARSER_CHG_USER::ParseStart(const char *el, const char **attr) +{ +depth++; +if (depth == 1) + { + if (strcasecmp(el, "SetUser") == 0) + { + ParseAnswer(el, attr); + } + else if (strcasecmp(el, "DelUser") == 0) + { + ParseAnswer(el, attr); + } + else if (strcasecmp(el, "AddUser") == 0) + { + ParseAnswer(el, attr); + } + } +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::ParseEnd(const char *) +{ +depth--; +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::Reset() +{ + +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::ParseAnswer(const char *, const char **attr) +{ +if (RecvChgUserCb) + { + RecvChgUserCb(attr[1], chgUserCbData); + } +} +//----------------------------------------------------------------------------- +void PARSER_CHG_USER::SetChgUserRecvCb(RecvChgUserCb_t f, void * data) +{ +RecvChgUserCb = f; +chgUserCbData = data; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PARSER_CHECK_USER::PARSER_CHECK_USER() +{ +depth = 0; +error = false; +RecvCheckUserCb = NULL; +} +//----------------------------------------------------------------------------- +int PARSER_CHECK_USER::ParseStart(const char *el, const char **attr) +{ +depth++; +if (depth == 1) + { + if (strcasecmp(el, "CheckUser") == 0) + { + //printf("el=%s attr[0]=%s attr[1]=%s\n", el, attr[0], attr[1]); + ParseAnswer(el, attr); + } + } +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_CHECK_USER::ParseEnd(const char *) +{ +depth--; +} +//----------------------------------------------------------------------------- +void PARSER_CHECK_USER::ParseAnswer(const char *, const char **attr) +{ +if (RecvCheckUserCb) + { + RecvCheckUserCb(attr[1], checkUserCbData); + } +} +//----------------------------------------------------------------------------- +void PARSER_CHECK_USER::SetCheckUserRecvCb(RecvCheckUserCb_t f, void * data) +{ +RecvCheckUserCb = f; +checkUserCbData = data; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +PARSER_SEND_MESSAGE::PARSER_SEND_MESSAGE() +{ +depth = 0; +error = false; +RecvSendMessageCb = NULL; +} +//----------------------------------------------------------------------------- +int PARSER_SEND_MESSAGE::ParseStart(const char *el, const char **attr) +{ +depth++; +if (depth == 1) + { + if (strcasecmp(el, "SendMessageResult") == 0) + { + ParseAnswer(el, attr); + } + } +return 0; +} +//----------------------------------------------------------------------------- +void PARSER_SEND_MESSAGE::ParseEnd(const char *) +{ +depth--; +} +//----------------------------------------------------------------------------- +void PARSER_SEND_MESSAGE::Reset() +{ + +} +//----------------------------------------------------------------------------- +void PARSER_SEND_MESSAGE::ParseAnswer(const char *, const char **attr) +{ +if (RecvSendMessageCb) + RecvSendMessageCb(attr[1], sendMessageCbData); +} +//----------------------------------------------------------------------------- +void PARSER_SEND_MESSAGE::SetSendMessageRecvCb(RecvSendMessageCb_t f, void * data) +{ +RecvSendMessageCb = f; +sendMessageCbData = data; +} +//----------------------------------------------------------------------------- + diff --git a/stglibs/srvconf.lib/servconf.cpp b/stglibs/srvconf.lib/servconf.cpp new file mode 100644 index 00000000..be6fa279 --- /dev/null +++ b/stglibs/srvconf.lib/servconf.cpp @@ -0,0 +1,398 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.8 $ + $Date: 2010/08/04 00:40:38 $ + $Author: faust $ + */ + +#include +#include + +#include "servconf.h" + +using namespace std; + +//----------------------------------------------------------------------------- +int AnsRecv(void * data, list * list1) +{ +//NODE * node; +SERVCONF * sc; +char ans[ENC_MSG_LEN + 1]; +int len, done = 0; + +sc = (SERVCONF*)data; + +XML_ParserReset(sc->parser, NULL); +XML_SetElementHandler(sc->parser, Start, End); +XML_SetUserData(sc->parser, data); + +//loop parsing +list::iterator node; +node = list1->begin(); + +if (node == list1->end()) + { + return st_ok; + } + +while (node != list1->end()) + { + strncpy(ans, node->c_str(), ENC_MSG_LEN); + ans[ENC_MSG_LEN] = 0; + //printf("---> %s\n", ans); + len = strlen(ans); + + if (XML_Parse(sc->parser, ans, len, done) == XML_STATUS_ERROR) + { + snprintf(sc->errorMsg, MAX_ERR_STR_LEN, "XML parse error at line %d: %s", + static_cast(XML_GetCurrentLineNumber(sc->parser)), + XML_ErrorString(XML_GetErrorCode(sc->parser))); + printf(sc->errorMsg, "XML parse error at line %d: %s", + XML_GetCurrentLineNumber(sc->parser), + XML_ErrorString(XML_GetErrorCode(sc->parser))); + return st_xml_parse_error; + } + ++node; + + } + +return 0; +} +//----------------------------------------------------------------------------- +void Start(void *data, const char *el, const char **attr) +{ +SERVCONF * sc; +sc = (SERVCONF*)data; +sc->Start(el, attr); +} +//----------------------------------------------------------------------------- +void End(void *data, const char *el) +{ +SERVCONF * sc; +sc = (SERVCONF*)data; +sc->End(el); +} +//----------------------------------------------------------------------------- +SERVCONF::SERVCONF() +{ +parser = XML_ParserCreate(NULL); +parseDepth = 0; +} +//----------------------------------------------------------------------------- +void SERVCONF::SetServer(const char * server) +{ +nt.SetServer(server); +} +//----------------------------------------------------------------------------- +void SERVCONF::SetPort(uint16_t port) +{ +nt.SetServerPort(port); +} +//----------------------------------------------------------------------------- +void SERVCONF::SetAdmLogin(const char * login) +{ +nt.SetLogin(login); +} +//----------------------------------------------------------------------------- +void SERVCONF::SetAdmPassword(const char * password) +{ +nt.SetPassword(password); +} +//----------------------------------------------------------------------------- +int SERVCONF::GetUser(const char * l) +{ +char request[255]; +snprintf(request, 255, "", l); +int ret; + +currParser = &parserGetUser; +((PARSER_GET_USER*)currParser)->SetUserDataRecvCb(RecvGetUserDataCb, getUserDataDataCb); + +nt.Reset(); +nt.SetRxCallback(this, AnsRecv); + +if ((ret = nt.Connect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Transact(request)) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Disconnect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } + +return st_ok; +} +//----------------------------------------------------------------------------- +int SERVCONF::GetUsers() +{ +char request[] = ""; +int ret; + +currParser = &parserGetUsers; +((PARSER_GET_USERS*)currParser)->SetUserDataRecvCb(RecvUserDataCb, getUsersDataDataCb); + +nt.Reset(); +nt.SetRxCallback(this, AnsRecv); + +if ((ret = nt.Connect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Transact(request)) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Disconnect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } + +return st_ok; +} +//----------------------------------------------------------------------------- +int SERVCONF::SendMessage(const char * login, const char * message, int prio) +{ +char request[1000]; +char msg[500]; +Encode12(msg, message, strlen(message)); +snprintf(request, 1000, "", login, prio, msg); +int ret; + +currParser = &parserSendMessage; +parserSendMessage.SetSendMessageRecvCb(RecvSendMessageCb, sendMessageDataCb); + +nt.Reset(); +nt.SetRxCallback(this, AnsRecv); + +if ((ret = nt.Connect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Transact(request)) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Disconnect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } + +return st_ok; +} +//----------------------------------------------------------------------------- +int SERVCONF::GetServerInfo() +{ +char request[] = ""; +int ret; + +currParser = &parserServerInfo; +((PARSER_GET_SERVER_INFO*)currParser)->SetServerInfoRecvCb(RecvServerInfoDataCb, getServerInfoDataCb); + +nt.Reset(); +nt.SetRxCallback(this, AnsRecv); + +if ((ret = nt.Connect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Transact(request)) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Disconnect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } + +return st_ok; +} +//----------------------------------------------------------------------------- +int SERVCONF::ChgUser(const char * request) +{ +int ret; + +currParser = &parserChgUser; +((PARSER_CHG_USER*)currParser)->SetChgUserRecvCb(RecvChgUserCb, chgUserDataCb); + +nt.Reset(); +nt.SetRxCallback(this, AnsRecv); + +if ((ret = nt.Connect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + printfd(__FILE__, "Error on connect: '%s'\n", errorMsg); + return ret; + } +if ((ret = nt.Transact(request)) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + printfd(__FILE__, "Error on transact: '%s'\n", errorMsg); + return ret; + } +if ((ret = nt.Disconnect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + printfd(__FILE__, "Error on disconnect: '%s'\n", errorMsg); + return ret; + } + +return st_ok; +} +//----------------------------------------------------------------------------- +// TODO: remove this shit! +//----------------------------------------------------------------------------- +int SERVCONF::MsgUser(const char * request) +{ +int ret; + +currParser = &parserSendMessage; +parserSendMessage.SetSendMessageRecvCb(RecvSendMessageCb, sendMessageDataCb); + +nt.Reset(); +nt.SetRxCallback(this, AnsRecv); + +if ((ret = nt.Connect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Transact(request)) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Disconnect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } + +return st_ok; +} +//----------------------------------------------------------------------------- +int SERVCONF::CheckUser(const char * login, const char * password) +{ +char request[255]; +snprintf(request, 255, "", login, password); +int ret; + +currParser = &parserCheckUser; +((PARSER_CHECK_USER*)currParser)->SetCheckUserRecvCb(RecvCheckUserCb, checkUserDataCb); + +nt.Reset(); +nt.SetRxCallback(this, AnsRecv); + +if ((ret = nt.Connect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Transact(request)) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } +if ((ret = nt.Disconnect()) != st_ok) + { + strncpy(errorMsg, nt.GetError(), MAX_ERR_STR_LEN); + return ret; + } + +return st_ok; +} +//----------------------------------------------------------------------------- +int SERVCONF::Start(const char *el, const char **attr) +{ +currParser->ParseStart(el, attr); +return 0; +} +//----------------------------------------------------------------------------- +void SERVCONF::End(const char *el) +{ +currParser->ParseEnd(el); +} +//----------------------------------------------------------------------------- +void SERVCONF::SetUserDataRecvCb(RecvUserDataCb_t f, void * data) +{ +RecvUserDataCb = f; +getUsersDataDataCb = data; +} +//----------------------------------------------------------------------------- +void SERVCONF::SetGetUserDataRecvCb(RecvUserDataCb_t f, void * data) +{ +RecvGetUserDataCb = f; //GET_USER +getUserDataDataCb = data; +} +//----------------------------------------------------------------------------- +void SERVCONF::SetServerInfoRecvCb(RecvServerInfoDataCb_t f, void * data) +{ +RecvServerInfoDataCb = f; +getServerInfoDataCb = data; +} +//----------------------------------------------------------------------------- +void SERVCONF::SetChgUserCb(RecvChgUserCb_t f, void * data) +{ +RecvChgUserCb = f; +chgUserDataCb = data; +} +//----------------------------------------------------------------------------- +void SERVCONF::SetCheckUserCb(RecvCheckUserCb_t f, void * data) +{ +RecvCheckUserCb = f; +checkUserDataCb = data; +} +//----------------------------------------------------------------------------- +void SERVCONF::SetSendMessageCb(RecvSendMessageCb_t f, void * data) +{ +RecvSendMessageCb = f; +sendMessageDataCb = data; +} +//----------------------------------------------------------------------------- +char * SERVCONF::GetStrError() +{ +return errorMsg; +} +//----------------------------------------------------------------------------- +int SERVCONF::GetError() +{ +int e = error; +error = 0; +return e; +} +//----------------------------------------------------------------------------- + diff --git a/stglibs/srvconf.lib/servconf.h b/stglibs/srvconf.lib/servconf.h new file mode 100644 index 00000000..f3beaec3 --- /dev/null +++ b/stglibs/srvconf.lib/servconf.h @@ -0,0 +1,309 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.10 $ + $Date: 2009/03/17 09:52:35 $ + $Author: faust $ + */ + +#ifndef SERVCONF_H +#define SERVCONF_H + +#include "os_int.h" + +#include +#include +#include +#include "netunit.h" +#include "stg_const.h" + +void Start(void *data, const char *el, const char **attr); +void End(void *data, const char *el); + +#define MAX_ERR_STR_LEN (64) +#define IP_STRING_LEN (255) +#define UNAME_LEN (256) +#define SERV_VER_LEN (64) +#define DIRNAME_LEN (16) + +//----------------------------------------------------------------------------- +struct STAT +{ + long long su[DIR_NUM]; + long long sd[DIR_NUM]; + long long mu[DIR_NUM]; + long long md[DIR_NUM]; + double freeMb; +}; +//----------------------------------------------------------------------------- +struct SERVERINFO +{ + std::string version; + int tariffNum; + int tariffType; + int usersNum; + std::string uname; + int dirNum; + std::string dirName[DIR_NUM]; +}; +//----------------------------------------------------------------------------- +struct USERDATA +{ + std::string login; + std::string password; + double cash; + double credit; + double lastCash; + double prepaidTraff; + int down; + int passive; + int disableDetailStat; + int connected; + int alwaysOnline; + uint32_t ip; + std::string ips; + std::string tariff; + std::string iface; + std::string group; + std::string note; + std::string email; + std::string name; + std::string address; + std::string phone; + STAT stat; + std::string userData[USERDATA_NUM]; + + struct USERDATA * next; +}; +//----------------------------------------------------------------------------- +typedef void(*RecvUserDataCb_t)(USERDATA * ud, void * data); +typedef void(*RecvServerInfoDataCb_t)(SERVERINFO * si, void * data); +typedef int(*RecvChgUserCb_t)(const char * asnwer, void * data); +typedef int(*RecvCheckUserCb_t)(const char * answer, void * data); +typedef int(*RecvSendMessageCb_t)(const char * answer, void * data); +//----------------------------------------------------------------------------- +struct ADMINDATA +{ + char login[ADM_LOGIN_LEN]; +}; +//----------------------------------------------------------------------------- +class PARSER +{ +public: + PARSER(); + virtual ~PARSER(){}; + virtual int ParseStart(const char *el, const char **attr) = 0; + virtual void ParseEnd(const char *el) = 0; + void Reset(); + //virtual bool GetError() = 0; + //virtual void SetUserDataRecvCb(RecvUserDataCb_t) = 0; +protected: + //RecvUserDataCb_t RecvUserDataCb; +private: +}; +//----------------------------------------------------------------------------- +class PARSER_CHG_USER: public PARSER +{ +public: + PARSER_CHG_USER(); + int ParseStart(const char *el, const char **attr); + void ParseEnd(const char *el); + void Reset(); + void ParseAnswer(const char *el, const char **attr); + void SetChgUserRecvCb(RecvChgUserCb_t, void * data); +private: + RecvChgUserCb_t RecvChgUserCb; + void * chgUserCbData; + int depth; + bool error; +}; +//----------------------------------------------------------------------------- +class PARSER_CHECK_USER: public PARSER +{ +public: + PARSER_CHECK_USER(); + int ParseStart(const char *el, const char **attr); + void ParseEnd(const char *el); + void Reset(); + void ParseAnswer(const char *el, const char **attr); + void SetCheckUserRecvCb(RecvCheckUserCb_t, void * data); +private: + RecvCheckUserCb_t RecvCheckUserCb; + void * checkUserCbData; + int depth; + bool error; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_USERS: public PARSER +{ +public: + PARSER_GET_USERS(); + int ParseStart(const char *el, const char **attr); + void ParseEnd(const char *el); + void Reset(); + void ParseUsers(const char *el, const char **attr); + void ParseUser(const char *el, const char **attr); + void ParseUserParams(const char *el, const char **attr); + void ParseUserLoadStat(const char * el, const char ** attr); + //bool GetError(); + void SetUserDataRecvCb(RecvUserDataCb_t, void * data); +private: + RecvUserDataCb_t RecvUserDataCb; + void * userDataCb; + USERDATA user; + int depth; + bool error; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_USER: public PARSER +{ +public: + PARSER_GET_USER(); + int ParseStart(const char *el, const char **attr); + void ParseEnd(const char *el); + void Reset(); + void ParseUsers(const char *el, const char **attr); + void ParseUser(const char *el, const char **attr); + void ParseUserParams(const char *el, const char **attr); + void ParseUserLoadStat(const char * el, const char ** attr); + void SetUserDataRecvCb(RecvUserDataCb_t, void * data); +private: + RecvUserDataCb_t RecvUserDataCb; + void * userDataCb; + USERDATA user; + int depth; + bool error; +}; +//----------------------------------------------------------------------------- +class PARSER_GET_SERVER_INFO: public PARSER +{ +public: + PARSER_GET_SERVER_INFO(); + int ParseStart(const char *el, const char **attr); + void ParseEnd(const char *el); + void Reset(); + void ParseServerInfo(const char *el, const char **attr); + bool GetError(); + void SetServerInfoRecvCb(RecvServerInfoDataCb_t, void * data); +private: + void ParseUname(const char ** attr); + void ParseServerVersion(const char ** attr); + void ParseUsersNum(const char ** attr); + void ParseTariffsNum(const char ** attr); + void ParseTariffType(const char ** attr); + void ParseDirNum(const char **attr); + void ParseDirName(const char **attr, int d); + + RecvServerInfoDataCb_t RecvServerInfoDataCb; + void * serverInfoDataCb; + USERDATA user; + int depth; + bool error; + SERVERINFO serverInfo; +}; +//----------------------------------------------------------------------------- +class PARSER_SEND_MESSAGE: public PARSER +{ +public: + PARSER_SEND_MESSAGE(); + int ParseStart(const char *el, const char **attr); + void ParseEnd(const char *el); + void Reset(); + void ParseAnswer(const char *el, const char **attr); + void SetSendMessageRecvCb(RecvSendMessageCb_t, void * data); +private: + RecvSendMessageCb_t RecvSendMessageCb; + void * sendMessageCbData; + int depth; + bool error; +}; +//----------------------------------------------------------------------------- +class SERVCONF +{ +public: + SERVCONF(); + void SetServer(const char * server); + void SetPort(uint16_t port); + + void SetAdmLogin(const char * login); + void SetAdmPassword(const char * password); + + void SetUserDataRecvCb(RecvUserDataCb_t, void * data); + void SetServerInfoRecvCb(RecvServerInfoDataCb_t, void * data); + void SetChgUserCb(RecvChgUserCb_t, void * data); + void SetCheckUserCb(RecvCheckUserCb_t, void * data); + void SetGetUserDataRecvCb(RecvUserDataCb_t, void * data); + void SetSendMessageCb(RecvSendMessageCb_t, void * data); + + int GetUsers(); + int GetUser(const char * login); + int ChgUser(const char * request); + // TODO: Remove this shit! + int MsgUser(const char * request); + int SendMessage(const char * login, const char * message, int prio); + int GetServerInfo(); + int CheckUser(const char * login, const char * password); + + char * GetStrError(); + int GetError(); + int Start(const char *el, const char **attr); + void End(const char *el); + +private: + PARSER * currParser; + + PARSER_GET_USERS parserGetUsers; + PARSER_GET_USER parserGetUser; + PARSER_GET_SERVER_INFO parserServerInfo; + PARSER_CHG_USER parserChgUser; + PARSER_CHECK_USER parserCheckUser; + PARSER_SEND_MESSAGE parserSendMessage; + + NETTRANSACT nt; + int parseDepth; + USERDATA ud; + + char errorMsg[MAX_ERR_STR_LEN]; + int error; + XML_Parser parser; + + RecvUserDataCb_t RecvUserDataCb; + RecvUserDataCb_t RecvGetUserDataCb; + RecvServerInfoDataCb_t RecvServerInfoDataCb; + RecvChgUserCb_t RecvChgUserCb; + RecvCheckUserCb_t RecvCheckUserCb; + RecvSendMessageCb_t RecvSendMessageCb; + + void * getUserDataDataCb; + void * getUsersDataDataCb; + void * getServerInfoDataCb; + void * chgUserDataCb; + void * checkUserDataCb; + void * sendMessageDataCb; + + friend int AnsRecv(void * data, std::list * list); +}; +//----------------------------------------------------------------------------- + +#endif /* _SERVCONF_H_ */ + +/* EOF */ + diff --git a/stglibs/srvconf.lib/servconf.vpj b/stglibs/srvconf.lib/servconf.vpj new file mode 100644 index 00000000..c5f9b3c6 --- /dev/null +++ b/stglibs/srvconf.lib/servconf.vpj @@ -0,0 +1,191 @@ + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stglibs/srvconf.lib/servconf.vpw b/stglibs/srvconf.lib/servconf.vpw new file mode 100644 index 00000000..96ebe79c --- /dev/null +++ b/stglibs/srvconf.lib/servconf.vpw @@ -0,0 +1,4 @@ +[Global] +Version=8 +[ProjectFiles] +servconf.vpj diff --git a/stglibs/srvconf.lib/test.cpp b/stglibs/srvconf.lib/test.cpp new file mode 100644 index 00000000..092ffce6 --- /dev/null +++ b/stglibs/srvconf.lib/test.cpp @@ -0,0 +1,171 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + + /* + $Revision: 1.6 $ + $Date: 2008/02/09 16:22:18 $ + $Author: nobunaga $ + */ + +#include +#include +#include "servconf.h" + +//----------------------------------------------------------------------------- +void RecvUserData(USERDATA * ud, void * d) +{ +// ôÕÔ ×Ù×ÏÄÉÔÓÑ ÞÁÓÔØ ÉÎÆÙ Ï ÐÏÌØÚÏ×ÁÔÅÌÅ, ÎÏ × ud ÐÅÒÅÄÁÅÔÓÑ ×ÓÑ ÉÎÆÁ +printf("login: %s password :%s cash:%8.2f ip:%16s\n", ud->login, ud->password, ud->cash, ud->ips); +} +//----------------------------------------------------------------------------- +void RecvServerInfoData(SERVERINFO * si, void * d) +{ +// ôÕÔ ÔÏÖÅ ÔÏÌØËÏ ÞÁÓÔØ ÉÎÆÙ ×Ù×ÏÄÉÔÓÑ ÎÁ ÜËÒÁÎ +printf("uname: %20s\n", si->uname); +printf("version: %20s\n", si->version); +printf("users: %20d\n", si->usersNum); +for (int i = 0; i < DIR_NUM; i++) + { + printf("dir name 1: >%16s<\n", si->dirName[i]); + } +} +//----------------------------------------------------------------------------- +int RecvSetUserAnswer(const char * ans, void * d) +{ +printf("ans=%s\n", ans); +if (strcasecmp("Ok", ans) == 0) + *((bool*)d) = true; +else + *((bool*)d) = false; + +return 0; +} +//----------------------------------------------------------------------------- +int RecvCheckUserAnswer(const char * ans, void * d) +{ +if (strcmp("Ok", ans) == 0) + *((bool*)d) = true; +else + *((bool*)d) = false; +return 0; +} +//----------------------------------------------------------------------------- +int RecvSendMessageAnswer(const char * ans, void * d) +{ +if (strcasecmp("Ok", ans) == 0) + *((bool*)d) = true; +else + *((bool*)d) = false; +return 0; +} +//----------------------------------------------------------------------------- +int main() +{ +SERVCONF sc; +int ret; +bool userExist = false; +bool result = false; + +sc.SetServer("127.0.0.1"); // õÓÔÁÎÁ×ÌÉ×ÁÅÍ ÉÍÑ ÓÅÒ×ÅÒÁ Ó ËÏÔÏÒÇÏ ÚÁÂÉÒÁÔØ ÉÎÆÕ +sc.SetPort(5555); // ÁÄÍÉÎÓËÉÊ ÐÏÒÔ ÓÅÒ×ÅÒÁÐÏÒÔ +sc.SetAdmLogin("admin"); // ÷ÙÓÔÁ×ÌÑÅÍ ÌÏÇÉÎ É ÐÁÒÏÌØ ÁÄÍÉÎÁ +sc.SetAdmPassword("123456"); + +sc.SetUserDataRecvCb(RecvUserData, NULL); // óÔÁ×ÉÍ ËÏÌÂÜË-ÆÕÎËÃÉÉ, ËÏÔÏÒÙÅ +sc.SetGetUserDataRecvCb(RecvUserData, NULL); // GET USER +sc.SetServerInfoRecvCb(RecvServerInfoData, NULL); // ÂÕÄÕÔ ×ÙÚ×ÁÎÙ ÐÒÉ ÐÏÌÕÞÅÎÉÉ ÉÎÆÏÒÍÁÃÉÉ Ó ÓÅÒ×ÅÒÁ +sc.SetChgUserCb(RecvSetUserAnswer, &userExist); +sc.SetCheckUserCb(RecvCheckUserAnswer, &userExist); +sc.SetSendMessageCb(RecvSendMessageAnswer, &result); +printf("--------------- GetServerInfo ---------------\n"); +ret = sc.GetServerInfo(); // úÁÐÒÁÛÉ×ÁÅÍ ÉÎÆÕ Ï ÓÅÒ×ÅÒÅ. üÔÏ ÍÏÖÎÏ ÉÓÐÏÌØÚÏ×ÁÔØ +if (ret != st_ok) // ÄÌÑ ÐÒÏ×ÅÒËÉ ÌÏÇÉÎÁ É ÐÁÒÏÌÑ ÁÄÍÉÎÁ + { + printf("error %d %s\n", ret, sc.GetStrError()); + return 0; + } + +/*printf("--------------- GetUsers ---------------\n"); +ret = sc.GetUsers(); // úÁÐÒÁÛÉ×ÁÅÍ ÉÎÆÕ Ï ÐÏÌØÚÏ×ÁÔÅÌÅ +if (ret != st_ok) + { + printf("error %d %s\n", ret, sc.GetStrError()); + return 0; + }*/ + +printf("--------------- SendMessage ---------------\n"); +ret = sc.SendMessage("zubr11", "test", 0); // +if (ret != st_ok) + { + printf("error %d %s\n", ret, sc.GetStrError()); + return 0; + } +if (result) + printf("SendMessage ok\n"); +else + printf("SendMessage failed\n"); + +return 0; + +printf("--------------- GetUser ---------------\n"); +ret = sc.GetUser("test"); // úÁÐÒÁÛÉ×ÁÅÍ ÉÎÆÕ Ï ÐÏÌØÚÏ×ÁÔÅÌÅ +if (ret != st_ok) + { + printf("error %d %s\n", ret, sc.GetStrError()); + return 0; + } + +return 0; + +printf("--------------- CheckUser ---------------\n"); +sc.CheckUser("test", "123456"); +if (userExist) + printf("login - ok\n"); +else + printf("login failed\n"); + +printf("--------------- ChgUser ON ---------------\n"); +char req[1024]; +sprintf(req, " " + " " + " " + " " + ""); +sc.ChgUser(req); +if (userExist) + printf("chg user ok\n"); +else + printf("chg user error\n"); + +printf("--------------- ChgUser OFF ---------------\n"); +sprintf(req, " " + " " + " "); + +sc.ChgUser(req); +if (userExist) + printf("chg user ok\n"); +else + printf("chg user error\n"); + +return 0; +} +//----------------------------------------------------------------------------- + diff --git a/stglibs/srvconf.lib/test.sh b/stglibs/srvconf.lib/test.sh new file mode 100755 index 00000000..95b99137 --- /dev/null +++ b/stglibs/srvconf.lib/test.sh @@ -0,0 +1 @@ +gcc -g3 test.cpp -L../../lib ./libsrvconf.a -lexpat ../../lib/libstg_common.so ../../lib/libstg_crypto.so -I../../include diff --git a/stglibs/stg_locker.lib/Makefile b/stglibs/stg_locker.lib/Makefile new file mode 100644 index 00000000..5f99d5c7 --- /dev/null +++ b/stglibs/stg_locker.lib/Makefile @@ -0,0 +1,14 @@ +############################################################################### +# $Id: Makefile,v 1.2 2008/12/04 17:11:55 faust Exp $ +############################################################################### + +LIB_NAME = stg_locker +PROG = lib$(LIB_NAME) + +SRCS = stg_locker.cpp + +INCS = stg_locker.h + +LIBS=$(LIB_THREAD) + +include ../Makefile.in diff --git a/stglibs/stg_locker.lib/stg_locker.cpp b/stglibs/stg_locker.lib/stg_locker.cpp new file mode 100644 index 00000000..be0aa006 --- /dev/null +++ b/stglibs/stg_locker.lib/stg_locker.cpp @@ -0,0 +1,38 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.2 $ + $Date: 2007/11/30 08:57:49 $ + $Author: nobunaga $ +*/ + + + +#include +#include "stg_locker.h" + +#ifdef DEBUG_LOCKER + +long long STG_LOCKER::id = 0; +pthread_mutex_t STG_LOCKER::lockerMutex = PTHREAD_MUTEX_INITIALIZER; + +#endif + diff --git a/stglibs/stg_locker.lib/stg_locker.h b/stglibs/stg_locker.lib/stg_locker.h new file mode 100644 index 00000000..65833688 --- /dev/null +++ b/stglibs/stg_locker.lib/stg_locker.h @@ -0,0 +1,92 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.5 $ + $Date: 2010/03/04 11:57:11 $ + $Author: faust $ +*/ + + +#ifndef STG_LOCKER_H +#define STG_LOCKER_H + +#include +#include "noncopyable.h" + +#ifdef DEBUG_LOCKER + +#include +#include +#include + +#endif +//----------------------------------------------------------------------------- +class STG_LOCKER : private NONCOPYABLE +{ +public: + #ifdef DEBUG_LOCKER + STG_LOCKER(pthread_mutex_t * m, const char * __file__, int __line__) + : mutex(m), + file(__file__), + line(__line__), + lockID(0) + #else + STG_LOCKER(pthread_mutex_t * m, const char *, int) + : mutex(m) + #endif + { + mutex = m; + #ifdef DEBUG_LOCKER + pthread_mutex_lock(&lockerMutex); + file = __file__; + line = __line__; + if (id == 0) + pthread_mutex_init(&lockerMutex, NULL); + + lockID = ++id; + std::cout << "Lock: " << lockID << " " << file << ":" << line << " " << mutex << " " << pthread_self() << std::endl; + pthread_mutex_unlock(&lockerMutex); + #endif + pthread_mutex_lock(mutex); + }; + + ~STG_LOCKER() + { + pthread_mutex_unlock(mutex); + #ifdef DEBUG_LOCKER + pthread_mutex_lock(&lockerMutex); + std::cout << "Unlock: " << lockID << " " << file << ":" << line << " " << mutex << " " << pthread_self() << std::endl; + pthread_mutex_unlock(&lockerMutex); + #endif + }; +private: + pthread_mutex_t * mutex; + #ifdef DEBUG_LOCKER + std::string file; + int line; + static pthread_mutex_t lockerMutex; + static long long id; + long long lockID; + #endif +}; +//----------------------------------------------------------------------------- + +#endif //STG_LOCKER_H diff --git a/stglibs/stg_logger.lib/Makefile b/stglibs/stg_logger.lib/Makefile new file mode 100644 index 00000000..e19b8928 --- /dev/null +++ b/stglibs/stg_logger.lib/Makefile @@ -0,0 +1,12 @@ +############################################################################### +# $Id: Makefile,v 1.3 2007/05/08 14:29:21 faust Exp $ +############################################################################### + +LIB_NAME = stg_logger +PROG = lib$(LIB_NAME) + +SRCS = stg_logger.cpp + +INCS = stg_logger.h + +include ../Makefile.in diff --git a/stglibs/stg_logger.lib/stg_logger.cpp b/stglibs/stg_logger.lib/stg_logger.cpp new file mode 100644 index 00000000..cb96e506 --- /dev/null +++ b/stglibs/stg_logger.lib/stg_logger.cpp @@ -0,0 +1,94 @@ +#include +#include +#include + +#include "stg_logger.h" + +#ifdef STG_TIME +extern const volatile time_t stgTime; +#endif +//----------------------------------------------------------------------------- +STG_LOGGER & GetStgLogger() +{ +static STG_LOGGER logger; +return logger; +} +//----------------------------------------------------------------------------- +STG_LOGGER::STG_LOGGER() + : fileName() +{ +pthread_mutex_init(&mutex, NULL); +} +//----------------------------------------------------------------------------- +STG_LOGGER::~STG_LOGGER() +{ +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +void STG_LOGGER::SetLogFileName(const std::string & fn) +{ +STG_LOGGER_LOCKER lock(&mutex); +fileName = fn; +} +//----------------------------------------------------------------------------- +void STG_LOGGER::operator()(const char * fmt, ...) +{ +STG_LOGGER_LOCKER lock(&mutex); + +char buff[2048]; + +va_list vl; +va_start(vl, fmt); +vsnprintf(buff, sizeof(buff), fmt, vl); +va_end(vl); + +FILE * f; +if (!fileName.empty()) + { + f = fopen(fileName.c_str(), "at"); + if (f) + { + #ifdef STG_TIME + fprintf(f, "%s", LogDate(stgTime)); + #else + fprintf(f, "%s", LogDate(time(NULL))); + #endif + fprintf(f, " -- "); + fprintf(f, "%s", buff); + fprintf(f, "\n"); + fclose(f); + } + else + { + openlog("stg", LOG_NDELAY, LOG_USER); + syslog(LOG_CRIT, "%s", buff); + closelog(); + } + } +else + { + openlog("stg", LOG_NDELAY, LOG_USER); + syslog(LOG_CRIT, "%s", buff); + closelog(); + } +} +//----------------------------------------------------------------------------- +const char * STG_LOGGER::LogDate(time_t t) +{ +static char s[32]; +if (t == 0) + t = time(NULL); + +struct tm * tt = localtime(&t); + +snprintf(s, 32, "%d-%s%d-%s%d %s%d:%s%d:%s%d", + tt->tm_year + 1900, + tt->tm_mon + 1 < 10 ? "0" : "", tt->tm_mon + 1, + tt->tm_mday < 10 ? "0" : "", tt->tm_mday, + tt->tm_hour < 10 ? "0" : "", tt->tm_hour, + tt->tm_min < 10 ? "0" : "", tt->tm_min, + tt->tm_sec < 10 ? "0" : "", tt->tm_sec); + +return s; +} +//----------------------------------------------------------------------------- diff --git a/stglibs/stg_logger.lib/stg_logger.h b/stglibs/stg_logger.lib/stg_logger.h new file mode 100644 index 00000000..3f9eed09 --- /dev/null +++ b/stglibs/stg_logger.lib/stg_logger.h @@ -0,0 +1,40 @@ +#ifndef STG_LOGGER_H +#define STG_LOGGER_H + +#include +#include +#include "noncopyable.h" + +const char * LogDate(time_t t); +//----------------------------------------------------------------------------- +class STG_LOGGER; +STG_LOGGER & GetStgLogger(); +//----------------------------------------------------------------------------- +class STG_LOGGER_LOCKER : private NONCOPYABLE +{ +public: + STG_LOGGER_LOCKER(pthread_mutex_t * m) : mutex(m) { pthread_mutex_lock(mutex); }; + ~STG_LOGGER_LOCKER() { pthread_mutex_unlock(mutex); }; +private: + pthread_mutex_t * mutex; +}; +//----------------------------------------------------------------------------- +class STG_LOGGER +{ +friend STG_LOGGER & GetStgLogger(); + +public: + ~STG_LOGGER(); + void SetLogFileName(const std::string & fn); + void operator()(const char * fmt, ...); + +private: + STG_LOGGER(); + const char * LogDate(time_t t); + + std::string fileName; + pthread_mutex_t mutex; +}; +//----------------------------------------------------------------------------- + +#endif //STG_LOGGER_H -- 2.43.2

@ntA{OV^T^YSvo!{Nd=CHv%hqp{s&ra1a@xNvHh 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%0A?0Xkr06}E*(7D!Ueuv%u z=8ux9?ec3an~2=?8(a7LpTU>;bTO*O+2YUnbW* z4~ZK#L{$h^Dmv?$Y1z~&-@rs;z)Om_KQ1C7B-$3qy86Vcny?dl3MlnB0MX`6`H!5V za9Pskut6Edf3E5Nww~%BWDM-@uGn6+SmHKyg3Gpx{xS{_yJc`gaO@mF~jyXSiwFr6Mh3{_%!!AbMW=B!hPdO=m`5>=6$OLY$Sp|pMovYKw#r_}37?$FZf zN$!Ev>q*<`lP!?vY5NGQCpl-o*W1N|+@GGpW=rXK>pjpV(~bB1c;cTR%~yBE>N5n% z3_nViL8Pf{?%7If`Nrtb0CX837hyH&^6Y%i{WU~_HO97>uMY#!p3y62y=XH$Z5yc> zg@mVjQFC~@7e$Asd(momx(~fR{c(L*ez4W_q_V_|LECpRXhSXrZHVh_-Jq7hS{H-1 z@1oE4O`go?%8WG<17Z)o#(XA^Um|3;JgiqxRm2}l)+9X$STb_`tDFYf6s7 zD6^Bz&0vM%UEPgIKNCe~~uyAydNhY(UMS_i0Xy>chNs(SovB9l z#r)gL->y&YFo7Nu&8wA=;|ZGR@xuhQoMKbmMP-dclw8Bh+Bu{YU&4p^Yq5X_+A0@Mq-=( zFl^HwgZaxv`pvcf4)=my0g(t#^qB=cE8+6KT9aCLNezgnFQvc>FJ9X))dyIyFVA>+AFnMx*`ThLu`J3tG z+m~-<0?jRQI7Kx+!uK2cACU6x;e_6F*t$PhvoP75(vqf7{AkSL{?A@cobvPnO&ooE z%4r1nsj&-jw)O~d+d}>o|C~BP#^3Tv?E&IL(+haS{SdoHzh#{R zI-e~f*PTy?Ruz(Bls}D=rPo^@Q?i_urNH^QxWl!*Yft8|hkV|HOIyH3B69&kR2R@# zFUOoD8IE&>j!hp#vyjEEN0z1v9niFb;IG@uj@ba%0AIH@mF(OiwXd>zom68z(xd}A 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 +#include +#include + +#include +#include + +#include "user_ips.h" +#include "user_conf.h" +#include "user_stat.h" +#include "mysql_store.h" +#include "blowfish.h" + +#define adm_enc_passwd "cjeifY8m3" +char qbuf[4096]; + +using namespace std; + +const int pt_mega = 1024 * 1024; +const string badSyms = "'`"; +const char repSym = '\"'; +const int RepitTimes = 3; + +int GetInt(const string & str, int * val, int defaultVal) +{ + char *res; + + *val = strtol(str.c_str(), &res, 10); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +int GetDouble(const string & str, double * val, double defaultVal) +{ + char *res; + + *val = strtod(str.c_str(), &res); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +int GetTime(const string & str, time_t * val, time_t defaultVal) +{ + char *res; + + *val = strtol(str.c_str(), &res, 10); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +//----------------------------------------------------------------------------- +string ReplaceStr(string source, const string symlist, const char chgsym) +{ + string::size_type pos=0; + + while( (pos = source.find_first_of(symlist,pos)) != string::npos) + source.replace(pos, 1,1, chgsym); + + return source; +} + +int GetULongLongInt(const string & str, uint64_t * val, uint64_t defaultVal) +{ + char *res; + + *val = strtoull(str.c_str(), &res, 10); + + if (*res != 0) + { + *val = defaultVal; //Error! + return EINVAL; + } + + return 0; +} + +class STORE_CREATOR +{ +private: + BASE_STORE * bs; + +public: + STORE_CREATOR() + { + bs = new MYSQL_STORE(); + }; + ~STORE_CREATOR() + { + if (bs) + delete bs; + }; + + BASE_STORE * GetStore() + { + return bs; + }; +}; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//STORE_CREATOR sc; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +BASE_STORE * GetStore() +{ +//return sc.GetStore(); +return new MYSQL_STORE(); +} +//----------------------------------------------------------------------------- +MYSQL_STORE_SETTINGS::MYSQL_STORE_SETTINGS() +{ +} +//----------------------------------------------------------------------------- +MYSQL_STORE_SETTINGS::~MYSQL_STORE_SETTINGS() +{ + +} +//----------------------------------------------------------------------------- +int MYSQL_STORE_SETTINGS::ParseParam(const vector & moduleParams, + const string & name, string & result) +{ +PARAM_VALUE pv; +pv.param = name; +vector::const_iterator pvi; +pvi = find(moduleParams.begin(), moduleParams.end(), pv); +if (pvi == moduleParams.end()) + { + errorStr = "Parameter \'" + name + "\' not found."; + return -1; + } + +result = pvi->value[0]; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE_SETTINGS::ParseSettings(const MODULE_SETTINGS & s) +{ +if (ParseParam(s.moduleParams, "dbuser", dbUser) < 0) + return -1; +if (ParseParam(s.moduleParams, "rootdbpass", dbPass) < 0) + return -1; +if (ParseParam(s.moduleParams, "dbname", dbName) < 0) + return -1; +if (ParseParam(s.moduleParams, "dbhost", dbHost) < 0) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +const string & MYSQL_STORE_SETTINGS::GetStrError() const +{ +return errorStr; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBUser() const +{ +return dbUser; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBPassword() const +{ +return dbPass; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBHost() const +{ +return dbHost; +} +//----------------------------------------------------------------------------- +string MYSQL_STORE_SETTINGS::GetDBName() const +{ +return dbName; +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +MYSQL_STORE::MYSQL_STORE() +{ +version = "mysql_store v.0.67"; +}; +//----------------------------------------------------------------------------- +MYSQL_STORE::~MYSQL_STORE() +{ +}; +//----------------------------------------------------------------------------- +void MYSQL_STORE::SetSettings(const MODULE_SETTINGS & s) +{ +settings = s; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::MysqlQuery(const char* sQuery,MYSQL * sock) const +{ + int ret,i; + + if( (ret = mysql_query(sock,sQuery)) ) + { + for(i=0; i * ParamList, + const string & table, const string & name) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock=NULL; +unsigned int num,i; + +ParamList->clear(); + +sprintf(qbuf,"SELECT %s FROM %s", name.c_str(), table.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't GetAllParams Query for: "; + errorStr += name + " - " + table + "\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't GetAllParams Results for: "; + errorStr += name + " - " + table + "\n"; + errorStr += mysql_error(sock); + return -1; +} + +num = mysql_num_rows(res); + +for(i=0;ipush_back(row[0]); +} + +mysql_free_result(res); +mysql_close(sock); + +return 0; +} + +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetUsersList(vector * usersList) const +{ +if(GetAllParams(usersList, "users", "login")) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetAdminsList(vector * adminsList) const +{ +if(GetAllParams(adminsList, "admins", "login")) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetTariffsList(vector * tariffsList) const +{ +if(GetAllParams(tariffsList, "tariffs", "name")) + return -1; + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::AddUser(const string & login) const +{ +sprintf(qbuf,"INSERT INTO users SET login='%s'", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add user:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::DelUser(const string & login) const +{ +sprintf(qbuf,"DELETE FROM users WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete user:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreUserConf(USER_CONF * conf, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +string query; + +query = "SELECT login, Password, Passive, Down, DisabledDetailStat, \ + AlwaysOnline, Tariff, Address, Phone, Email, Note, \ + RealName, StgGroup, Credit, TariffChange, "; + +for (int i = 0; i < USERDATA_NUM; i++) +{ + sprintf(qbuf, "Userdata%d, ", i); + query += qbuf; +} + +query += "CreditExpire, IP FROM users WHERE login='"; +query += login + "' LIMIT 1"; + +//sprintf(qbuf,"SELECT * FROM users WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlGetQuery(query.c_str(),sock)) +{ + errorStr = "Couldn't restore Tariff(on query):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore Tariff(on getting result):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +string param; + +conf->password = row[1]; + +if (conf->password.empty()) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' password is blank."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[2],&conf->passive, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter Passive."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[3], &conf->disabled, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter Down."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[4], &conf->disabledDetailStat, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter DisabledDetailStat."; + mysql_close(sock); + return -1; + } + +if (GetInt(row[5], &conf->alwaysOnline, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter AlwaysOnline."; + mysql_close(sock); + return -1; + } + +conf->tariffName = row[6]; + +if (conf->tariffName.empty()) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' tariff is blank."; + mysql_close(sock); + return -1; + } + +conf->address = row[7]; +conf->phone = row[8]; +conf->email = row[9]; +conf->note = row[10]; +conf->realName = row[11]; +conf->group = row[12]; + +if (GetDouble(row[13], &conf->credit, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter Credit."; + mysql_close(sock); + return -1; + } + +conf->nextTariff = row[14]; + +for (int i = 0; i < USERDATA_NUM; i++) + { + conf->userdata[i] = row[15+i]; + } + +GetTime(row[15+USERDATA_NUM], &conf->creditExpire, 0); + +string ipStr = row[16+USERDATA_NUM]; +USER_IPS i; +try + { + i = StrToIPS(ipStr); + } +catch (string s) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' data not read. Parameter IP address. " + s; + mysql_close(sock); + return -1; + } +conf->ips = i; + +mysql_free_result(res); +mysql_close(sock); + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreUserStat(USER_STAT * stat, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; + +string query; + +query = "SELECT "; + +for (int i = 0; i < DIR_NUM; i++) +{ + sprintf(qbuf, "D%d, U%d, ", i, i); + query += qbuf; +} + +query += "Cash, FreeMb, LastCashAdd, LastCashAddTime, PassiveTime, LastActivityTime \ + FROM users WHERE login = '"; +query += login + "'"; + +//sprintf(qbuf,"SELECT * FROM users WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlGetQuery(query.c_str() ,sock)) +{ + errorStr = "Couldn't restore UserStat(on query):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore UserStat(on getting result):\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +unsigned int startPos=0; + +char s[22]; +uint64_t traffU[DIR_NUM]; +uint64_t traffD[DIR_NUM]; + +for (int i = 0; i < DIR_NUM; i++) + { + sprintf(s, "D%d", i); + if (GetULongLongInt(row[startPos+i*2],&traffD[i], 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter " + string(s); + mysql_close(sock); + return -1; + } + stat->down = traffD; + + sprintf(s, "U%d", i); + if (GetULongLongInt(row[startPos+i*2+1], &traffU[i], 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter " + string(s); + mysql_close(sock); + return -1; + } + stat->up = traffU; + }//for + +startPos += (2*DIR_NUM); + +if (GetDouble(row[startPos], &stat->cash, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter Cash"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[startPos+1],&stat->freeMb, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter FreeMb"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[startPos+2], &stat->lastCashAdd, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter LastCashAdd"; + mysql_close(sock); + return -1; + } + +if (GetTime(row[startPos+3], &stat->lastCashAddTime, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter LastCashAddTime"; + mysql_close(sock); + return -1; + } + +if (GetTime(row[startPos+4], &stat->passiveTime, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter PassiveTime"; + mysql_close(sock); + return -1; + } + +if (GetTime(row[startPos+5], &stat->lastActivityTime, 0) != 0) + { + mysql_free_result(res); + errorStr = "User \'" + login + "\' stat not read. Parameter LastActivityTime"; + mysql_close(sock); + return -1; + } + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveUserConf(const USER_CONF & conf, const string & login) const +{ +string param; +string res; + +strprintf(&res,"UPDATE users SET Password='%s', Passive=%d, Down=%d, DisabledDetailStat = %d, "\ + "AlwaysOnline=%d, Tariff='%s', Address='%s', Phone='%s', Email='%s', "\ + "Note='%s', RealName='%s', StgGroup='%s', Credit=%f, TariffChange='%s', ", + conf.password.c_str(), + conf.passive, + conf.disabled, + conf.disabledDetailStat, + conf.alwaysOnline, + conf.tariffName.c_str(), + (ReplaceStr(conf.address,badSyms,repSym)).c_str(), + (ReplaceStr(conf.phone,badSyms,repSym)).c_str(), + (ReplaceStr(conf.email,badSyms,repSym)).c_str(), + (ReplaceStr(conf.note,badSyms,repSym)).c_str(), + (ReplaceStr(conf.realName,badSyms,repSym)).c_str(), + (ReplaceStr(conf.group,badSyms,repSym)).c_str(), + conf.credit, + conf.nextTariff.c_str() + ); + +for (int i = 0; i < USERDATA_NUM; i++) + { + strprintf(¶m, " Userdata%d='%s',", i, + (ReplaceStr(conf.userdata[i],badSyms,repSym)).c_str()); + res += param; + } + +strprintf(¶m, " CreditExpire=%d,", conf.creditExpire); +res += param; + +stringstream ipStr; +ipStr << conf.ips; + +strprintf(¶m, " IP='%s'", ipStr.str().c_str()); +res += param; + +strprintf(¶m, " WHERE login='%s' LIMIT 1", login.c_str()); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't save user conf:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveUserStat(const USER_STAT & stat, const string & login) const +{ +string param; +string res; + +res = "UPDATE users SET"; + +for (int i = 0; i < DIR_NUM; i++) + { + strprintf(¶m, " D%d=%lld,", i, stat.down[i]); + res += param; + + strprintf(¶m, " U%d=%lld,", i, stat.up[i]); + res += param; + } + +strprintf(¶m, " Cash=%f, FreeMb=%f, LastCashAdd=%f, LastCashAddTime=%d,"\ + " PassiveTime=%d, LastActivityTime=%d", + stat.cash, + stat.freeMb, + stat.lastCashAdd, + stat.lastCashAddTime, + stat.passiveTime, + stat.lastActivityTime + ); +res += param; + +strprintf(¶m, " WHERE login='%s' LIMIT 1", login.c_str()); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't save user stat:\n"; +// errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteLogString(const string & str, const string & login) const +{ +string res, tempStr; +time_t t; +tm * lt; + +t = time(NULL); +lt = localtime(&t); + +MYSQL_RES* result; +MYSQL * sock; +strprintf(&tempStr, "logs_%02d_%4d", lt->tm_mon+1, lt->tm_year+1900); +if (!(sock=MysqlConnect())){ + errorStr = "Couldn't connect to Server"; + return -1; +} +if (!(result=mysql_list_tables(sock,tempStr.c_str() ))) +{ + errorStr = "Couldn't get table " + tempStr + ":\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +unsigned int num_rows = mysql_num_rows(result); + +mysql_free_result(result); + +if (num_rows < 1) +{ + sprintf(qbuf,"CREATE TABLE logs_%02d_%4d (unid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, login VARCHAR(40),text TEXT)", + lt->tm_mon+1, lt->tm_year+1900); + + if(MysqlQuery(qbuf,sock)) + { + errorStr = "Couldn't create WriteDetailedStat table:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; + } +} + +strprintf(&res, "%s -- %s",LogDate(t), str.c_str()); + +string send; + +strprintf(&send,"INSERT INTO logs_%02d_%4d SET login='%s', text='%s'", + lt->tm_mon+1, lt->tm_year+1900, + login.c_str(), (ReplaceStr(res,badSyms,repSym)).c_str()); + +if(MysqlQuery(send.c_str(),sock)) +{ + errorStr = "Couldn't write log string:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} +mysql_close(sock); +return 0; + +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message) const +{ +string userLogMsg = "Admin \'" + admLogin + "\', " + string(inet_ntostr(admIP)) + ": \'" + + paramName + "\' parameter changed from \'" + oldValue + + "\' to \'" + newValue + "\'. " + message; + +return WriteLogString(userLogMsg, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteUserConnect(const string & login, uint32_t ip) const +{ +string logStr = "Connect, " + string(inet_ntostr(ip)); +return WriteLogString(logStr, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb) const +{ +string logStr = "Disconnect, "; +stringstream sssu; +stringstream sssd; +stringstream ssmu; +stringstream ssmd; +stringstream sscash; + +ssmu << up; +ssmd << down; + +sssu << sessionUp; +sssd << sessionDown; + +sscash << cash; + +logStr += " session upload: \'"; +logStr += sssu.str(); +logStr += "\' session download: \'"; +logStr += sssd.str(); +logStr += "\' month upload: \'"; +logStr += ssmu.str(); +logStr += "\' month download: \'"; +logStr += ssmd.str(); +logStr += "\' cash: \'"; +logStr += sscash.str(); +logStr += "\'"; + +return WriteLogString(logStr, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveMonthStat(const USER_STAT & stat, int month, int year, + const string & login) const +{ +string param, res; + +strprintf(&res, "INSERT INTO stat SET login='%s', month=%d, year=%d,", + login.c_str(), month+1, year+1900); + +for (int i = 0; i < DIR_NUM; i++) + { + strprintf(¶m, " U%d=%lld,", i, stat.up[i]); + res += param; + + strprintf(¶m, " D%d=%lld,", i, stat.down[i]); + res += param; + } + +strprintf(¶m, " cash=%f", stat.cash); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't SaveMonthStat:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//-----------------------------------------------------------------------------*/ +int MYSQL_STORE::AddAdmin(const string & login) const +{ +sprintf(qbuf,"INSERT INTO admins SET login='%s'", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//-----------------------------------------------------------------------------*/ +int MYSQL_STORE::DelAdmin(const string & login) const +{ +sprintf(qbuf,"DELETE FROM admins where login='%s' LIMIT 1", login.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//-----------------------------------------------------------------------------*/ +int MYSQL_STORE::SaveAdmin(const ADMIN_CONF & ac) const +{ +char passwordE[2 * ADM_PASSWD_LEN + 2]; +char pass[ADM_PASSWD_LEN + 1]; +char adminPass[ADM_PASSWD_LEN + 1]; + +memset(pass, 0, sizeof(pass)); +memset(adminPass, 0, sizeof(adminPass)); + +BLOWFISH_CTX ctx; +EnDecodeInit(adm_enc_passwd, strlen(adm_enc_passwd), &ctx); + +strncpy(adminPass, ac.password.c_str(), ADM_PASSWD_LEN); +adminPass[ADM_PASSWD_LEN - 1] = 0; + +for (int i = 0; i < ADM_PASSWD_LEN/8; i++) + { + EncodeString(pass + 8*i, adminPass + 8*i, &ctx); + } + +pass[ADM_PASSWD_LEN - 1] = 0; +Encode12(passwordE, pass, ADM_PASSWD_LEN); + +sprintf(qbuf,"UPDATE admins SET password='%s', ChgConf=%d, ChgPassword=%d, "\ + "ChgStat=%d, ChgCash=%d, UsrAddDel=%d, ChgTariff=%d, ChgAdmin=%d "\ + "WHERE login='%s' LIMIT 1", + passwordE, + ac.priv.userConf, + ac.priv.userPasswd, + ac.priv.userStat, + ac.priv.userCash, + ac.priv.userAddDel, + ac.priv.tariffChg, + ac.priv.adminChg, + ac.login.c_str() + ); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't save admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreAdmin(ADMIN_CONF * ac, const string & login) const +{ +char pass[ADM_PASSWD_LEN + 1]; +char password[ADM_PASSWD_LEN + 1]; +char passwordE[2*ADM_PASSWD_LEN + 2]; +BLOWFISH_CTX ctx; + +string p; +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +sprintf(qbuf,"SELECT * FROM admins WHERE login='%s' LIMIT 1", login.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't restore admin:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore admin:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if ( mysql_num_rows(res) == 0) +{ + mysql_free_result(res); + errorStr = "Couldn't restore admin as couldn't found him in table.\n"; + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +p = row[1]; +int a; + +if(p.length() == 0) +{ + mysql_free_result(res); + errorStr = "Error in parameter password"; + mysql_close(sock); + return -1; +} + +memset(passwordE, 0, sizeof(passwordE)); +strncpy(passwordE, p.c_str(), 2*ADM_PASSWD_LEN); + +memset(pass, 0, sizeof(pass)); + +if (passwordE[0] != 0) + { + Decode21(pass, passwordE); + EnDecodeInit(adm_enc_passwd, strlen(adm_enc_passwd), &ctx); + + for (int i = 0; i < ADM_PASSWD_LEN/8; i++) + { + DecodeString(password + 8*i, pass + 8*i, &ctx); + } + } +else + { + password[0] = 0; + } + +ac->password = password; + +if (GetInt(row[2], &a, 0) == 0) + ac->priv.userConf = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgConf"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[3], &a, 0) == 0) + ac->priv.userPasswd = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgPassword"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[4], &a, 0) == 0) + ac->priv.userStat = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgStat"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[5], &a, 0) == 0) + ac->priv.userCash = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgCash"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[6], &a, 0) == 0) + ac->priv.userAddDel = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter UsrAddDel"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[7], &a, 0) == 0) + ac->priv.tariffChg = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgTariff"; + mysql_close(sock); + return -1; + } + +if (GetInt(row[8], &a, 0) == 0) + ac->priv.adminChg = a; +else + { + mysql_free_result(res); + errorStr = "Error in parameter ChgAdmin"; + mysql_close(sock); + return -1; + } + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::AddTariff(const string & name) const +{ +sprintf(qbuf,"INSERT INTO tariffs SET name='%s'", name.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add tariff:\n"; +// errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::DelTariff(const string & name) const +{ +sprintf(qbuf,"DELETE FROM tariffs WHERE name='%s' LIMIT 1", name.c_str()); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete tariff: "; +// errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::RestoreTariff(TARIFF_DATA * td, const string & tariffName) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +sprintf(qbuf,"SELECT * FROM tariffs WHERE name='%s' LIMIT 1", tariffName.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't restore Tariff:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't restore Tariff:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +string str; +td->tariffConf.name = tariffName; + +row = mysql_fetch_row(res); + +string param; +for (int i = 0; idirPrice[i].hDay, + td->dirPrice[i].mDay, + td->dirPrice[i].hNight, + td->dirPrice[i].mNight); + + strprintf(¶m, "PriceDayA%d", i); + if (GetDouble(row[1+i*8], &td->dirPrice[i].priceDayA, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceDayA /= (1024*1024); + + strprintf(¶m, "PriceDayB%d", i); + if (GetDouble(row[2+i*8], &td->dirPrice[i].priceDayB, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceDayB /= (1024*1024); + + strprintf(¶m, "PriceNightA%d", i); + if (GetDouble(row[3+i*8], &td->dirPrice[i].priceNightA, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceNightA /= (1024*1024); + + strprintf(¶m, "PriceNightB%d", i); + if (GetDouble(row[4+i*8], &td->dirPrice[i].priceNightB, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + td->dirPrice[i].priceNightB /= (1024*1024); + + strprintf(¶m, "Threshold%d", i); + if (GetInt(row[5+i*8], &td->dirPrice[i].threshold, 0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + + strprintf(¶m, "SinglePrice%d", i); + if (GetInt(row[8+i*8], &td->dirPrice[i].singlePrice, 0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + + strprintf(¶m, "NoDiscount%d", i); + if (GetInt(row[7+i*8], &td->dirPrice[i].noDiscount, 0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + }//main for + +if (GetDouble(row[2+8*DIR_NUM], &td->tariffConf.fee, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter Fee"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[3+8*DIR_NUM], &td->tariffConf.free, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter Free"; + mysql_close(sock); + return -1; + } + +if (GetDouble(row[1+8*DIR_NUM], &td->tariffConf.passiveCost, 0.0) < 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter PassiveCost"; + mysql_close(sock); + return -1; + } + + str = row[4+8*DIR_NUM]; + param = "TraffType"; + + if (str.length() == 0) + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param; + mysql_close(sock); + return -1; + } + +if (!strcasecmp(str.c_str(), "up")) + td->tariffConf.traffType = TRAFF_UP; +else + if (!strcasecmp(str.c_str(), "down")) + td->tariffConf.traffType = TRAFF_DOWN; + else + if (!strcasecmp(str.c_str(), "up+down")) + td->tariffConf.traffType = TRAFF_UP_DOWN; + else + if (!strcasecmp(str.c_str(), "max")) + td->tariffConf.traffType = TRAFF_MAX; + else + { + mysql_free_result(res); + errorStr = "Cannot read tariff " + tariffName + ". Parameter TraffType incorrect"; + mysql_close(sock); + return -1; + } + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::SaveTariff(const TARIFF_DATA & td, const string & tariffName) const +{ +string param; + +string res="UPDATE tariffs SET"; + +for (int i = 0; i < DIR_NUM; i++) + { + strprintf(¶m, " PriceDayA%d=%f,", i, + td.dirPrice[i].priceDayA * pt_mega); + res += param; + + strprintf(¶m, " PriceDayB%d=%f,", i, + td.dirPrice[i].priceDayB * pt_mega); + res += param; + + strprintf(¶m, " PriceNightA%d=%f,", i, + td.dirPrice[i].priceNightA * pt_mega); + res += param; + + strprintf(¶m, " PriceNightB%d=%f,", i, + td.dirPrice[i].priceNightB * pt_mega); + res += param; + + strprintf(¶m, " Threshold%d=%d,", i, + td.dirPrice[i].threshold); + res += param; + + string s; + strprintf(¶m, " Time%d", i); + + strprintf(&s, "%0d:%0d-%0d:%0d", + td.dirPrice[i].hDay, + td.dirPrice[i].mDay, + td.dirPrice[i].hNight, + td.dirPrice[i].mNight); + + res += (param + "='" + s + "',"); + + strprintf(¶m, " NoDiscount%d=%d,", i, + td.dirPrice[i].noDiscount); + res += param; + + strprintf(¶m, " SinglePrice%d=%d,", i, + td.dirPrice[i].singlePrice); + res += param; + } + +strprintf(¶m, " PassiveCost=%f,", td.tariffConf.passiveCost); +res += param; + +strprintf(¶m, " Fee=%f,", td.tariffConf.fee); +res += param; + +strprintf(¶m, " Free=%f,", td.tariffConf.free); +res += param; + +switch (td.tariffConf.traffType) + { + case TRAFF_UP: + res += " TraffType='up'"; + break; + case TRAFF_DOWN: + res += " TraffType='down'"; + break; + case TRAFF_UP_DOWN: + res += " TraffType='up+down'"; + break; + case TRAFF_MAX: + res += " TraffType='max'"; + break; + } +strprintf(¶m, " WHERE name='%s' LIMIT 1", tariffName.c_str()); +res += param; + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't save admin:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const +{ +string res, stTime, endTime, tempStr; +time_t t; +tm * lt; + +t = time(NULL); +lt = localtime(&t); + +if (lt->tm_hour == 0 && lt->tm_min <= 5) + { + t -= 3600 * 24; + lt = localtime(&t); + } + +MYSQL_RES* result; +MYSQL * sock; +strprintf(&tempStr, "detailstat_%02d_%4d", lt->tm_mon+1, lt->tm_year+1900); + +if (!(sock=MysqlConnect())){ + mysql_close(sock); + return -1; +} + +if (!(result=mysql_list_tables(sock,tempStr.c_str() ))) +{ + errorStr = "Couldn't get table " + tempStr + ":\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +unsigned int num_rows = mysql_num_rows(result); + +mysql_free_result(result); + +if (num_rows < 1) +{ + sprintf(qbuf,"CREATE TABLE detailstat_%02d_%4d (login VARCHAR(40) DEFAULT '',"\ + "day TINYINT DEFAULT 0,startTime TIME,endTime TIME,"\ + "IP VARCHAR(17) DEFAULT '',dir INT DEFAULT 0,"\ + "down BIGINT DEFAULT 0,up BIGINT DEFAULT 0, cash DOUBLE DEFAULT 0.0, INDEX (login), INDEX(dir), INDEX(day), INDEX(IP))", + lt->tm_mon+1, lt->tm_year+1900); + + if(MysqlQuery(qbuf,sock)) + { + errorStr = "Couldn't create WriteDetailedStat table:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; + } +} + +struct tm * lt1; +struct tm * lt2; + +lt1 = localtime(&lastStat); + +int h1, m1, s1; +int h2, m2, s2; + +h1 = lt1->tm_hour; +m1 = lt1->tm_min; +s1 = lt1->tm_sec; + +lt2 = localtime(&t); + +h2 = lt2->tm_hour; +m2 = lt2->tm_min; +s2 = lt2->tm_sec; + +strprintf(&stTime, "%02d:%02d:%02d", h1, m1, s1); +strprintf(&endTime, "%02d:%02d:%02d", h2, m2, s2); + +strprintf(&res,"INSERT INTO detailstat_%02d_%4d SET login='%s',"\ + "day=%d,startTime='%s',endTime='%s',", + lt->tm_mon+1, lt->tm_year+1900, + login.c_str(), + lt->tm_mday, + stTime.c_str(), + endTime.c_str() + ); + +int retRes; +map::const_iterator stIter; +stIter = statTree->begin(); + +while (stIter != statTree->end()) + { + strprintf(&tempStr,"IP='%s', dir=%d, down=%lld, up=%lld, cash=%f", + inet_ntostr(stIter->first.ip), + stIter->first.dir, + stIter->second.down, + stIter->second.up, + stIter->second.cash + ); + + if( (retRes = MysqlQuery((res+tempStr).c_str(),sock)) ) + { + errorStr = "Couldn't insert data in WriteDetailedStat:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; + } + + result=mysql_store_result(sock); + if(result) + mysql_free_result(result); + + ++stIter; + } +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::AddMessage(STG_MSG * msg, const string & login) const +{ +struct timeval tv; + +gettimeofday(&tv, NULL); + +msg->header.id = ((long long)tv.tv_sec) * 1000000 + ((long long)tv.tv_usec); + +sprintf(qbuf,"INSERT INTO messages SET login='%s', id=%lld", + login.c_str(), + (long long)msg->header.id + ); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't add message:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return EditMessage(*msg, login); +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::EditMessage(const STG_MSG & msg, const string & login) const +{ +string res; + +strprintf(&res,"UPDATE messages SET type=%d, lastSendTime=%u, creationTime=%u, "\ + "showTime=%u, stgRepeat=%d, repeatPeriod=%u, text='%s' "\ + "WHERE login='%s' AND id=%lld LIMIT 1", + msg.header.type, + msg.header.lastSendTime, + msg.header.creationTime, + msg.header.showTime, + msg.header.repeat, + msg.header.repeatPeriod, + (ReplaceStr(msg.text,badSyms,repSym)).c_str(), + login.c_str(), + (long long)msg.header.id + ); + +if(MysqlSetQuery(res.c_str())) +{ + errorStr = "Couldn't edit message:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetMessage(uint64_t id, STG_MSG * msg, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; + +sprintf(qbuf,"SELECT * FROM messages WHERE login='%s' AND id=%lld LIMIT 1", + login.c_str(), id); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't GetMessage:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't GetMessage:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +row = mysql_fetch_row(res); + +if(row[2]&&str2x(row[2], msg->header.type)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[3] && str2x(row[3], msg->header.lastSendTime)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[4] && str2x(row[4], msg->header.creationTime)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[5] && str2x(row[5], msg->header.showTime)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[6] && str2x(row[6], msg->header.repeat)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +if(row[7] && str2x(row[7], msg->header.repeatPeriod)) +{ + mysql_free_result(res); + errorStr = "Invalid value in message header for user: " + login; + mysql_close(sock); + return -1; +} + +msg->header.id = id; +msg->text = row[8]; + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::DelMessage(uint64_t id, const string & login) const +{ +sprintf(qbuf,"DELETE FROM messages WHERE login='%s' AND id=%lld LIMIT 1", + login.c_str(),(long long)id); + +if(MysqlSetQuery(qbuf)) +{ + errorStr = "Couldn't delete Message:\n"; + //errorStr += mysql_error(sock); + return -1; +} + +return 0; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::GetMessageHdrs(vector * hdrsList, const string & login) const +{ +MYSQL_RES *res; +MYSQL_ROW row; +MYSQL * sock; +sprintf(qbuf,"SELECT * FROM messages WHERE login='%s'", login.c_str()); + +if(MysqlGetQuery(qbuf,sock)) +{ + errorStr = "Couldn't GetMessageHdrs:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +if (!(res=mysql_store_result(sock))) +{ + errorStr = "Couldn't GetMessageHdrs:\n"; + errorStr += mysql_error(sock); + mysql_close(sock); + return -1; +} + +unsigned int i, num_rows = mysql_num_rows(res); +long long int unsigned id; + +for (i=0; ipush_back(hdr); +} + +mysql_free_result(res); +mysql_close(sock); +return 0; +} +//----------------------------------------------------------------------------- + +int MYSQL_STORE::MysqlSetQuery(const char * Query) const { + + MYSQL * sock; + int ret=MysqlGetQuery(Query,sock); + mysql_close(sock); + return ret; +} +//----------------------------------------------------------------------------- +int MYSQL_STORE::MysqlGetQuery(const char * Query,MYSQL * & sock) const { + if (!(sock=MysqlConnect())) { + return -1; + } + return MysqlQuery(Query,sock); +} +//----------------------------------------------------------------------------- +MYSQL * MYSQL_STORE::MysqlConnect() const { + MYSQL * sock; + if ( !(sock=mysql_init(NULL)) ){ + errorStr= "mysql init susck\n"; + return NULL; + } + if (!(sock = mysql_real_connect(sock,storeSettings.GetDBHost().c_str(), + storeSettings.GetDBUser().c_str(),storeSettings.GetDBPassword().c_str(), + 0,0,NULL,0))) + { + errorStr = "Couldn't connect to mysql engine! With error:\n"; + errorStr += mysql_error(sock); + return NULL; + } + else{ + if(mysql_select_db(sock, storeSettings.GetDBName().c_str())){ + errorStr = "Database lost !\n"; + return NULL; + } + } + return sock; +} +//----------------------------------------------------------------------------- diff --git a/projects/stargazer/plugins/store/mysql/.#mysql_store.h.1.1.1.1 b/projects/stargazer/plugins/store/mysql/.#mysql_store.h.1.1.1.1 new file mode 100644 index 00000000..016bde54 --- /dev/null +++ b/projects/stargazer/plugins/store/mysql/.#mysql_store.h.1.1.1.1 @@ -0,0 +1,143 @@ + /* + $Revision: 1.1.1.1 $ + $Date: 2007/11/17 18:28:39 $ + */ + + +#ifndef FILE_STORE_H +#define FILE_STORE_H + +#include + +#include "base_settings.h" +#include "base_store.h" +#include "user_traff.h" +#include + +using namespace std; +//----------------------------------------------------------------------------- +extern "C" BASE_STORE * GetStore(); +//----------------------------------------------------------------------------- +class MYSQL_STORE_SETTINGS//: public BASE_SETTINGS +{ +public: + MYSQL_STORE_SETTINGS(); + virtual ~MYSQL_STORE_SETTINGS(); + virtual int ParseSettings(const MODULE_SETTINGS & s); + virtual const string & GetStrError() const; + + string GetDBUser() const; + string GetDBPassword() const; + string GetDBHost() const; + string GetDBName() const; + +private: + const MODULE_SETTINGS * settings; + + int ParseParam(const vector & moduleParams, + const string & name, string & result); + + string errorStr; + + string dbUser; + string dbPass; + string dbName; + string dbHost; +}; +//----------------------------------------------------------------------------- +class MYSQL_STORE: public BASE_STORE +{ +public: + MYSQL_STORE(); + virtual ~MYSQL_STORE(); + virtual const string & GetStrError() const; + + //User + virtual int GetUsersList(vector * usersList) const; + virtual int AddUser(const string & login) const; + virtual int DelUser(const string & login) const; + virtual int SaveUserStat(const USER_STAT & stat, const string & login) const; + virtual int SaveUserConf(const USER_CONF & conf, const string & login) const; + virtual int RestoreUserStat(USER_STAT * stat, const string & login) const; + virtual int RestoreUserConf(USER_CONF * conf, const string & login) const; + virtual int WriteUserChgLog(const string & login, + const string & admLogin, + uint32_t admIP, + const string & paramName, + const string & oldValue, + const string & newValue, + const string & message = "") const; + virtual int WriteUserConnect(const string & login, uint32_t ip) const; + virtual int WriteUserDisconnect(const string & login, + const DIR_TRAFF & up, + const DIR_TRAFF & down, + const DIR_TRAFF & sessionUp, + const DIR_TRAFF & sessionDown, + double cash, + double freeMb) const; + + virtual int WriteDetailedStat(const map * statTree, + time_t lastStat, + const string & login) const; + + virtual int AddMessage(STG_MSG * msg, const string & login) const; + virtual int EditMessage(const STG_MSG & msg, const string & login) const; + virtual int GetMessage(uint64_t id, STG_MSG * msg, const string & login) const; + virtual int DelMessage(uint64_t id, const string & login) const; + virtual int GetMessageHdrs(vector * hdrsList, const string & login) const; + + virtual int SaveMonthStat(const USER_STAT & stat, int month, int year, const string & login) const; + + //Admin + virtual int GetAdminsList(vector * adminsList) const; + virtual int AddAdmin(const string & login) const; + virtual int DelAdmin(const string & login) const; + virtual int RestoreAdmin(ADMIN_CONF * ac, const string & login) const; + virtual int SaveAdmin(const ADMIN_CONF & ac) const; + + //Tariff + virtual int GetTariffsList(vector * tariffsList) const; + virtual int AddTariff(const string & name) const; + virtual int DelTariff(const string & name) const; + virtual int SaveTariff(const TARIFF_DATA & td, const string & tariffName) const; + virtual int RestoreTariff(TARIFF_DATA * td, const string & tariffName) const; + + //Corparation + virtual int GetCorpsList(vector * corpsList) const {return 0;}; + virtual int SaveCorp(const CORP_CONF & cc) const {return 0;}; + virtual int RestoreCorp(CORP_CONF * cc, const string & name) const {return 0;}; + virtual int AddCorp(const string & name) const {return 0;}; + virtual int DelCorp(const string & name) const {return 0;}; + + // Services + virtual int GetServicesList(vector * corpsList) const {return 0;}; + virtual int SaveService(const SERVICE_CONF & sc) const {return 0;}; + virtual int RestoreService(SERVICE_CONF * sc, const string & name) const {return 0;}; + virtual int AddService(const string & name) const {return 0;}; + virtual int DelService(const string & name) const {return 0;}; + + //virtual BASE_SETTINGS * GetStoreSettings(); + virtual void SetSettings(const MODULE_SETTINGS & s); + virtual int ParseSettings(); + virtual const string & GetVersion() const; + +private: + virtual int WriteLogString(const string & str, const string & login) const; + int GetAllParams(vector * ParamList, const string & table, const string & name) const; + int CheckAllTables(MYSQL * sock); + bool IsTablePresent(const string & str,MYSQL * sock); + mutable string errorStr; +// int Reconnect(); + int MysqlQuery(const char* sQuery,MYSQL * sock) const; + int MysqlGetQuery(const char * Query,MYSQL * & sock) const; + int MysqlSetQuery(const char * Query) const; + MYSQL * MysqlConnect() const ; + string version; + MYSQL_STORE_SETTINGS storeSettings; + MODULE_SETTINGS settings; + //mutable MYSQL mysql; + //mutable MYSQL* sock; +}; +//----------------------------------------------------------------------------- + +#endif //FILE_STORE_H diff --git a/projects/stargazer/plugins/store/mysql/Makefile b/projects/stargazer/plugins/store/mysql/Makefile new file mode 100644 index 00000000..494a0318 --- /dev/null +++ b/projects/stargazer/plugins/store/mysql/Makefile @@ -0,0 +1,20 @@ +############################################################################### +# $Id: Makefile,v 1.6 2010/03/25 10:35:55 faust Exp $ +############################################################################### + +include ../../../../../Makefile.conf + +PROG = mod_store_mysql.so + +SRCS = ./mysql_store.cpp + +STGLIBS = -lconffiles -lstg_common -lstg_crypto + +MYSQL_CFLAGS = $(shell mysql_config --cflags) +MYSQL_LDFLAGS = $(shell mysql_config --libs_r) + +CXXFLAGS += $(MYSQL_CFLAGS) +LIBS += $(MYSQL_LDFLAGS) + +include ../../Makefile.in + diff --git a/projects/stargazer/plugins/store/mysql/deps b/projects/stargazer/plugins/store/mysql/deps new file mode 100644 index 00000000..ffffca15 --- /dev/null +++ b/projects/stargazer/plugins/store/mysql/deps @@ -0,0 +1,30 @@ +mysql_store.o: mysql_store.cpp /usr/include/mysql/mysql.h \ + /usr/include/mysql/mysql_version.h /usr/include/mysql/mysql_com.h \ + /usr/include/mysql/mysql_time.h /usr/include/mysql/my_list.h \ + /usr/include/mysql/typelib.h /usr/include/mysql/my_alloc.h \ + /usr/include/mysql/errmsg.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/common.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/os_int.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_const.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_ips.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/resetable.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_locker.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/noncopyable.h \ + mysql_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_store.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_stat.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/corp_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/service_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/admin_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/tariff_conf.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/base_settings.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/stg_message.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/user_traff.h \ + /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include/blowfish.h Makefile ../../../../../Makefile.conf + $(CC) -c $< -g3 -W -Wall -I/usr/local/include -DARCH_LE -I/usr/include/mysql -DHAVE_ERRNO_AS_DEFINE=1 -DUNIV_LINUX -DUNIV_LINUX -fPIC -I /mnt/tank/home/Projects/STG/stg-2.5/projects/stargazer/../../include -DDEBUG -DLINUX diff --git a/projects/stargazer/plugins/store/mysql/mod_store_mysql.so b/projects/stargazer/plugins/store/mysql/mod_store_mysql.so new file mode 100755 index 0000000000000000000000000000000000000000..1313d26e91df257138d8dde0818bf1c9919c9d3f GIT binary patch literal 523709 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