From: fredrik Date: Fri, 14 Oct 2005 23:35:54 +0000 (+0000) Subject: Transfer from CVS at SourceForge X-Git-Tag: 0.1~219 X-Git-Url: http://dolda2000.com/gitweb/?p=doldaconnect.git;a=commitdiff_plain;h=d3372da97568d5e1f35fa19787c8ec8af93a0435 Transfer from CVS at SourceForge git-svn-id: svn+ssh://svn.dolda2000.com/srv/svn/repos/src/doldaconnect@356 959494ce-11ee-0310-bf91-de5d638817bd --- d3372da97568d5e1f35fa19787c8ec8af93a0435 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d17f040 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Maintainer, developer, documenter, etc.: +Fredrik Tolf diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/CVS/Entries b/CVS/Entries new file mode 100644 index 0000000..2a2ae7f --- /dev/null +++ b/CVS/Entries @@ -0,0 +1,16 @@ +/AUTHORS/1.1.1.1/Tue May 11 15:45:57 2004// +/COPYING/1.1.1.1/Tue May 11 15:46:34 2004// +/ChangeLog/1.4/Sat Jul 9 03:42:47 2005// +/INSTALL/1.1.1.1/Tue May 11 15:46:34 2004// +/Makefile.am/1.4/Sat Jul 9 03:38:28 2005// +/NEWS/1.1.1.1/Tue May 11 15:46:45 2004// +/README/1.1.1.1/Tue May 11 15:46:45 2004// +/bootstrap/1.2/Sat Jul 9 03:17:49 2005// +/configure.in/1.19/Sun Oct 9 15:28:57 2005// +D/autopackage//// +D/clients//// +D/config//// +D/daemon//// +D/include//// +D/lib//// +D/po//// diff --git a/CVS/Repository b/CVS/Repository new file mode 100644 index 0000000..48b3e60 --- /dev/null +++ b/CVS/Repository @@ -0,0 +1 @@ +doldaconnect diff --git a/CVS/Root b/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..d4e4aa6 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,62 @@ +2005-07-09 Fredrik Tolf + + * Cleaned up CVS heavily. + +2004-08-13 gettextize + + * Makefile.am (SUBDIRS): Add m4. + (ACLOCAL_AMFLAGS): New variable. + (EXTRA_DIST): New variable. + * configure.in (AC_OUTPUT): Add po/Makefile.in, + +2004-05-11 dolda2000 + + * utils.h: Removed the format warnings, since they didn't work with %N. + + * transfer.h, transfer.c, fnet-dc.c: Transfer system rewrite. + + * net.c, net.h, sysevents.h, transfer.c, transfer.h, utils.c: + Initial import. + + * net.c, net.h, sysevents.h, transfer.c, transfer.h, utils.c: New file. + + * filenet.h, log.c, module.h: Initial import. + + * filenet.h, log.c, module.h: New file. + + * conf.h, fnet-dc.c, log.h: Initial import. + + * conf.h, fnet-dc.c, log.h: New file. + + * client.c, client.h, conf.c, filenet.c: Initial import. + + * client.c, client.h, conf.c, filenet.c: New file. + + * auth-pam.c: Initial import. + + * auth-pam.c: New file. + + * auth.c, auth.h: Initial import. + + * auth.c, auth.h: New file. + + * Makefile: Initial import. + + * Makefile: New file. + + * Makefile.in: Initial import. + + * Makefile.in: New file. + + * ui.c, uiretref, utils.h: Initial import. + + * ui.c, uiretref, utils.h: New file. + + * main.c: Initial import. + + * main.c: New file. + + * Makefile.am, emacs-local: Initial import. + + * Makefile.am, emacs-local: New file. + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..b42a17a --- /dev/null +++ b/INSTALL @@ -0,0 +1,182 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..a08c847 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS= daemon lib include clients po config + +ACLOCAL_AMFLAGS = -I m4 + +EXTRA_DIST = config.rpath diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/autopackage/CVS/Entries b/autopackage/CVS/Entries new file mode 100644 index 0000000..b5479b2 --- /dev/null +++ b/autopackage/CVS/Entries @@ -0,0 +1,2 @@ +/dolcon.apspec.in/1.2/Tue Jul 12 02:05:28 2005// +D diff --git a/autopackage/CVS/Repository b/autopackage/CVS/Repository new file mode 100644 index 0000000..1564dad --- /dev/null +++ b/autopackage/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/autopackage diff --git a/autopackage/CVS/Root b/autopackage/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/autopackage/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/autopackage/dolcon.apspec.in b/autopackage/dolcon.apspec.in new file mode 100644 index 0000000..0fbfbf8 --- /dev/null +++ b/autopackage/dolcon.apspec.in @@ -0,0 +1,48 @@ +# -*-shell-script-*- + +[Meta] +RootName: @dolda2000.com/~fredrik/doldaconnect/dolcon:$SOFTWAREVERSION +DisplayName: Dolda Connect GTK 2 user interface +ShortName: doldaconnect-gtk +Maintainer: Fredrik Tolf +Packager: Fredrik Tolf +Summary: A user interface module for Dolda Connect using GTK 2. +URL: http://www.dolda2000.com/~fredrik/doldaconnect/ +License: GNU General Public License, Version 2 +SoftwareVersion: @VERSION@ +AutopackageTarget: 1.0 + +# Only uncomment InterfaceVersion if your package exposes interfaces to other software, +# for instance if it includes DSOs or python/perl modules. See the developer guide for more info, +# or ask on autopackage-dev if you don't understand interface versioning in autopackage. +# +# InterfaceVersion: 0.0 + +[Description] +This is a user interface program for Dolda Connect. It connects to the +Dolda Connect daemon and lets a user control it. This user interface +is written with GTK 2. + +Note that this program does not share files or anything of the sort -- +it is the daemon that does that. This program only controls the +daemon. + +[BuildPrepare] +prepareBuild --enable-gtk2ui --disable-gnomeapplet + +[BuildUnprepare] +unprepareBuild + +[Imports] +echo '*' | import + +[Prepare] +require @gtk.org/gtk 2.0 +require @dolda2000.com/~fredrik/doldaconnect/dcuilib + +[Install] +installExe bin/dolcon +installLocale share/locale + +[Uninstall] +uninstallFromLog diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..1afe2dd --- /dev/null +++ b/bootstrap @@ -0,0 +1,3 @@ +#! /bin/sh + +autopoint && aclocal$AUTOMAKE_VERSION -I m4 && autoheader && libtoolize --copy --force && automake$AUTOMAKE_VERSION --add-missing --copy && autoconf diff --git a/clients/CVS/Entries b/clients/CVS/Entries new file mode 100644 index 0000000..fcfb102 --- /dev/null +++ b/clients/CVS/Entries @@ -0,0 +1,5 @@ +/Makefile.am/1.6/Thu Dec 30 02:47:05 2004// +/hellodolda.jpg/1.1/Sun Dec 26 23:48:43 2004/-ko/ +/test.c/1.1.1.1/Tue May 11 15:46:45 2004// +D/gnome-trans-applet//// +D/gtk2//// diff --git a/clients/CVS/Repository b/clients/CVS/Repository new file mode 100644 index 0000000..e46c3bb --- /dev/null +++ b/clients/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/clients diff --git a/clients/CVS/Root b/clients/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/clients/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/clients/Makefile.am b/clients/Makefile.am new file mode 100644 index 0000000..aca20d2 --- /dev/null +++ b/clients/Makefile.am @@ -0,0 +1,13 @@ +SUBDIRS=@clients@ +DIST_SUBDIRS=gtk2 gnome-trans-applet +EXTRA_DIST=hellodolda.jpg + +iconsdir = $(datadir)/pixmaps +icons_DATA = hellodolda.jpg + +noinst_PROGRAMS=test + +test_SOURCES=test.c + +test_LDADD=$(top_srcdir)/lib/libdcui.la +AM_CPPFLAGS=-I$(top_srcdir)/include diff --git a/clients/gnome-trans-applet/CVS/Entries b/clients/gnome-trans-applet/CVS/Entries new file mode 100644 index 0000000..8beace3 --- /dev/null +++ b/clients/gnome-trans-applet/CVS/Entries @@ -0,0 +1,9 @@ +/Dolcon_Transferapplet_Factory.server.in/1.2/Thu Dec 30 01:11:03 2004// +/Makefile.am/1.5/Sat Jan 1 17:39:45 2005// +/conduit-dclib.c/1.4/Tue May 10 00:20:16 2005// +/conduit-pipe.c/1.4/Fri Dec 31 12:35:38 2004// +/conduit.c/1.3/Thu Dec 30 02:30:49 2004// +/conduit.h/1.4/Sat Jan 1 17:39:11 2005// +/dctrmon/1.2/Mon Jan 24 12:07:16 2005// +/dolcon-trans-applet.c/1.5/Tue Oct 11 20:23:48 2005// +D diff --git a/clients/gnome-trans-applet/CVS/Repository b/clients/gnome-trans-applet/CVS/Repository new file mode 100644 index 0000000..4ebbf5a --- /dev/null +++ b/clients/gnome-trans-applet/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/clients/gnome-trans-applet diff --git a/clients/gnome-trans-applet/CVS/Root b/clients/gnome-trans-applet/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/clients/gnome-trans-applet/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/clients/gnome-trans-applet/Dolcon_Transferapplet_Factory.server.in b/clients/gnome-trans-applet/Dolcon_Transferapplet_Factory.server.in new file mode 100644 index 0000000..b058242 --- /dev/null +++ b/clients/gnome-trans-applet/Dolcon_Transferapplet_Factory.server.in @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/clients/gnome-trans-applet/Makefile.am b/clients/gnome-trans-applet/Makefile.am new file mode 100644 index 0000000..c73f993 --- /dev/null +++ b/clients/gnome-trans-applet/Makefile.am @@ -0,0 +1,22 @@ +libexec_PROGRAMS=dolcon-trans-applet + +dolcon_trans_applet_SOURCES= dolcon-trans-applet.c \ + conduit-pipe.c \ + conduit-dclib.c \ + conduit.c \ + conduit.h + +localedir=$(datadir)/locale +dolcon_trans_applet_LDFLAGS=$(shell pkg-config --libs libpanelapplet-2.0) +dolcon_trans_applet_LDADD=$(top_srcdir)/lib/libdcui.la +dolcon_trans_applet_CPPFLAGS=$(shell pkg-config --cflags libpanelapplet-2.0) -DLOCALEDIR=\"$(localedir)\" + +BUILT_SOURCES=Dolcon_Transferapplet_Factory.server + +serverdir=$(libdir)/bonobo/servers +server_DATA=Dolcon_Transferapplet_Factory.server + +EXTRA_DIST=Dolcon_Transferapplet_Factory.server.in + +%.server: %.server.in + sed -e "s|\@LIBEXECDIR\@|$(libexecdir)|" $< > $@ diff --git a/clients/gnome-trans-applet/conduit-dclib.c b/clients/gnome-trans-applet/conduit-dclib.c new file mode 100644 index 0000000..afc90c3 --- /dev/null +++ b/clients/gnome-trans-applet/conduit-dclib.c @@ -0,0 +1,314 @@ +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conduit.h" + +struct data +{ + int fd; + int gdkread, gdkwrite; +}; + +struct dtdata +{ + struct conduit *conduit; + struct transfer *ct; + char *tag; + int realtag; +}; + +static struct conduit *inuse = NULL; + +static void dcfdcb(struct conduit *conduit, int fd, GdkInputCondition condition); + +static void updatewrite(struct conduit *conduit) +{ + struct data *data; + + data = conduit->cdata; + if(data->fd < 0) + return; + if(dc_wantwrite()) + { + if(data->gdkwrite == -1) + data->gdkwrite = gdk_input_add(data->fd, GDK_INPUT_WRITE, (void (*)(gpointer, int, GdkInputCondition))dcfdcb, conduit); + } else { + if(data->gdkwrite != -1) + { + gdk_input_remove(data->gdkwrite); + data->gdkwrite = -1; + } + } +} + +static void disconnected(struct conduit *conduit) +{ + struct data *data; + + data = conduit->cdata; + if(inuse == conduit) + inuse = NULL; + if(data->gdkread != -1) + { + gdk_input_remove(data->gdkread); + data->gdkread = -1; + } + if(data->gdkwrite != -1) + { + gdk_input_remove(data->gdkwrite); + data->gdkwrite = -1; + } + data->fd = -1; + conddisconn(conduit); +} + +static int noconv(int type, wchar_t *text, char **resp, void *data) +{ + return(1); +} + +static char *gettag(struct dc_transfer *dt) +{ + char *mbspath, *p, *buf; + + if(dt->path == NULL) + return(NULL); + if((mbspath = icwcstombs(dt->path, "UTF-8")) == NULL) + return(NULL); + /* XXX: Achtung! Too DC-specific! */ + if((p = strrchr(mbspath, '\\')) == NULL) + p = mbspath; + else + p++; + buf = sstrdup(p); + free(mbspath); + return(buf); +} + +static void dtfreecb(struct dc_transfer *dt) +{ + struct dtdata *dtd; + + if((dtd = dt->udata) == NULL) + return; + if(dtd->ct != NULL) + freetransfer(dtd->ct); + if(dtd->tag != NULL) + free(dtd->tag); + free(dtd); +} + +static int lstrargcb(struct dc_response *resp) +{ + struct dc_transfer *dt; + struct dtdata *dtd; + struct dc_intresp *ires; + + dt = resp->data; + dtd = dt->udata; + if(resp->code == 200) + { + while((dtd->tag == NULL) && ((ires = dc_interpret(resp)) != NULL)) + { + if(!wcscmp(ires->argv[0].val.str, L"tag")) + { + dtd->realtag = 1; + dtd->tag = icwcstombs(ires->argv[1].val.str, "UTF-8"); + } + dc_freeires(ires); + } + } + if(dtd->tag == NULL) + dtd->tag = gettag(dt); + dtd->ct = newtransfer(dtd->conduit, dtd->tag, dt->size, dt->curpos); + return(1); +} + +static void inittrans(struct conduit *conduit, struct dc_transfer *dt) +{ + struct dtdata *dtd; + + dtd = smalloc(sizeof(*dtd)); + memset(dtd, 0, sizeof(*dtd)); + dtd->conduit = conduit; + dt->udata = dtd; + dt->destroycb = dtfreecb; + dc_queuecmd(lstrargcb, dt, L"lstrarg", L"%%i", dt->id, NULL); +} + +static void trlistcb(int resp, struct conduit *conduit) +{ + struct data *data; + struct dc_transfer *dt; + + data = conduit->cdata; + if(resp != 200) + return; + for(dt = dc_transfers; dt != NULL; dt = dt->next) + { + if(dt->dir != DC_TRNSD_DOWN) + continue; + inittrans(conduit, dt); + } +} + +static void logincb(int err, wchar_t *reason, struct conduit *conduit) +{ + struct data *data; + + data = conduit->cdata; + if(err != DC_LOGIN_ERR_SUCCESS) + { + dc_disconnect(); + disconnected(conduit); + return; + } + condconnected(conduit); + dc_gettrlistasync((void (*)(int, void *))trlistcb, conduit); + dc_queuecmd(NULL, NULL, L"notify", L"trans:act", L"on", L"trans:prog", L"on", NULL); +} + +static void dcfdcb(struct conduit *conduit, int fd, GdkInputCondition condition) +{ + struct data *data; + struct dc_response *resp; + struct dc_intresp *ires; + struct dc_transfer *dt; + struct dtdata *dtd; + + data = conduit->cdata; + if(((condition & GDK_INPUT_READ) && dc_handleread()) || ((condition & GDK_INPUT_WRITE) && dc_handlewrite())) + { + disconnected(conduit); + return; + } + while((resp = dc_getresp()) != NULL) + { + if(!wcscmp(resp->cmdname, L".connect")) + { + if(resp->code == 200) + { + dc_loginasync(NULL, 1, noconv, (void (*)(int, wchar_t *, void *))logincb, conduit); + } else { + dc_disconnect(); + disconnected(conduit); + } + } else if(!wcscmp(resp->cmdname, L".notify")) { + dc_uimisc_handlenotify(resp); + switch(resp->code) + { + case 610: + if((ires = dc_interpret(resp)) != NULL) + { + if((dt = dc_findtransfer(ires->argv[0].val.num)) != NULL) + { + if(dt->dir == DC_TRNSD_DOWN) + inittrans(conduit, dt); + } + dc_freeires(ires); + } + break; + case 613: + if((ires = dc_interpret(resp)) != NULL) + { + if((dt = dc_findtransfer(ires->argv[0].val.num)) != NULL) + { + if(((dtd = dt->udata) != NULL) && (dtd->ct != NULL)) + { + if(dtd->ct->size != dt->size) + transfersetsize(dtd->ct, dt->size); + } + } + dc_freeires(ires); + } + break; + case 615: + if((ires = dc_interpret(resp)) != NULL) + { + if((dt = dc_findtransfer(ires->argv[0].val.num)) != NULL) + { + if(((dtd = dt->udata) != NULL) && (dtd->ct != NULL)) + { + if(dtd->ct->pos != dt->curpos) + transfersetpos(dtd->ct, dt->curpos); + } + } + dc_freeires(ires); + } + break; + } + } + dc_freeresp(resp); + } + updatewrite(conduit); +} + +static int init(struct conduit *conduit) +{ + static int inited = 0; + struct data *data; + + if(!inited) + { + dc_init(); + inited = 1; + } + data = smalloc(sizeof(*data)); + memset(data, 0, sizeof(*data)); + data->fd = -1; + data->gdkread = data->gdkwrite = -1; + conduit->cdata = data; + return(0); +} + +static int connect(struct conduit *conduit) +{ + struct data *data; + char *host; + + data = conduit->cdata; + if(inuse != NULL) + return(-1); + if((host = getenv("DCSERVER")) == NULL) + host = "localhost"; + if((data->fd = dc_connect(host, -1)) < 0) + return(-1); + data->gdkread = gdk_input_add(data->fd, GDK_INPUT_READ, (void (*)(gpointer, int, GdkInputCondition))dcfdcb, conduit); + updatewrite(conduit); + inuse = conduit; + return(0); +} + +static void destroy(struct conduit *conduit) +{ + struct data *data; + + data = conduit->cdata; + if(data->gdkread != -1) + gdk_input_remove(data->gdkread); + if(data->gdkwrite != -1) + gdk_input_remove(data->gdkwrite); + if(data->fd >= 0) + dc_disconnect(); + if(inuse == conduit) + inuse = NULL; + free(data); +} + +static struct conduitiface st_conduit_dclib = +{ + .init = init, + .connect = connect, + .destroy = destroy, +}; + +struct conduitiface *conduit_dclib = &st_conduit_dclib; diff --git a/clients/gnome-trans-applet/conduit-pipe.c b/clients/gnome-trans-applet/conduit-pipe.c new file mode 100644 index 0000000..c58f4b4 --- /dev/null +++ b/clients/gnome-trans-applet/conduit-pipe.c @@ -0,0 +1,190 @@ +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conduit.h" + +#define SUBPROCCMD "ksu fredrik -q -e /home/fredrik/bin/dctrmon" + +struct data +{ + pid_t subproc; + int fd; + int gdktag; + char *inbuf; + size_t inbufsize, inbufdata; +}; + +static void pipefdcb(struct conduit *conduit, int fd, GdkInputCondition condition) +{ + struct data *data; + int ret; + char *p, *p2, *cmd; + char **args; + size_t argssize, argsdata; + struct transfer *transfer; + + data = (struct data *)conduit->cdata; + if(conduit->state == CNDS_SYN) + condconnected(conduit); + sizebuf2(data->inbuf, data->inbufdata + 80, 1); + ret = read(data->fd, data->inbuf + data->inbufdata, data->inbufsize - data->inbufdata); + if(ret < 0) + { + if((errno == EINTR) || (errno == EAGAIN)) /* Shouldn't happen, but, oh well... */ + return; + perror("conduit-pipe: read"); + gdk_input_remove(data->gdktag); + close(data->fd); + kill(-data->subproc, SIGHUP); + data->gdktag = -1; + data->fd = -1; + data->subproc = 0; + conddisconn(conduit); + } + if(ret == 0) + { + gdk_input_remove(data->gdktag); + close(data->fd); + kill(-data->subproc, SIGHUP); + data->gdktag = -1; + data->fd = -1; + data->subproc = 0; + conddisconn(conduit); + } + data->inbufdata += ret; + while((p = memchr(data->inbuf, '\n', data->inbufdata)) != NULL) + { + *p = 0; + cmd = sstrdup(data->inbuf); + memmove(data->inbuf, p + 1, data->inbufdata -= (p - data->inbuf) + 1); + args = NULL; + argssize = argsdata = 0; + p = cmd; + do + { + if((p2 = strchr(p, '\t')) != NULL) + *(p2++) = 0; + addtobuf(args, p); + p = p2; + } while(p2 != NULL); + if(!strcmp(args[0], "N") && (argsdata >= 4)) + { + transfer = newtransfer(conduit, args[1], atoi(args[2]), atoi(args[3])); + } + if(!strcmp(args[0], "D") && (argsdata >= 2)) + { + if((transfer = findtransferbytag(conduit, args[1])) != NULL) + freetransfer(transfer); + } + if(!strcmp(args[0], "S") && (argsdata >= 3)) + { + if((transfer = findtransferbytag(conduit, args[1])) != NULL) + transfersetsize(transfer, atoi(args[2])); + } + if(!strcmp(args[0], "P") && (argsdata >= 3)) + { + if((transfer = findtransferbytag(conduit, args[1])) != NULL) + transfersetpos(transfer, atoi(args[2])); + } + free(args); + free(cmd); + } +} + +static int init(struct conduit *conduit) +{ + static int inited = 0; + struct data *data; + + if(!inited) + { + signal(SIGCHLD, SIG_IGN); + inited = 1; + } + data = smalloc(sizeof(*data)); + memset(data, 0, sizeof(*data)); + data->fd = -1; + data->inbuf = NULL; + data->inbufsize = data->inbufdata = 0; + data->gdktag = -1; + conduit->cdata = data; + return(0); +} + +static int connect(struct conduit *conduit) +{ + struct data *data; + pid_t pid; + int pfd[2]; + + data = conduit->cdata; + if(pipe(pfd)) + return(-1); + if((pid = fork()) < 0) + { + close(pfd[0]); + close(pfd[1]); + return(-1); + } + if(!pid) + { + int devnull; + + setpgrp(); + if((devnull = open("/dev/null", O_RDWR)) < 0) + exit(127); + close(pfd[0]); + dup2(pfd[1], 1); + close(pfd[1]); + dup2(devnull, 0); + close(devnull); + /* Leave stderr as is */ + execl("/bin/sh", "sh", "-c", SUBPROCCMD, NULL); + exit(127); + } + close(pfd[1]); + fcntl(pfd[0], F_SETFL, fcntl(pfd[0], F_GETFL) | O_NONBLOCK); + data->subproc = pid; + data->fd = pfd[0]; + data->gdktag = gdk_input_add(pfd[0], GDK_INPUT_READ, (void (*)(gpointer, gint, GdkInputCondition))pipefdcb, conduit); + data->inbufdata = 0; + return(0); +} + +static void destroy(struct conduit *conduit) +{ + struct data *data; + + data = conduit->cdata; + if(data == NULL) + return; + if(data->gdktag >= 0) + gdk_input_remove(data->gdktag); + if(data->subproc > 0) + kill(-data->subproc, SIGHUP); + if(data->fd >= 0) + close(data->fd); + if(data->inbuf != NULL) + free(data->inbuf); + free(data); + conduit->cdata = NULL; +} + +static struct conduitiface st_conduit_pipe = +{ + .init = init, + .connect = connect, + .destroy = destroy, +}; + +struct conduitiface *conduit_pipe = &st_conduit_pipe; diff --git a/clients/gnome-trans-applet/conduit.c b/clients/gnome-trans-applet/conduit.c new file mode 100644 index 0000000..5959a27 --- /dev/null +++ b/clients/gnome-trans-applet/conduit.c @@ -0,0 +1,145 @@ +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "conduit.h" + +void (*cb_condstate)(struct conduit *conduit, void *data) = NULL; +void (*cb_trsize)(struct transfer *transfer, void *data) = NULL; +void (*cb_trpos)(struct transfer *transfer, void *data) = NULL; +void (*cb_trnew)(struct transfer *transfer, void *data) = NULL; +void (*cb_trfree)(struct transfer *transfer, void *data) = NULL; + +struct transfer *findtransferbytag(struct conduit *conduit, char *tag) +{ + struct transfer *transfer; + + for(transfer = conduit->transfers; transfer != NULL; transfer = transfer->next) + { + if((transfer->tag != NULL) && !strcmp(transfer->tag, tag)) + break; + } + return(transfer); +} + +void transfersetsize(struct transfer *transfer, int size) +{ + transfer->size = size; + cb_trsize(transfer, transfer->conduit->udata); +} + +void transfersetpos(struct transfer *transfer, int pos) +{ + transfer->pos = pos; + cb_trpos(transfer, transfer->conduit->udata); +} + +static gboolean trupdatetime(struct transfer *transfer) +{ + time_t now; + + if((transfer->size == -1) || (transfer->pos == -1)) + return(TRUE); + now = time(NULL); + if(now - transfer->ckptime >= 10) + { + transfer->cmptime = transfer->ckptime; + transfer->cmpsize = transfer->ckpsize; + transfer->ckptime = 0; + } + if(transfer->ckptime == 0) + { + transfer->ckptime = now; + transfer->ckpsize = transfer->pos; + } + return(TRUE); +} + +struct transfer *newtransfer(struct conduit *conduit, char *tag, int size, int pos) +{ + struct transfer *transfer; + + transfer = smalloc(sizeof(*transfer)); + memset(transfer, 0, sizeof(*transfer)); + if(tag != NULL) + transfer->tag = sstrdup(tag); + transfer->size = size; + transfer->pos = pos; + transfer->timeout = g_timeout_add(1000, (gboolean (*)(gpointer))trupdatetime, transfer); + transfer->next = conduit->transfers; + transfer->conduit = conduit; + if(conduit->transfers != NULL) + conduit->transfers->prev = transfer; + conduit->transfers = transfer; + cb_trnew(transfer, conduit->udata); + return(transfer); +} + +void freetransfer(struct transfer *transfer) +{ + if(transfer->next != NULL) + transfer->next->prev = transfer->prev; + if(transfer->prev != NULL) + transfer->prev->next = transfer->next; + if(transfer->conduit->transfers == transfer) + transfer->conduit->transfers = transfer->next; + cb_trfree(transfer, transfer->conduit->udata); + g_source_remove(transfer->timeout); + if(transfer->tag != NULL) + free(transfer->tag); + free(transfer); +} + +struct conduit *newconduit(struct conduitiface *iface, void *udata) +{ + struct conduit *conduit; + + conduit = smalloc(sizeof(*conduit)); + memset(conduit, 0, sizeof(*conduit)); + conduit->iface = iface; + conduit->udata = udata; + if(iface->init(conduit)) + { + free(conduit); + return(NULL); + } + return(conduit); +} + +void freeconduit(struct conduit *conduit) +{ + conduit->iface->destroy(conduit); + while(conduit->transfers != NULL) + freetransfer(conduit->transfers); + free(conduit); +} + +int condtryconn(struct conduit *conduit) +{ + if(conduit->state != CNDS_IDLE) + return(-1); + if(conduit->iface->connect(conduit)) + return(-1); + conduit->state = CNDS_SYN; + return(0); +} + +void conddisconn(struct conduit *conduit) +{ + while(conduit->transfers != NULL) + freetransfer(conduit->transfers); + conduit->state = CNDS_IDLE; + cb_condstate(conduit, conduit->udata); +} + +void condconnected(struct conduit *conduit) +{ + conduit->state = CNDS_EST; + cb_condstate(conduit, conduit->udata); +} diff --git a/clients/gnome-trans-applet/conduit.h b/clients/gnome-trans-applet/conduit.h new file mode 100644 index 0000000..2540960 --- /dev/null +++ b/clients/gnome-trans-applet/conduit.h @@ -0,0 +1,55 @@ +#ifndef _CONDUIT_H +#define _CONDUIT_H + +#include + +#define CNDS_IDLE 0 +#define CNDS_SYN 1 +#define CNDS_EST 2 + +struct transfer +{ + struct transfer *next, *prev; + struct conduit *conduit; + char *tag; /* UTF8 */ + int pos, size; + time_t cmptime, ckptime; + size_t cmpsize, ckpsize; + int timeout; +}; + +struct conduit +{ + struct transfer *transfers; + struct conduitiface *iface; + void *cdata, *udata; + int state; +}; + +struct conduitiface +{ + int (*init)(struct conduit *conduit); + int (*connect)(struct conduit *conduit); + void (*destroy)(struct conduit *conduit); +}; + +struct transfer *findtransferbytag(struct conduit *conduit, char *tag); +void transfersetsize(struct transfer *transfer, int size); +void transfersetpos(struct transfer *transfer, int pos); +struct transfer *newtransfer(struct conduit *conduit, char *tag, int size, int pos); +void freetransfer(struct transfer *transfer); +struct conduit *newconduit(struct conduitiface *iface, void *udata); +void freeconduit(struct conduit *conduit); +int condtryconn(struct conduit *conduit); +void conddisconn(struct conduit *conduit); +void condconnected(struct conduit *conduit); + +extern void (*cb_condstate)(struct conduit *conduit, void *data); +extern void (*cb_trsize)(struct transfer *transfer, void *data); +extern void (*cb_trpos)(struct transfer *transfer, void *data); +extern void (*cb_trnew)(struct transfer *transfer, void *data); +extern void (*cb_trfree)(struct transfer *transfer, void *data); +extern struct conduitiface *conduit_pipe; +extern struct conduitiface *conduit_dclib; + +#endif diff --git a/clients/gnome-trans-applet/dctrmon b/clients/gnome-trans-applet/dctrmon new file mode 100755 index 0000000..8b9e8cb --- /dev/null +++ b/clients/gnome-trans-applet/dctrmon @@ -0,0 +1,93 @@ +#!/usr/bin/guile \ +--debug -s +!# + +(use-modules (dolcon ui)) +(use-modules (ice-9 popen)) +(use-modules (ice-9 pretty-print)) + +(define (flush port) + (force-output port)) + +(define idlist '()) +(define filter '()) +(define (filtered tag filter) + (and (pair? filter) + (or (equal? (car filter) (substring tag 0 (min (string-length (car filter)) (string-length tag)))) + (filtered tag (cdr filter))))) +(catch 'system-error + (lambda () + (let ((port (open-input-file (string-append (getenv "HOME") "/.dctrmon-defines"))) (form #f)) + (while (begin (set! form (read port)) (not (eof-object? form))) + (primitive-eval form)))) + (lambda args + #f)) + + +(define krbcc (string-append "/tmp/krb5cc_dcmon_" (number->string (getuid)) "_XXXXXX")) +(close-port (mkstemp! krbcc)) +(setenv "KRB5CCNAME" (string-append "FILE:" krbcc)) +(sigaction SIGCHLD SIG_DFL) +(define pid (primitive-fork)) +(if (= pid 0) + (begin (execlp "kinit" "kinit" "-f" "-r" "10d" "-k" "-t" (string-append (getenv "HOME") "/.myprinc.keytab") (string-append (passwd:name (getpwuid (getuid))) "/dcview")) + (exit 1)) + (if (not (= (cdr (waitpid pid)) 0)) + (exit 1))) +(dc-c&l #f (getenv "DCSERVER") #t) +(delete-file krbcc) + +(dc-ecmd-assert 200 "notify" "all" "on") + +(display "C\n") + +(let ((resp (dc-ecmd-assert '(200 201) "lstrans"))) + (if (and resp (= (cdr (assoc 'code (dc-extract resp))) 200)) + (for-each (lambda (o) + (if (= (cadr o) 2) + (catch 'bad-return + (lambda () + (for-each (lambda (a) + (if (and (equal? (car a) "tag") (filtered (cadr a) filter)) + (begin + (display (string-append "N\t" (cadr a) "\t" (number->string (list-ref o 6)) "\t" (number->string (list-ref o 7)) "\n")) + (set! idlist (append idlist (list (cons (car o) (cadr a)))))))) + (dc-intall (dc-ecmd-assert 200 "lstrarg" (car o))))) + (lambda args #f)))) + (dc-intall resp)))) + +(flush (current-output-port)) + +(while #t + (dc-select 10000) + (while (let ((resp (dc-getresp))) + (if resp + (begin + (let* ((er (dc-extract resp)) (code (cdr (assoc 'code er))) (cmd (cdr (assoc 'cmd er))) (ir (dc-intresp resp))) + (if (equal? cmd ".notify") + (case code + ((610) + (let* ((id (car ir)) (ir2 (dc-intall (dc-ecmd-assert '(200 201) "lstrarg" id))) (tag (if (eq? (car ir2) '()) #f (assoc "tag" ir2)))) + (if (and tag (filtered (cadr tag) filter)) + (begin (display (string-append "N\t" (cadr tag) "\t-1\t-1\n")) + (flush (current-output-port)) + (set! idlist (append idlist (list (cons (car ir) (cadr tag))))))))) + ((613) + (let ((id (car ir))) + (if (assoc id idlist) + (begin (display (string-append "S\t" (cdr (assoc id idlist)) "\t" (number->string (cadr ir)) "\n")) + (flush (current-output-port)))))) + ((615) + (let ((id (car ir))) + (if (assoc id idlist) + (begin (display (string-append "P\t" (cdr (assoc id idlist)) "\t" (number->string (cadr ir)) "\n")) + (flush (current-output-port)))))) + ((617) + (let ((id (car ir))) + (if (assoc id idlist) + (begin (display (string-append "D\t" (cdr (assoc id idlist)) "\n")) + (flush (current-output-port))))))))) + #t) + #f)) #f)) + +(dc-disconnect) diff --git a/clients/gnome-trans-applet/dolcon-trans-applet.c b/clients/gnome-trans-applet/dolcon-trans-applet.c new file mode 100644 index 0000000..b09e529 --- /dev/null +++ b/clients/gnome-trans-applet/dolcon-trans-applet.c @@ -0,0 +1,229 @@ +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "conduit.h" + +struct appletdata +{ + PanelApplet *applet; + GtkLabel *label; + GtkProgressBar *pbar; + GtkTooltips *tips; + gint tiptimeout; + struct conduit *conduit; + struct transfer *curdisplay; +}; + +static char *ctxtmenu = +"" +" " +" " +""; + +static void run_pref_dialog(BonoboUIComponent *uic, gpointer data, const char *cname) +{ +} + +static BonoboUIVerb ctxtmenuverbs[] = +{ + BONOBO_UI_VERB("dca_pref", run_pref_dialog), + BONOBO_UI_VERB_END +}; + +static gint reconncb(struct appletdata *data) +{ + condtryconn(data->conduit); + return(FALSE); +} + +static gboolean updatetip(struct appletdata *data) +{ + int diff, speed, left; + time_t now; + char buf[256]; + + if(data->curdisplay == NULL) + return(TRUE); + now = time(NULL); + if(data->curdisplay->cmptime == 0) + { + strcpy(buf, _("Calculating remaining time...")); + } else { + diff = data->curdisplay->pos - data->curdisplay->cmpsize; + speed = diff / (now - data->curdisplay->cmptime); + if(speed == 0) + { + strcpy(buf, _("Time left: Infinite (Transfer is standing still)")); + } else { + left = (data->curdisplay->size - data->curdisplay->pos) / speed; + sprintf(buf, _("Time left: %i:%02i"), left / 3600, (left / 60) % 60); + } + } + gtk_tooltips_set_tip(data->tips, GTK_WIDGET(data->applet), buf, NULL); + return(TRUE); +} + +static void update(struct appletdata *data) +{ + char buf[256]; + + switch(data->conduit->state) + { + case CNDS_IDLE: + gtk_progress_bar_set_text(data->pbar, _("Not connected")); + gtk_label_set_text(data->label, ""); + break; + case CNDS_SYN: + gtk_progress_bar_set_text(data->pbar, _("Connecting...")); + gtk_label_set_text(data->label, ""); + break; + case CNDS_EST: + if(data->conduit->transfers == NULL) + { + gtk_progress_bar_set_fraction(data->pbar, 0); + gtk_progress_bar_set_text(data->pbar, ""); + gtk_label_set_text(data->label, _("No transfers to display")); + } else if(data->curdisplay == NULL) { + gtk_progress_bar_set_fraction(data->pbar, 0); + gtk_progress_bar_set_text(data->pbar, ""); + gtk_label_set_text(data->label, _("No transfer selected")); + } else { + if((data->curdisplay->pos > 0) && (data->curdisplay->size > 0)) + { + sprintf(buf, "%'i/%'i", data->curdisplay->pos, data->curdisplay->size); + gtk_progress_bar_set_fraction(data->pbar, (double)data->curdisplay->pos / (double)data->curdisplay->size); + gtk_progress_bar_set_text(data->pbar, buf); + } else { + gtk_progress_bar_set_fraction(data->pbar, 0); + gtk_progress_bar_set_text(data->pbar, _("Initializing")); + } + gtk_label_set_text(data->label, data->curdisplay->tag); + } + break; + } +} + +static void trsize(struct transfer *transfer, struct appletdata *data) +{ + update(data); +} + +static void trpos(struct transfer *transfer, struct appletdata *data) +{ + update(data); +} + +static void trnew(struct transfer *transfer, struct appletdata *data) +{ + if(data->curdisplay == NULL) + data->curdisplay = transfer; + update(data); +} + +static void trfree(struct transfer *transfer, struct appletdata *data) +{ + if(data->curdisplay == transfer) + data->curdisplay = data->conduit->transfers; + update(data); +} + +static void condstate(struct conduit *conduit, struct appletdata *data) +{ + if(conduit->state == CNDS_IDLE) + g_timeout_add(10000, (gboolean (*)(gpointer))reconncb, data); + update(data); +} + +static void initcond(void) +{ + static int inited = 0; + + if(!inited) + { + cb_trsize = (void (*)(struct transfer *, void *))trsize; + cb_trpos = (void (*)(struct transfer *, void *))trpos; + cb_trnew = (void (*)(struct transfer *, void *))trnew; + cb_trfree = (void (*)(struct transfer *, void *))trfree; + cb_condstate = (void (*)(struct conduit *, void *))condstate; + inited = 1; + } +} + +static gboolean trview_applet_button_press(GtkWidget *widget, GdkEventButton *event, struct appletdata *data) +{ + if(event->button == 1) + { + if(data->curdisplay == NULL) + data->curdisplay = data->conduit->transfers; + else if(data->curdisplay->next == NULL) + data->curdisplay = data->conduit->transfers; + else + data->curdisplay = data->curdisplay->next; + update(data); + } + return(FALSE); +} + +static void trview_applet_destroy(GtkWidget *widget, struct appletdata *data) +{ + freeconduit(data->conduit); + g_source_remove(data->tiptimeout); + g_object_unref(data->applet); + g_object_unref(data->tips); + free(data); +} + +static gboolean trview_applet_fill(PanelApplet *applet, const gchar *iid, gpointer uudata) +{ + GtkWidget *hbox, *pbar, *label; + struct appletdata *data; + + initcond(); + if(strcmp(iid, "OAFIID:Dolcon_Transferapplet")) + return(FALSE); + + panel_applet_setup_menu(applet, ctxtmenu, ctxtmenuverbs, NULL); + + hbox = gtk_hbox_new(FALSE, 0); + label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5); + pbar = gtk_progress_bar_new(); + gtk_box_pack_start(GTK_BOX(hbox), pbar, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(applet), hbox); + gtk_widget_show_all(GTK_WIDGET(applet)); + + data = smalloc(sizeof(*data)); + memset(data, 0, sizeof(*data)); + g_object_ref(data->applet = applet); + data->conduit = newconduit(conduit_dclib, data); + data->pbar = GTK_PROGRESS_BAR(pbar); + g_object_ref(data->tips = gtk_tooltips_new()); + data->tiptimeout = g_timeout_add(500, (gboolean (*)(gpointer))updatetip, data); + data->label = GTK_LABEL(label); + + g_signal_connect(applet, "button-press-event", (GCallback)trview_applet_button_press, data); + g_signal_connect(applet, "destroy", (GCallback)trview_applet_destroy, data); + + condtryconn(data->conduit); + + update(data); + + return(TRUE); +} + +#define GETTEXT_PACKAGE PACKAGE +#define GNOMELOCALEDIR LOCALEDIR + +PANEL_APPLET_BONOBO_FACTORY("OAFIID:Dolcon_Transferapplet_Factory", + PANEL_TYPE_APPLET, + "Doldaconnect Transfer Viewer", + "0", + trview_applet_fill, + NULL); diff --git a/clients/gtk2/CVS/Entries b/clients/gtk2/CVS/Entries new file mode 100644 index 0000000..2282c03 --- /dev/null +++ b/clients/gtk2/CVS/Entries @@ -0,0 +1,10 @@ +/Makefile.am/1.11/Mon Oct 4 02:05:16 2004// +/emacs-local/1.1/Fri Aug 13 18:05:08 2004// +/inpdialog.desc/1.2/Sun Sep 26 03:18:56 2004// +/main.c/1.22/Mon Nov 15 08:34:25 2004// +/mainwnd.desc/1.14/Tue Oct 26 02:16:28 2004// +/makegdesc/1.9/Sun Oct 3 22:28:16 2004// +/pref.desc/1.2/Sun Sep 26 03:19:19 2004// +/progressbar.c/1.1/Thu Aug 5 00:22:11 2004// +/progressbar.h/1.1/Thu Aug 5 00:22:17 2004// +D diff --git a/clients/gtk2/CVS/Repository b/clients/gtk2/CVS/Repository new file mode 100644 index 0000000..26ff2c0 --- /dev/null +++ b/clients/gtk2/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/clients/gtk2 diff --git a/clients/gtk2/CVS/Root b/clients/gtk2/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/clients/gtk2/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/clients/gtk2/Makefile.am b/clients/gtk2/Makefile.am new file mode 100644 index 0000000..6afbb6f --- /dev/null +++ b/clients/gtk2/Makefile.am @@ -0,0 +1,21 @@ +bin_PROGRAMS=dolcon + +dolcon_SOURCES= main.c \ + progressbar.c \ + progressbar.h + +EXTRA_DIST=mainwnd.desc inpdialog.desc pref.desc makegdesc + +BUILT_SOURCES=mainwnd.gtk inpdialog.gtk pref.gtk + +main.c: mainwnd.gtk inpdialog.gtk + +localedir=$(datadir)/locale +dolcon_LDFLAGS=$(shell pkg-config --libs gtk+-2.0) +dolcon_LDADD=$(top_srcdir)/lib/libdcui.la +dolcon_CPPFLAGS=$(shell pkg-config --cflags gtk+-2.0) -DLOCALEDIR=\"$(localedir)\" + +%.gtk: %.desc makegdesc + cpp $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $< | ./makegdesc >$@ + +mainwnd.desc: ../../config.h diff --git a/clients/gtk2/emacs-local b/clients/gtk2/emacs-local new file mode 100644 index 0000000..f7263a1 --- /dev/null +++ b/clients/gtk2/emacs-local @@ -0,0 +1,13 @@ +; -*-Lisp-*- + +; Use with: +; (add-hook 'find-file-hooks +; (lambda () +; (load (concat default-directory "emacs-local") t))) + +(if + (string-match "\\.[ch]$" (buffer-file-name (current-buffer))) + (progn + (make-local-variable 'compile-command) + (setq compile-command "make -k 'CFLAGS=-g -Wall'") +)) diff --git a/clients/gtk2/inpdialog.desc b/clients/gtk2/inpdialog.desc new file mode 100644 index 0000000..b441f33 --- /dev/null +++ b/clients/gtk2/inpdialog.desc @@ -0,0 +1,8 @@ +;prefix: inpdialog_ +:hbox + $simg stock: DIALOG_QUESTION size: DIALOG + :vbox + $lbl name: prompt label: " " var: y + $text name: entry var: y sig: activate + end +end diff --git a/clients/gtk2/main.c b/clients/gtk2/main.c new file mode 100644 index 0000000..66b1cce --- /dev/null +++ b/clients/gtk2/main.c @@ -0,0 +1,1815 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "progressbar.h" + + +struct fndata +{ + GtkTextBuffer *textbuf; +}; + +struct srchsize +{ + int size; + int num; + int slots; + double resptime; + GtkTreeRowReference *ref; +}; + +struct knownspeed +{ + char *userid; + int speed, seq; + time_t fetched; +}; + +GtkWidget *inpdialog; +GtkListStore *fnmodel, *ulmodel, *dlmodel, *pubhubmodel; +GtkTreeStore *srchmodel; +GtkTreeModelFilter *srchmodelfilter; +GtkTextTagTable *chattags; +int dcfd = -1, gdkread = -1, gdkwrite = -1; +int pubhubfd = -1, pubhubtag = -1, filterpubhub = 0; +int curchat = -1; +regex_t pubhubfilter; +pid_t pubhubproc = 0; +char *pubhubaddr = NULL; +char *connectas = NULL; +char *dcserver = NULL; +int autoconn = 0; +int srchautoupdate = 0; +int cursrch = -1, nextsrch = -1; +time_t srcheta; +struct srchsize *srchsizes = NULL; +struct knownspeed *knownspeeds = NULL; +int numsizes = 0, numspeeds = 0, ksqueryseq = -1, ksquerytag = -1; + +gboolean initdeath(GtkWidget *, gpointer); +void cb_main_connmenu_activate(GtkWidget *widget, gpointer data); +void cb_main_dconnmenu_activate(GtkWidget *widget, gpointer data); +void cb_main_prefmenu_activate(GtkWidget *widget, gpointer data); +void cb_main_sdmenu_activate(GtkWidget *widget, gpointer data); +void cb_inpdialog_entry_activate(GtkWidget *widget, gpointer data); +void cb_main_fnaddr_activate(GtkWidget *widget, gpointer data); +void cb_main_pubhubfilter_activate(GtkWidget *widget, gpointer data); +void cb_main_dcnctbtn_clicked(GtkWidget *widget, gpointer data); +void cb_main_phublist_cchange(GtkWidget *widget, gpointer data); +void cb_main_phublist_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data); +void cb_main_chatnodes_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data); +void cb_main_srchres_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data); +void cb_main_chatstr_activate(GtkWidget *widget, gpointer data); +void cb_main_simplesrch_changed(GtkWidget *widget, gpointer data); +void cb_main_realsrch_changed(GtkWidget *widget, gpointer data); +void cb_main_srchbtn_clicked(GtkWidget *widget, gpointer data); +void cb_main_srchcanbtn_clicked(GtkWidget *widget, gpointer data); +void cb_main_trlist_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data); +void cb_main_filternoslots_toggled(GtkToggleButton *widget, gpointer data); +void dcfdcallback(gpointer data, gint source, GdkInputCondition condition); +void srchstatupdate(void); +void transnicebytefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data); +void transerrorinfo(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data); +void percentagefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data); +void hidezerofunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data); +void speedtimefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data); + +#define DCCHARSET "windows-1252" + +#define _(text) gettext(text) + +#include "mainwnd.gtk" +#include "inpdialog.gtk" +#include "pref.gtk" + +void updatewrite(void) +{ + if(dcfd < 0) + return; + if(dc_wantwrite()) + { + if(gdkwrite == -1) + gdkwrite = gdk_input_add(dcfd, GDK_INPUT_WRITE, dcfdcallback, NULL); + } else { + if(gdkwrite != -1) + { + gdk_input_remove(gdkwrite); + gdkwrite = -1; + } + } +} + +void fndestroycb(struct dc_fnetnode *fn) +{ + struct fndata *data; + GtkTextBuffer *textbuf; + + data = fn->udata; + g_object_unref(data->textbuf); + free(data); + if(curchat == fn->id) + { + textbuf = gtk_text_buffer_new(chattags); + gtk_text_view_set_buffer(GTK_TEXT_VIEW(main_chatview), textbuf); + g_object_unref(textbuf); + } +} + +void addfndata(struct dc_fnetnode *fn) +{ + struct fndata *data; + + if(fn->udata != NULL) + return; + fn->destroycb = fndestroycb; + data = smalloc(sizeof(*data)); + data->textbuf = gtk_text_buffer_new(chattags); + fn->udata = data; +} + +char *getfnstatestock(int state) +{ + if(state == DC_FNN_STATE_SYN) + return("gtk-jump-to"); + if(state == DC_FNN_STATE_HS) + return("gtk-execute"); + if(state == DC_FNN_STATE_EST) + return("gtk-yes"); + if(state == DC_FNN_STATE_DEAD) + return("gtk-cancel"); + return(NULL); +} + +void updatehublist(void) +{ + int done; + struct dc_fnetnode *fn; + GtkTreeIter iter; + int id; + char *buf; + char *name; + int state, numusers; + + for(fn = dc_fnetnodes; fn != NULL; fn = fn->next) + fn->found = 0; + done = 0; + while(!done) + { + done = 1; + if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(fnmodel), &iter)) + { + do + { + gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1); + if((fn = dc_findfnetnode(id)) == NULL) + { + /* I can't seem to get a sensible reply fromp + * gtk_list_store, so I'm just doing this + * instead. */ + gtk_list_store_remove(fnmodel, &iter); + done = 0; + break; + } else { + gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 1, &name, 2, &state, 3, &numusers, -1); + if(fn->name == NULL) + buf = _("Unknown"); + else + buf = icswcstombs(fn->name, "UTF-8", NULL); + if(strcmp(buf, name)) + gtk_list_store_set(fnmodel, &iter, 1, buf, -1); + if(state != fn->state) + { + gtk_list_store_set(fnmodel, &iter, 2, fn->state, -1); + gtk_list_store_set(fnmodel, &iter, 4, getfnstatestock(fn->state), -1); + } + if(numusers != fn->numusers) + gtk_list_store_set(fnmodel, &iter, 3, fn->numusers, -1); + g_free(name); + fn->found = 1; + } + } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(fnmodel), &iter)); + } + } + for(fn = dc_fnetnodes; fn != NULL; fn = fn->next) + { + if(!fn->found) + { + if(fn->name == NULL) + buf = _("Unknown"); + else + buf = icswcstombs(fn->name, "UTF-8", NULL); + gtk_list_store_append(fnmodel, &iter); + gtk_list_store_set(fnmodel, &iter, 0, fn->id, 1, buf, 2, fn->state, 3, fn->numusers, 4, getfnstatestock(fn->state), -1); + addfndata(fn); + } + } +} + +void percentagefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + int colnum; + float val; + char buf[64]; + + colnum = (int)data; + gtk_tree_model_get(model, iter, colnum, &val, -1); + snprintf(buf, 64, "%.2f%%", (double)(val * 100.0)); + g_object_set(rend, "text", buf, NULL); +} + +void transnicebytefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + int colnum, val; + char buf[64]; + + colnum = (int)data; + gtk_tree_model_get(model, iter, colnum, &val, -1); + if(val >= 0) + snprintf(buf, 64, "%'i", val); + else + strcpy(buf, _("Unknown")); + g_object_set(rend, "text", buf, NULL); +} + +void hidezerofunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + int colnum, val; + char buf[64]; + + colnum = (int)data; + gtk_tree_model_get(model, iter, colnum, &val, -1); + if(val > 0) + snprintf(buf, 64, "%i", val); + else + strcpy(buf, ""); + g_object_set(rend, "text", buf, NULL); +} + +void speedtimefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + int speed, size, time; + char buf[64]; + + gtk_tree_model_get(model, iter, 4, &size, 8, &speed, -1); + if(speed > 0) + { + time = (size / speed) / 60; + if(time < 1) + snprintf(buf, 64, "%'i (<00:01)", speed); + else + snprintf(buf, 64, "%'i (%02i:%02i)", speed, time / 60, time % 60); + } else if(speed == 0) { + strcpy(buf, "0"); + } else { + strcpy(buf, _("Unknown")); + } + g_object_set(rend, "text", buf, NULL); +} + +void transerrorinfo(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + int error; + time_t errortime; + char finbuf[64], tbuf[64], *errstr; + + gtk_tree_model_get(model, iter, 10, &error, 11, &errortime, -1); + if(error != DC_TRNSE_NOERROR) + { + if(error == DC_TRNSE_NOTFOUND) + errstr = _("Not found"); + else if(error == DC_TRNSE_NOSLOTS) + errstr = _("No slots"); + strftime(tbuf, 64, _("%H:%M:%S"), localtime(&errortime)); + snprintf(finbuf, 64, _("%s (reported at %s)"), errstr, tbuf); + } else { + *finbuf = 0; + } + g_object_set(rend, "text", finbuf, NULL); +} + +char *gettrstatestock(int state) +{ + if(state == DC_TRNS_WAITING) + return("gtk-jump-to"); + if(state == DC_TRNS_HS) + return("gtk-execute"); + if(state == DC_TRNS_MAIN) + return("gtk-network"); + if(state == DC_TRNS_DONE) + return("gtk-yes"); + return(NULL); +} + +void updatetransferlists(void) +{ + int i; + int done; + struct dc_transfer *transfer; + GtkTreeIter iter; + int id; + char *buf; + char *peerid, *peernick, *path; + int state, dir, size, curpos, error; + time_t errortime; + GtkListStore *stores[3]; + + for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next) + transfer->found = 0; + stores[DC_TRNSD_UNKNOWN] = NULL; + stores[DC_TRNSD_UP] = ulmodel; + stores[DC_TRNSD_DOWN] = dlmodel; + for(i = 0; i < 3; i++) + { + if(stores[i] == NULL) + continue; + done = 0; + while(!done) + { + done = 1; + if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(stores[i]), &iter)) + { + do + { + gtk_tree_model_get(GTK_TREE_MODEL(stores[i]), &iter, 0, &id, 1, &dir, -1); + if(((transfer = dc_findtransfer(id)) == NULL) || (transfer->dir != dir)) + { + gtk_list_store_remove(stores[i], &iter); + done = 0; + break; + } else { + transfer->found = 1; + gtk_tree_model_get(GTK_TREE_MODEL(stores[i]), &iter, 2, &state, 3, &peerid, 4, &peernick, 5, &path, 6, &size, 7, &curpos, 10, &error, 11, &errortime, -1); + if(state != transfer->state) + gtk_list_store_set(stores[i], &iter, 2, transfer->state, 8, gettrstatestock(transfer->state), -1); + if(size != transfer->size) + gtk_list_store_set(stores[i], &iter, 6, transfer->size, -1); + if(curpos != transfer->curpos) + gtk_list_store_set(stores[i], &iter, 7, transfer->curpos, -1); + if(error != transfer->error) + gtk_list_store_set(stores[i], &iter, 10, transfer->error, -1); + if(errortime != transfer->errortime) + gtk_list_store_set(stores[i], &iter, 11, transfer->errortime, -1); + if((transfer->size > 0) && (transfer->curpos > 0)) + gtk_list_store_set(stores[i], &iter, 9, (float)transfer->curpos / (float)transfer->size, -1); + buf = icswcstombs(transfer->peerid, "UTF-8", NULL); + if(strcmp(buf, peerid)) + gtk_list_store_set(stores[i], &iter, 3, buf, -1); + buf = icswcstombs(((transfer->peernick == NULL) || (transfer->peernick[0] == L'\0'))?transfer->peerid:transfer->peernick, "UTF-8", NULL); + if(strcmp(buf, peernick)) + gtk_list_store_set(stores[i], &iter, 4, buf, -1); + buf = (transfer->path == NULL)?_("Unknown"):icswcstombs(transfer->path, "UTF-8", NULL); + if(strcmp(buf, path)) + gtk_list_store_set(stores[i], &iter, 5, buf, -1); + g_free(peerid); + g_free(peernick); + g_free(path); + } + } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(stores[i]), &iter)); + } + } + } + for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next) + { + if(!transfer->found) + { + if(stores[transfer->dir] != NULL) + { + peerid = icwcstombs(transfer->peerid, "UTF-8"); + peernick = icwcstombs(((transfer->peernick == NULL) || (transfer->peernick[0] == L'\0'))?transfer->peerid:transfer->peernick, "UTF-8"); + path = (transfer->path == NULL)?_("Unknown"):icwcstombs(transfer->path, "UTF-8"); + gtk_list_store_append(stores[transfer->dir], &iter); + gtk_list_store_set(stores[transfer->dir], &iter, + 0, transfer->id, + 1, transfer->dir, + 2, transfer->state, + 3, peerid, + 4, peernick, + 5, path, + 6, transfer->size, + 7, transfer->curpos, + 8, gettrstatestock(transfer->state), + 9, 0.0, + 10, transfer->error, + 11, transfer->errortime, + -1); + free(peerid); + free(peernick); + if(transfer->path != NULL) + free(path); + } + } + } +} + +void updatesbar(char *msg) +{ + gtk_statusbar_pop(GTK_STATUSBAR(main_statusbar), 0); + gtk_statusbar_push(GTK_STATUSBAR(main_statusbar), 0, msg); +} + +void freesrchsizes(void) +{ + int i; + + for(i = 0; i < numsizes; i++) + { + if(srchsizes[i].ref != NULL) + gtk_tree_row_reference_free(srchsizes[i].ref); + } + if(srchsizes != NULL) + free(srchsizes); + srchsizes = NULL; + numsizes = 0; +} + +void dcdisconnected(void) +{ + if(gdkread != -1) + { + gdk_input_remove(gdkread); + gdkread = -1; + } + dcfd = -1; + updatehublist(); + updatetransferlists(); + cursrch = nextsrch = -1; + gtk_tree_store_clear(srchmodel); + freesrchsizes(); + gtk_widget_set_sensitive(main_connmenu, TRUE); + gtk_widget_set_sensitive(main_dconnmenu, FALSE); + gtk_widget_set_sensitive(main_simplesrch, TRUE); + gtk_widget_set_sensitive(main_realsrch, TRUE); + gtk_widget_set_sensitive(main_srchbtn, TRUE); + gtk_widget_set_sensitive(main_srchcanbtn, FALSE); + updatesbar(_("Disconnected")); +} + +char *inputbox(char *title, char *prompt, char *def, int echo) +{ + int resp; + GtkWidget *swnd; + char *buf; + + inpdialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(inpdialog)->vbox), swnd = create_inpdialog_wnd(), TRUE, TRUE, 0); + gtk_widget_show(swnd); + if(!echo) + gtk_entry_set_visibility(GTK_ENTRY(inpdialog_entry), FALSE); + gtk_label_set_text(GTK_LABEL(inpdialog_prompt), prompt); + gtk_entry_set_text(GTK_ENTRY(inpdialog_entry), def); + resp = gtk_dialog_run(GTK_DIALOG(inpdialog)); + if(!echo) + gtk_entry_set_visibility(GTK_ENTRY(inpdialog_entry), TRUE); + if(resp == GTK_RESPONSE_ACCEPT) + buf = strdup(gtk_entry_get_text(GTK_ENTRY(inpdialog_entry))); + else + buf = NULL; + gtk_widget_destroy(inpdialog); + updatewrite(); + return(buf); +} + +int msgbox(int type, int buttons, char *format, ...) +{ + GtkWidget *swnd; + va_list args; + char *buf; + int resp; + + va_start(args, format); + buf = vsprintf2(format, args); + va_end(args); + swnd = gtk_message_dialog_new(GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, type, buttons, "%s", buf); + resp = gtk_dialog_run(GTK_DIALOG(swnd)); + gtk_widget_destroy(swnd); + free(buf); + return(resp); +} + +void readconfigfile(void) +{ + FILE *cfgfile; + char *homedir, *buf, *p; + int w, h; + + if((homedir = getenv("HOME")) == NULL) + { + fprintf(stderr, "warning: could not find home directory!\n"); + return; + } + buf = sprintf2("%s/.dolconrc", homedir); + if((cfgfile = fopen(buf, "r")) == NULL) + { + if(errno != ENOENT) + perror(buf); + free(buf); + return; + } + free(buf); + buf = smalloc(1024); + while(fgets(buf, 1024, cfgfile) != NULL) + { + if(strlen(buf) < 1) + continue; + p = buf + strlen(buf); + if(p[-1] == '\n') + *(--p) = 0; + if((p = strchr(buf, ':')) == NULL) + continue; + *(p++) = 0; + while((*p == ' ') || (*p == '\t')) + p++; + if(!strcmp(buf, "wnd-width")) + { + w = atoi(p); + } else if(!strcmp(buf, "wnd-height")) { + h = atoi(p); + } else if(!strcmp(buf, "pane1-pos")) { + gtk_paned_set_position(GTK_PANED(main_pane1), atoi(p)); + } else if(!strcmp(buf, "pane2-pos")) { + gtk_paned_set_position(GTK_PANED(main_pane2), atoi(p)); + } else if(!strcmp(buf, "pane3-pos")) { + gtk_paned_set_position(GTK_PANED(main_pane3), atoi(p)); + } else if(!strcmp(buf, "pubhubaddr")) { + free(pubhubaddr); + pubhubaddr = sstrdup(p); + } else if(!strcmp(buf, "dcserver")) { + free(dcserver); + dcserver = sstrdup(p); + } else if(!strcmp(buf, "advexpanded")) { + gtk_expander_set_expanded(GTK_EXPANDER(main_advexp), atoi(p)); + } else if(!strcmp(buf, "connectas")) { + free(connectas); + connectas = sstrdup(p); + } else if(!strcmp(buf, "autoconn")) { + autoconn = atoi(p); + } else if(!strcmp(buf, "filternoslots")) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(main_filternoslots), atoi(p)); + } + } + free(buf); + fclose(cfgfile); +/* + if(w != 1589) + abort(); +*/ + gtk_window_resize(GTK_WINDOW(main_wnd), w, h); +} + +void updateconfigfile(void) +{ + FILE *cfgfile; + char *homedir, *buf; + int w, h; + + if((homedir = getenv("HOME")) == NULL) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!")); + return; + } + buf = sprintf2("%s/.dolconrc", homedir); + if((cfgfile = fopen(buf, "w")) == NULL) + { + free(buf); + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not open configuration file for writing: %s"), strerror(errno)); + return; + } + free(buf); + gtk_window_get_size(GTK_WINDOW(main_wnd), &w, &h); + fprintf(cfgfile, "wnd-width: %i\n", w); + fprintf(cfgfile, "wnd-height: %i\n", h); + fprintf(cfgfile, "pane1-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane1))); + fprintf(cfgfile, "pane2-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane2))); + fprintf(cfgfile, "pane3-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane3))); + fprintf(cfgfile, "pubhubaddr: %s\n", pubhubaddr); + fprintf(cfgfile, "dcserver: %s\n", dcserver); + fprintf(cfgfile, "advexpanded: %i\n", gtk_expander_get_expanded(GTK_EXPANDER(main_advexp))); + fprintf(cfgfile, "connectas: %s\n", connectas); + fprintf(cfgfile, "autoconn: %i\n", autoconn); + fprintf(cfgfile, "filternoslots: %i\n", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(main_filternoslots))); + fclose(cfgfile); +} + +gboolean initdeath(GtkWidget *widget, gpointer data) +{ + updateconfigfile(); + gtk_main_quit(); + return(TRUE); +} + +void cb_inpdialog_entry_activate(GtkWidget *widget, gpointer data) +{ + gtk_dialog_response(GTK_DIALOG(inpdialog), GTK_RESPONSE_ACCEPT); +} + +int loginconv(int type, wchar_t *prompt, char **resp, void *data) +{ + int ret; + char *buf; + + ret = 0; + buf = icwcstombs(prompt, "UTF-8"); + switch(type) + { + case DC_LOGIN_CONV_NOECHO: + if((*resp = inputbox(_("Login"), buf, "", 0)) == NULL) + ret = 1; + break; + case DC_LOGIN_CONV_ECHO: + if((*resp = inputbox(_("Login"), buf, "", 1)) == NULL) + ret = 1; + break; + case DC_LOGIN_CONV_INFO: + msgbox(GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", buf); + break; + case DC_LOGIN_CONV_ERROR: + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf); + break; + } + free(buf); + updatewrite(); + return(ret); +} + +void getfnlistcallback(int resp, void *data) +{ + updatehublist(); +} + +void gettrlistcallback(int resp, void *data) +{ + updatetransferlists(); +} + +void logincallback(int err, wchar_t *reason, void *data) +{ + switch(err) + { + case DC_LOGIN_ERR_SUCCESS: + dc_queuecmd(NULL, NULL, L"notify", L"all", L"on", NULL); + dc_getfnlistasync(getfnlistcallback, NULL); + dc_gettrlistasync(gettrlistcallback, NULL); + updatesbar("Authenticated"); + break; + case DC_LOGIN_ERR_NOLOGIN: + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not negotiate an acceptable authentication mechanism")); + dc_disconnect(); + dcdisconnected(); + break; + case DC_LOGIN_ERR_SERVER: + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server has encountered an error")); + dc_disconnect(); + dcdisconnected(); + break; + case DC_LOGIN_ERR_USER: + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal client error")); + dc_disconnect(); + dcdisconnected(); + break; + case DC_LOGIN_ERR_CONV: + dc_disconnect(); + dcdisconnected(); + break; + case DC_LOGIN_ERR_AUTHFAIL: + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Login attempt failed!")); + dc_disconnect(); + dcdisconnected(); + break; + } + updatewrite(); +} + +GtkTreeIter *ref2iter(GtkTreeRowReference *ref) +{ + static GtkTreeIter iter; + GtkTreePath *path; + + assert((path = gtk_tree_row_reference_get_path(ref)) != NULL); + assert(gtk_tree_model_get_iter(GTK_TREE_MODEL(srchmodel), &iter, path)); + gtk_tree_path_free(path); + return(&iter); +} + +GtkTreeRowReference *iter2ref(GtkTreeIter *iter) +{ + GtkTreePath *path; + GtkTreeRowReference *ref; + + assert((path = gtk_tree_model_get_path(GTK_TREE_MODEL(srchmodel), iter)) != NULL); + assert((ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(srchmodel), path)) != NULL); + gtk_tree_path_free(path); + return(ref); +} + +struct srchsize *finddiscsize(void) +{ + int i; + GtkTreeIter iter; + + for(i = 0; i < numsizes; i++) + { + if(srchsizes[i].size == -1) + return(&srchsizes[i]); + } + srchsizes = srealloc(srchsizes, sizeof(*srchsizes) * ++numsizes); + srchsizes[i].size = -1; + srchsizes[i].num = 1; + srchsizes[i].slots = 0; + srchsizes[i].resptime = 0.0; + gtk_tree_store_append(srchmodel, &iter, NULL); + gtk_tree_store_set(srchmodel, &iter, 3, _("Discrete sizes"), 7, 1, -1); + srchsizes[i].ref = iter2ref(&iter); + return(&srchsizes[i]); +} + +struct knownspeed *findksentbyname(char *userid) +{ + int i; + + for(i = 0; i < numspeeds; i++) + { + if(!strcmp(knownspeeds[i].userid, userid)) + return(&knownspeeds[i]); + } + return(NULL); +} + +struct knownspeed *findksentbyseq(int seq) +{ + int i; + + for(i = 0; i < numspeeds; i++) + { + if(knownspeeds[i].seq == seq) + return(&knownspeeds[i]); + } + return(NULL); +} + +gboolean ksupdaterow(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + struct knownspeed *ks; + char *userid; + + gtk_tree_model_get(GTK_TREE_MODEL(model), iter, 1, &userid, -1); + if(userid == NULL) + return(FALSE); + ks = findksentbyname(userid); + if(ks == NULL) + { + knownspeeds = srealloc(knownspeeds, (numspeeds + 1) * sizeof(*knownspeeds)); + ks = &knownspeeds[numspeeds]; + numspeeds++; + ks->userid = sstrdup(userid); + ks->speed = -1; + ks->seq = -2; + ksqueryseq = -2; + } + g_free(userid); + if(ks->speed != -1) + gtk_tree_store_set(GTK_TREE_STORE(model), iter, 8, ks->speed, -1); + return(FALSE); +} + +gint ksupdatecb(gpointer data) +{ + int i, oldnum; + time_t now; + wchar_t **users, *buf; + size_t userssize, usersdata; + + if(ksquerytag != -1) + return(TRUE); + now = time(NULL); + oldnum = numspeeds; + for(i = 0; i < numspeeds;) + { + if(now - knownspeeds[i].fetched > 60) + { + free(knownspeeds[i].userid); + memmove(&knownspeeds[i], &knownspeeds[i + 1], (--numspeeds - i) * sizeof(*knownspeeds)); + } else { + i++; + } + } + if(oldnum != numspeeds) + knownspeeds = srealloc(knownspeeds, numspeeds * sizeof(*knownspeeds)); + gtk_tree_model_foreach(GTK_TREE_MODEL(srchmodel), ksupdaterow, NULL); + if(ksqueryseq == -2) + { + users = NULL; + userssize = usersdata = 0; + ksqueryseq = 0; + for(i = 0; i < numspeeds; i++) + { + if(knownspeeds[i].seq == -2) + { + assert((buf = icmbstowcs(knownspeeds[i].userid, "UTF-8")) != NULL); + knownspeeds[i].seq = ksqueryseq++; + addtobuf(users, buf); + } + } + addtobuf(users, NULL); + ksquerytag = dc_queuecmd(NULL, NULL, L"filtercmd", L"userspeeda", L"%%a", users, NULL); + dc_freewcsarr(users); + } + return(TRUE); +} + +void handleresps(void) +{ + int i; + struct dc_response *resp; + struct dc_intresp *ires; + struct dc_fnetnode *fn; + struct fndata *fndata; + GtkTextIter iter; + GtkTreeIter titer, piter; + char *buf, *p; + int tosbuf; + struct srchsize *ss; + struct knownspeed *ks; + + while((resp = dc_getresp()) != NULL) + { + if(!wcscmp(resp->cmdname, L".connect")) + { + if(resp->code == 200) + { + tosbuf = 0x10; /* Minimum cost */ + setsockopt(dcfd, SOL_IP, IP_TOS, &tosbuf, sizeof(tosbuf)); + updatesbar(_("Connected")); + dc_loginasync(connectas, 1, loginconv, logincallback, NULL); + } else { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server refused the connection")); + dc_disconnect(); + dcdisconnected(); + } + } else if(!wcscmp(resp->cmdname, L".notify")) { + dc_uimisc_handlenotify(resp); + switch(resp->code) + { + case 600: + if((ires = dc_interpret(resp)) != NULL) + { + if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL) + { + fndata = fn->udata; + gtk_text_buffer_get_end_iter(fndata->textbuf, &iter); + if((buf = icwcstombs(ires->argv[3].val.str, "UTF-8")) != NULL) + { + gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, "<", -1, "sender", NULL); + gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, buf, -1, "sender", NULL); + gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, ">", -1, "sender", NULL); + gtk_text_buffer_insert(fndata->textbuf, &iter, " ", -1); + free(buf); + } + if((buf = icwcstombs(ires->argv[4].val.str, "UTF-8")) != NULL) + { + gtk_text_buffer_insert(fndata->textbuf, &iter, buf, -1); + gtk_text_buffer_insert(fndata->textbuf, &iter, "\n", -1); + free(buf); + if(curchat == fn->id) + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(main_chatview), &iter, 0, 0, 0, 0); + } + } + dc_freeires(ires); + } + break; + case 601: + case 602: + case 603: + case 604: + case 605: + updatehublist(); + break; + case 610: + case 611: + case 612: + case 613: + case 614: + case 615: + case 616: + case 617: + updatetransferlists(); + break; + case 620: + if((ires = dc_interpret(resp)) != NULL) + { + if(ires->argv[0].val.num == nextsrch) + srcheta = time(NULL) + ires->argv[0].val.num; + dc_freeires(ires); + } + break; + case 621: + if((ires = dc_interpret(resp)) != NULL) + { + if(ires->argv[0].val.num == nextsrch) + { + if(cursrch != -1) + dc_queuecmd(NULL, NULL, L"cansrch", L"%%i", cursrch, NULL); + cursrch = nextsrch; + nextsrch = -1; + gtk_widget_set_sensitive(main_realsrch, TRUE); + gtk_widget_set_sensitive(main_simplesrch, TRUE); + gtk_widget_set_sensitive(main_srchbtn, TRUE); + gtk_widget_set_sensitive(main_srchcanbtn, FALSE); + srchstatupdate(); + gtk_entry_set_text(GTK_ENTRY(main_realsrch), ""); + gtk_entry_set_text(GTK_ENTRY(main_simplesrch), ""); + gtk_tree_store_clear(srchmodel); + freesrchsizes(); + } + dc_freeires(ires); + } + break; + case 622: + if((ires = dc_interpret(resp)) != NULL) + { + if(ires->argv[0].val.num == cursrch) + { + for(i = 0; i < numsizes; i++) + { + if(srchsizes[i].size == ires->argv[4].val.num) + break; + } + if(i == numsizes) + { + srchsizes = srealloc(srchsizes, sizeof(*srchsizes) * ++numsizes); + srchsizes[i].size = ires->argv[4].val.num; + srchsizes[i].num = 1; + srchsizes[i].slots = ires->argv[5].val.num; + srchsizes[i].resptime = ires->argv[7].val.flnum; + ss = finddiscsize(); + ss->slots += ires->argv[5].val.num; + if((ss->resptime == 0.0) || (ss->resptime > ires->argv[7].val.flnum)) + ss->resptime = ires->argv[7].val.flnum; + piter = *ref2iter(ss->ref); + gtk_tree_store_set(srchmodel, &piter, 5, ss->slots, 6, ss->resptime, -1); + gtk_tree_store_append(srchmodel, &titer, &piter); + srchsizes[i].ref = iter2ref(&titer); + } else if(srchsizes[i].num == 1) { + char *filename, *peername, *fnetname; + int slots, speed; + double resptime; + + gtk_tree_model_get(GTK_TREE_MODEL(srchmodel), ref2iter(srchsizes[i].ref), 0, &fnetname, 1, &peername, 3, &filename, 5, &slots, 6, &resptime, 8, &speed, -1); + gtk_tree_store_remove(srchmodel, ref2iter(srchsizes[i].ref)); + gtk_tree_row_reference_free(srchsizes[i].ref); + ss = finddiscsize(); + ss->slots -= slots; + gtk_tree_store_set(srchmodel, ref2iter(ss->ref), 5, ss->slots, -1); + gtk_tree_store_append(srchmodel, &piter, NULL); + srchsizes[i].slots = ires->argv[5].val.num + slots; + srchsizes[i].resptime = (ires->argv[7].val.flnum < resptime)?ires->argv[7].val.flnum:resptime; + srchsizes[i].num = 2; + srchsizes[i].ref = iter2ref(&piter); + gtk_tree_store_set(srchmodel, &piter, 4, srchsizes[i].size, 5, srchsizes[i].slots, 6, srchsizes[i].resptime, 7, 2, -1); + if((buf = icwcstombs(ires->argv[1].val.str, "UTF-8")) != NULL) + { + p = buf; + /* XXX: Too DC-specific! */ + if(strrchr(p, '\\') != NULL) + p = strrchr(p, '\\') + 1; + gtk_tree_store_set(srchmodel, &piter, 3, p, -1); + free(buf); + } + gtk_tree_store_append(srchmodel, &titer, &piter); + gtk_tree_store_set(srchmodel, &titer, 0, fnetname, 1, peername, 2, peername, 3, filename, 4, srchsizes[i].size, 5, slots, 6, resptime, 8, speed, -1); + g_free(filename); g_free(peername); g_free(fnetname); + gtk_tree_store_append(srchmodel, &titer, &piter); + } else { + srchsizes[i].num++; + srchsizes[i].slots += ires->argv[5].val.num; + if(ires->argv[7].val.flnum < srchsizes[i].resptime) + srchsizes[i].resptime = ires->argv[7].val.flnum; + piter = *ref2iter(srchsizes[i].ref); + gtk_tree_store_set(srchmodel, &piter, 5, srchsizes[i].slots, 6, srchsizes[i].resptime, 7, srchsizes[i].num, -1); + gtk_tree_store_append(srchmodel, &titer, &piter); + } + if((buf = icwcstombs(ires->argv[1].val.str, "UTF-8")) != NULL) + { + gtk_tree_store_set(srchmodel, &titer, 3, buf, -1); + free(buf); + } + if((buf = icwcstombs(ires->argv[2].val.str, "UTF-8")) != NULL) + { + gtk_tree_store_set(srchmodel, &titer, 0, buf, -1); + free(buf); + } + if((buf = icwcstombs(ires->argv[3].val.str, "UTF-8")) != NULL) + { + gtk_tree_store_set(srchmodel, &titer, 1, buf, -1); + gtk_tree_store_set(srchmodel, &titer, 2, buf, -1); + free(buf); + } + gtk_tree_store_set(srchmodel, &titer, 4, ires->argv[4].val.num, 5, ires->argv[5].val.num, 6, ires->argv[7].val.flnum, 8, -1, -1); + } + dc_freeires(ires); + } + break; + default: + break; + } + } else if(!wcscmp(resp->cmdname, L"filtercmd")) { + if((ksquerytag >= 0) && (ksquerytag == resp->tag)) + { + for(i = 0; i < resp->numlines; i++) + { + assert((ks = findksentbyseq(i)) != NULL); + ks->speed = wcstol(resp->rlines[i].argv[1], NULL, 10); + ks->seq = -1; + ks->fetched = time(NULL); + } + ksquerytag = -1; + ksupdatecb(NULL); + } + } + dc_freeresp(resp); + } + updatewrite(); +} + +void dcfdcallback(gpointer data, gint source, GdkInputCondition condition) +{ + int errnobak; + + if(((condition & GDK_INPUT_READ) && dc_handleread()) || ((condition & GDK_INPUT_WRITE) && dc_handlewrite())) + { + errnobak = errno; + dcdisconnected(); + if(errnobak == 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server has closed the connection")); + } else { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The connection to the server failed:\n\n%s"), strerror(errnobak)); + } + return; + } + handleresps(); +} + +void cb_main_dconnmenu_activate(GtkWidget *widget, gpointer data) +{ + if(dcfd < 0) + return; + dc_disconnect(); + dcdisconnected(); +} + +void cb_main_prefmenu_activate(GtkWidget *widget, gpointer data) +{ + GtkWidget *dialog, *swnd; + int resp; + + dialog = gtk_dialog_new_with_buttons(_("Preferences"), GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), swnd = create_pref_wnd(), TRUE, TRUE, 0); + gtk_entry_set_text(GTK_ENTRY(pref_pubhuburl), pubhubaddr); + gtk_entry_set_text(GTK_ENTRY(pref_connectas), connectas); + gtk_entry_set_text(GTK_ENTRY(pref_dcserver), dcserver); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pref_autoconn), autoconn); + gtk_widget_show(swnd); + resp = gtk_dialog_run(GTK_DIALOG(dialog)); + if(resp == GTK_RESPONSE_ACCEPT) + { + free(pubhubaddr); + pubhubaddr = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_pubhuburl))); + free(connectas); + connectas = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_connectas))); + free(dcserver); + dcserver = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_dcserver))); + autoconn = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_autoconn)); + } + gtk_widget_destroy(dialog); +} + +void dcconnect(char *host) +{ + dcfd = dc_connect(host, -1); + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect:\n\n%s"), strerror(errno)); + return; + } + gdkread = gdk_input_add(dcfd, GDK_INPUT_READ, dcfdcallback, NULL); + updatewrite(); + gtk_widget_set_sensitive(main_connmenu, FALSE); + gtk_widget_set_sensitive(main_dconnmenu, TRUE); + updatesbar(_("Connecting...")); +} + +void cb_main_connmenu_activate(GtkWidget *widget, gpointer data) +{ + char *buf; + + if(dcfd >= 0) + return; + if((buf = inputbox(_("Connect"), _("Server address:"), dcserver, 1)) == NULL) + return; + dcconnect(buf); + free(buf); +} + +void cb_main_sdmenu_activate(GtkWidget *widget, gpointer data) +{ + int tag; + struct dc_response *resp; + + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server")); + return; + } + tag = dc_queuecmd(NULL, NULL, L"shutdown", NULL); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + dc_freeresp(resp); + } + handleresps(); +} + +void cb_main_fnaddr_activate(GtkWidget *widget, gpointer data) +{ + int tag; + char *buf; + struct dc_response *resp; + + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server")); + return; + } + buf = sstrdup(gtk_entry_get_text(GTK_ENTRY(main_fnaddr))); + if(strchr(buf, ':') == NULL) + { + buf = srealloc(buf, strlen(buf) + 5); + strcat(buf, ":411"); + } + tag = dc_queuecmd(NULL, NULL, L"cnct", L"dc", L"%%s", buf, NULL); + free(buf); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + if(resp->code == 509) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse that address")); + dc_freeresp(resp); + } + gtk_entry_set_text(GTK_ENTRY(main_fnaddr), ""); + handleresps(); +} + +void pubhubfdcallback(gpointer data, gint source, GdkInputCondition condition) +{ + static char buf[65536]; + static int bufpos = 0; + int ret, i; + char *p, *p2; + char *fields[4]; + wchar_t *wbuf; + GtkTreeIter iter; + int sorted, sortcol; + GtkSortType sortorder; + GtkTreeModel *sortmodel; + + if(!(condition & GDK_INPUT_READ)) + return; + if(bufpos == 1024) + bufpos = 0; + ret = read(pubhubfd, buf + bufpos, sizeof(buf) - bufpos); + if(ret <= 0) + { + if(ret < 0) + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not read from public hub listing process: %s"), strerror(errno)); + close(pubhubfd); + gdk_input_remove(pubhubtag); + kill(pubhubproc, SIGINT); + pubhubfd = pubhubtag = -1; + pubhubproc = 0; + bufpos = 0; + if(filterpubhub) + { + regfree(&pubhubfilter); + filterpubhub = 0; + } + return; + } + bufpos += ret; + sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(main_phublist)); + sorted = gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(sortmodel), &sortcol, &sortorder); + gtk_tree_view_set_model(GTK_TREE_VIEW(main_phublist), NULL); + while((p = memchr(buf, '\n', bufpos)) != NULL) + { + *(p++) = 0; + if(!filterpubhub || !regexec(&pubhubfilter, buf, 0, NULL, 0)) + { + p2 = buf; + for(i = 0; i < 4; i++) + { + fields[i] = p2; + if((p2 = strchr(p2, '|')) == NULL) + break; + *(p2++) = 0; + } + if(i == 4) + { + for(i = 0; i < 4; i++) + { + if((wbuf = icsmbstowcs(fields[i], DCCHARSET, NULL)) == NULL) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not decode hublist - aborting at this point: %s"), strerror(errno)); + kill(pubhubproc, SIGINT); + break; + } + if((fields[i] = icwcstombs(wbuf, "UTF-8")) == NULL) + break; + } + if(i == 4) + { + gtk_list_store_append(pubhubmodel, &iter); + gtk_list_store_set(pubhubmodel, &iter, 0, fields[0], 1, fields[1], 2, fields[2], 3, atoi(fields[3]), -1); + } + for(i--; i >= 0; i--) + free(fields[i]); + } + } + memmove(buf, p, bufpos -= p - buf); + } + sortmodel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(pubhubmodel)); + if(sorted) + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), sortcol, sortorder); + gtk_tree_view_set_model(GTK_TREE_VIEW(main_phublist), sortmodel); +} + +void cb_main_pubhubfilter_activate(GtkWidget *widget, gpointer data) +{ + int pipe1[2], pipe2[2]; + int len, err; + const char *buf; + char errbuf[1024]; + + if(pubhubtag >= 0) + gdk_input_remove(pubhubtag); + if(pubhubfd >= 0) + close(pubhubfd); + if(pubhubproc > 0) + kill(pubhubproc, SIGINT); + if(filterpubhub) + { + regfree(&pubhubfilter); + filterpubhub = 0; + } + buf = gtk_entry_get_text(GTK_ENTRY(main_pubhubfilter)); + if(*buf) + { + if((err = regcomp(&pubhubfilter, buf, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0) + { + regerror(err, &pubhubfilter, errbuf, sizeof(errbuf)); + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not compile regex: %s", errbuf); + regfree(&pubhubfilter); + filterpubhub = 0; + return; + } + filterpubhub = 1; + } + gtk_list_store_clear(pubhubmodel); + pipe(pipe1); + if((pubhubproc = fork()) == 0) + { + dup2(pipe1[1], 1); + close(pipe1[0]); + close(pipe1[1]); + execlp("wget", "wget", "-qO", "-", pubhubaddr, NULL); + perror("wget"); + exit(127); + } + close(pipe1[1]); + pubhubfd = pipe1[0]; + len = strlen(pubhubaddr); + if((len > 4) && !strcmp(pubhubaddr + len - 4, ".bz2")) + { + pipe(pipe2); + if(fork() == 0) + { + dup2(pipe1[0], 0); + dup2(pipe2[1], 1); + close(pipe1[0]); + close(pipe2[0]); + close(pipe2[1]); + execlp("bzcat", "bzcat", NULL); + perror("bzcat"); + exit(127); + } + close(pipe1[0]); + close(pipe2[1]); + pubhubfd = pipe2[0]; + } + pubhubtag = gdk_input_add(pubhubfd, GDK_INPUT_READ, pubhubfdcallback, NULL); +} + +void cb_main_dcnctbtn_clicked(GtkWidget *widget, gpointer data) +{ + GtkTreeIter iter; + int tag, id; + struct dc_response *resp; + + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server")); + return; + } + if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(main_fnetnodes)), NULL, &iter)) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("No hub selected")); + return; + } + gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1); + tag = dc_queuecmd(NULL, NULL, L"dcnct", L"%%i", id, NULL); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + dc_freeresp(resp); + } + handleresps(); +} + +void cb_main_phublist_cchange(GtkWidget *widget, gpointer data) +{ + GtkTreeIter iter; + GtkTreeModel *model; + char *addr; + + if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(main_phublist)), &model, &iter)) + return; + gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &addr, -1); + gtk_entry_set_text(GTK_ENTRY(main_fnaddr), addr); + g_free(addr); +} + +void cb_main_phublist_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) +{ + int tag; + struct dc_response *resp; + GtkTreeIter iter; + GtkTreeModel *model; + char *buf; + + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server")); + return; + } + model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget)); + if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path)) + return; + gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &buf, -1); + if(strchr(buf, ':') == NULL) + { + buf = g_realloc(buf, strlen(buf) + 5); + strcat(buf, ":411"); + } + tag = dc_queuecmd(NULL, NULL, L"cnct", L"dc", L"%%s", buf, NULL); + g_free(buf); + gtk_entry_set_text(GTK_ENTRY(main_fnaddr), ""); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + if(resp->code == 509) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse that address")); + dc_freeresp(resp); + } + handleresps(); +} + +void cb_main_chatnodes_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer uudata) +{ + GtkTreeIter iter; + int id; + struct dc_fnetnode *fn; + struct fndata *data; + + if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(fnmodel), &iter, path)) + return; + gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1); + if((fn = dc_findfnetnode(id)) == NULL) + return; + data = fn->udata; + curchat = id; + if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(fnmodel), &iter)) + { + do + { + gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1); + if(id == curchat) + gtk_list_store_set(fnmodel, &iter, 5, "gtk-apply", -1); + else + gtk_list_store_set(fnmodel, &iter, 5, NULL, -1); + } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(fnmodel), &iter)); + } + gtk_text_view_set_buffer(GTK_TEXT_VIEW(main_chatview), GTK_TEXT_BUFFER(data->textbuf)); +} + +void cb_main_chatstr_activate(GtkWidget *widget, gpointer data) +{ + int tag; + const char *buf; + struct dc_response *resp; + + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server")); + return; + } + if(curchat < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("No hub selected")); + return; + } + buf = gtk_entry_get_text(GTK_ENTRY(main_chatstr)); + tag = dc_queuecmd(NULL, NULL, L"sendchat", L"%%i", curchat, L"1", L"", L"%%s", buf, NULL); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + else if(resp->code == 504) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("This hub could not support all the types of characters in your chat message")); + else if(resp->code == 513) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("This hub does not support chatting")); + else if(resp->code != 200) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to chat (%i)"), resp->code); + dc_freeresp(resp); + } + gtk_entry_set_text(GTK_ENTRY(main_chatstr), ""); + handleresps(); +} + +void updatesrchfld(const char *simple) +{ + char *buf, *s; + char *p, *p2; + size_t bufsize, bufdata; + + s = sstrdup(simple); + buf = NULL; + bufsize = bufdata = 0; + p = s; + do + { + p2 = strchr(p, ' '); + if(p2 != NULL) + *(p2++) = 0; + if(*p) + { + if(bufdata > 0) + bufcat(buf, " & ", 3); + bufcat(buf, "N~", 2); + for(; *p; p++) + { + if(strchr("[]()$^.*?+\\|\"", *p) != NULL) + addtobuf(buf, '\\'); + addtobuf(buf, *p); + } + } + p = p2; + } while(p2 != NULL); + addtobuf(buf, 0); + gtk_entry_set_text(GTK_ENTRY(main_realsrch), buf); + free(buf); + free(s); +} + +void cb_main_simplesrch_changed(GtkWidget *widget, gpointer data) +{ + if(srchautoupdate) + return; + srchautoupdate = 1; + updatesrchfld(gtk_entry_get_text(GTK_ENTRY(main_simplesrch))); + srchautoupdate = 0; +} + +void cb_main_realsrch_changed(GtkWidget *widget, gpointer data) +{ + if(srchautoupdate) + return; + srchautoupdate = 1; + gtk_entry_set_text(GTK_ENTRY(main_simplesrch), ""); + srchautoupdate = 0; +} + +void cb_main_srchbtn_clicked(GtkWidget *widget, gpointer data) +{ + wchar_t **toks; + int tag; + struct dc_response *resp; + struct dc_intresp *ires; + + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server")); + return; + } + if(nextsrch != -1) /* Impossible case, but oh well... */ + return; + toks = dc_lexsexpr(icsmbstowcs((char *)gtk_entry_get_text(GTK_ENTRY(main_realsrch)), "UTF-8", NULL)); + if(*toks == NULL) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Please enter a search expression before searching")); + return; + } + tag = dc_queuecmd(NULL, NULL, L"search", L"all", L"%%a", toks, NULL); + dc_freewcsarr(toks); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 501) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not find any hubs to search on")); + else if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + else if(resp->code == 509) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse your search expression")); + else if(resp->code != 200) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to search (%i)"), resp->code); + if(resp->code == 200) + { + if((ires = dc_interpret(resp)) != NULL) + { + nextsrch = ires->argv[0].val.num; + srcheta = time(NULL) + ires->argv[1].val.num; + dc_freeires(ires); + } + gtk_widget_set_sensitive(main_realsrch, FALSE); + gtk_widget_set_sensitive(main_simplesrch, FALSE); + gtk_widget_set_sensitive(main_srchbtn, FALSE); + gtk_widget_set_sensitive(main_srchcanbtn, TRUE); + srchstatupdate(); + } + dc_freeresp(resp); + } + handleresps(); +} + +void cb_main_srchcanbtn_clicked(GtkWidget *widget, gpointer data) +{ + if(nextsrch == -1) + return; + dc_queuecmd(NULL, NULL, L"cansrch", L"%%i", nextsrch, NULL); + nextsrch = -1; + gtk_widget_set_sensitive(main_realsrch, TRUE); + gtk_widget_set_sensitive(main_simplesrch, TRUE); + gtk_widget_set_sensitive(main_srchbtn, TRUE); + gtk_widget_set_sensitive(main_srchcanbtn, FALSE); + srchstatupdate(); +} + +void cb_main_trlist_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + int id, tag; + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + struct dc_response *resp; + + if((event->type == GDK_KEY_PRESS) && (event->keyval == GDK_Delete)) + { + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); + if(gtk_tree_selection_get_selected(sel, &model, &iter)) + { + gtk_tree_model_get(model, &iter, 0, &id, -1); + tag = dc_queuecmd(NULL, NULL, L"cancel", L"%%i", id, NULL); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + else if(resp->code != 200) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to cancel (%i)"), resp->code); + dc_freeresp(resp); + } + handleresps(); + } + } +} + +void cb_main_srchres_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) +{ + int tag; + struct dc_response *resp; + GtkTreeIter iter; + GtkTreeModel *model; + int size, num; + char *tfnet, *tpeerid, *tfilename, *arg; + wchar_t *fnet, *peerid, *filename; + + if(dcfd < 0) + { + msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server")); + return; + } + model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget)); + if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path)) + return; + gtk_tree_model_get(model, &iter, 7, &num, -1); + if(num > 0) + return; + gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &tfnet, 1, &tpeerid, 3, &tfilename, 4, &size, -1); + fnet = icmbstowcs(tfnet, "UTF-8"); + peerid = icmbstowcs(tpeerid, "UTF-8"); + filename = icmbstowcs(tfilename, "UTF-8"); + if((fnet == NULL) || (peerid == NULL) || (filename == NULL)) + { + if(fnet != NULL) + free(fnet); + if(peerid != NULL) + free(peerid); + if(filename != NULL) + free(filename); + g_free(tfnet); + g_free(tpeerid); + g_free(tfilename); + return; + } + g_free(tfnet); + g_free(tpeerid); + g_free(tfilename); + arg = (char *)gtk_entry_get_text(GTK_ENTRY(main_dlarg)); + if(*arg) + tag = dc_queuecmd(NULL, NULL, L"download", fnet, L"%%ls", peerid, L"%%ls", filename, L"%%i", size, L"user", L"%%s", arg, NULL); + else + tag = dc_queuecmd(NULL, NULL, L"download", fnet, L"%%ls", peerid, L"%%ls", filename, L"%%i", size, NULL); + free(fnet); + free(peerid); + free(filename); + if((resp = dc_gettaggedrespsync(tag)) != NULL) + { + if(resp->code == 502) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that")); + if(resp->code != 200) + msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to queue the download (%i)"), resp->code); + dc_freeresp(resp); + } + handleresps(); +} + +gboolean srchfilterfunc(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + int slots; + int filteratall; + + filteratall = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(main_filternoslots)); + if(!filteratall) + return(TRUE); + gtk_tree_model_get(model, iter, 5, &slots, -1); + if(slots < 1) + return(FALSE); + return(TRUE); +} + +void cb_main_filternoslots_toggled(GtkToggleButton *widget, gpointer data) +{ + gtk_tree_model_filter_refilter(srchmodelfilter); +} + +void srchstatupdate(void) +{ + char buf[1024]; + + if(nextsrch == -1) + { + snprintf(buf, 1024, _("Ready to search")); + } else { + snprintf(buf, 1024, _("Search scheduled and will be submitted in %i seconds"), (int)(srcheta - time(NULL))); + } + if(strcmp(gtk_label_get_text(GTK_LABEL(main_srchstatus)), buf)) + gtk_label_set_text(GTK_LABEL(main_srchstatus), buf); +} + +gint srchstatupdatecb(gpointer data) +{ + srchstatupdate(); + return(TRUE); +} + +void initchattags(void) +{ + GtkTextTag *tag; + + chattags = gtk_text_tag_table_new(); + tag = gtk_text_tag_new("sender"); + g_object_set(tag, "foreground", "blue", NULL); + gtk_text_tag_table_add(chattags, tag); +} + +int main(int argc, char **argv) +{ + GtkWidget *wnd; + PangoFontDescription *monospacefont; + GtkTreeModel *sortmodel; + struct passwd *pwent; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + gtk_init(&argc, &argv); + dc_init(); + signal(SIGCHLD, SIG_IGN); + pubhubaddr = sstrdup("http://www.neo-modus.com/PublicHubList.config"); + dcserver = sstrdup("localhost"); + if((pwent = getpwuid(getuid())) == NULL) + { + fprintf(stderr, "could not get your passwd data"); + exit(1); + } + connectas = sstrdup(pwent->pw_name); + wnd = create_main_wnd(); + initchattags(); + + fnmodel = gtk_list_store_new(6, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); + gtk_tree_view_set_model(GTK_TREE_VIEW(main_fnetnodes), GTK_TREE_MODEL(fnmodel)); + gtk_tree_view_set_model(GTK_TREE_VIEW(main_chatnodes), GTK_TREE_MODEL(fnmodel)); + + pubhubmodel = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); + sortmodel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(pubhubmodel)); + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), 3, GTK_SORT_DESCENDING); + gtk_tree_view_set_model(GTK_TREE_VIEW(main_phublist), GTK_TREE_MODEL(sortmodel)); + g_object_unref(sortmodel); + + dlmodel = gtk_list_store_new(12, G_TYPE_INT, /* id */ + G_TYPE_INT, /* dir */ + G_TYPE_INT, /* state */ + G_TYPE_STRING, /* peerid */ + G_TYPE_STRING, /* peernick */ + G_TYPE_STRING, /* path */ + G_TYPE_INT, /* size */ + G_TYPE_INT, /* curpos */ + G_TYPE_STRING, /* stock */ + G_TYPE_FLOAT, /* percentage */ + G_TYPE_INT, /* error */ + G_TYPE_INT); /* errortime */ + gtk_tree_view_set_model(GTK_TREE_VIEW(main_downloads), GTK_TREE_MODEL(dlmodel)); + + ulmodel = gtk_list_store_new(12, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT); + gtk_tree_view_set_model(GTK_TREE_VIEW(main_uploads), GTK_TREE_MODEL(ulmodel)); + + srchmodel = gtk_tree_store_new(9, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_INT, G_TYPE_INT); + srchmodelfilter = GTK_TREE_MODEL_FILTER(gtk_tree_model_filter_new(GTK_TREE_MODEL(srchmodel), NULL)); + gtk_tree_model_filter_set_visible_func(srchmodelfilter, srchfilterfunc, NULL, NULL); + sortmodel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(srchmodelfilter)); + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), 4, GTK_SORT_DESCENDING); + gtk_tree_view_set_model(GTK_TREE_VIEW(main_srchres), GTK_TREE_MODEL(sortmodel)); + g_object_unref(sortmodel); + + monospacefont = pango_font_description_from_string("Monospace 10"); + gtk_widget_modify_font(main_chatview, monospacefont); + pango_font_description_free(monospacefont); + readconfigfile(); + updatesbar(_("Disconnected")); + gtk_widget_show(wnd); + if(autoconn) + dcconnect(dcserver); + g_timeout_add(500, srchstatupdatecb, NULL); + g_timeout_add(5000, ksupdatecb, NULL); + gtk_main(); + return(0); +} diff --git a/clients/gtk2/mainwnd.desc b/clients/gtk2/mainwnd.desc new file mode 100644 index 0000000..c5d653e --- /dev/null +++ b/clients/gtk2/mainwnd.desc @@ -0,0 +1,176 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +;prefix: main_ +;hasaccels: y +;hascolumns: y +;hasrenderers: y +:wnd name: wnd title: "Dolda Connect" var: y sig(delete_event):initdeath + :vbox + :menubar + :menuitem label: _Main + :menu + $menuitem name: connmenu label: _Connect sig: activate accel: "CONTROL+C" var: y + $menuitem name: dconnmenu label: _Disconnect sig: activate accel: "CONTROL+D" var: y sensitive: FALSE + $menusep + $menuitem name: sdmenu label: "_Shut down daemon" sig: activate + $smenuitem stock: QUIT sig(activate):initdeath + end + end + :menuitem label: Op_tions + :menu + $smenuitem name: prefmenu stock: PREFERENCES sig: activate + end + end + end + :vpaned name: pane1 var: y expand: TRUE fill: TRUE resize: 1 + :notebook + :vpaned name: pane2 var: y nblabel: "_Hub connections" resize: 2 + :vbox + $mlbl label: "Connected hu_bs" mwidget: fnetnodes + :sw expand: TRUE fill: TRUE + :treeview name: fnetnodes var: y rules: TRUE + :tvcol title: "Hub name" sortcol: 1 resizable: TRUE + $pixbufrend stock_id: 4 + $textrend text: 1 + end + $tvcol title: "# users" text: 3 sortcol: 3 resizable: TRUE + end + end + $btn name: dcnctbtn label: "D_isconnect" sig: clicked + end + :vbox + $mlbl label: "_Public hub list" mwidget: phublist + :sw expand: TRUE fill: TRUE + :treeview name: phublist var: y rules: TRUE searchcol: 0 sig(cursor-changed): cb_main_phublist_cchange sig(row-activated): cb_main_phublist_activate + $tvcol title: "# users" text: 3 sortcol: 3 resizable: TRUE + $tvcol title: "Name" text: 0 sortcol: 0 resizable: TRUE + $tvcol title: "Description" text: 2 sortcol: 2 resizable: TRUE + end + end + :table rows: 2 cols: 3 fill: TRUE + $mlbl label: "_Filter:" tx: 0 ty: 0 mwidget: pubhubfilter + $text name: pubhubfilter var: y expand: y fill: y sig: activate tx: 1 ty: 0 + $btn label: "_Get public hub list" sig(clicked): cb_main_pubhubfilter_activate tx: 2 ty: 0 + $mlbl label: "_Address:" tx: 0 ty: 1 mwidget: fnaddr + $text name: fnaddr var: y expand: y fill: y sig: activate tx: 1 ty: 1 + $btn label: "C_onnect" sig(clicked): cb_main_fnaddr_activate tx: 2 ty: 1 + end + end + end + :vpaned name: pane3 var: y nblabel: "_Chat" resize: 2 + :vbox + $mlbl label: "Hu_bs" mwidget: chatnodes + :sw fill: TRUE expand: TRUE + :treeview name: chatnodes var: y rules: TRUE sig(row-activated): cb_main_chatnodes_activate + :tvcol title: "Hub name" sortcol: 1 + $pixbufrend stock_id: 5 + $textrend text: 1 + end + end + end + end + :vbox + :sw fill: TRUE expand: TRUE + $textview name: chatview var: y editable: FALSE + end + :hbox + $mlbl label: "Chat st_ring:" mwidget: chatstr + $text name: chatstr var: y expand: TRUE fill: TRUE sig: activate + $btn label: "S_end" sig(clicked): cb_main_chatstr_activate + end + end + end + :vbox nblabel: "_Search" + :hbox + $mlbl label: "S_imple search:" mwidget: simplesrch + $text name: simplesrch var: y expand: TRUE fill: TRUE sig: changed sig(activate): cb_main_srchbtn_clicked + $btn name: srchbtn var: y label: "S_earch" sig: clicked + $btn name: srchcanbtn var: y label: "C_ancel" sig: clicked sensitive: FALSE + end + $chk name: filternoslots var: y label: "Displa_y results with free slots only" sig: toggled + :exp label: "Ad_vanced" name: advexp var: y + :table rows: 2 cols: 2 + $mlbl tx: 0 ty: 0 label: "C_omplete search expression:" mwidget: realsrch + $text tx: 1 ty: 0 name: realsrch var: y expand: y fill: y sig: changed sig(activate): cb_main_srchbtn_clicked + $mlbl tx: 0 ty: 1 label: "Filter ar_gument:" mwidget: dlarg + $text tx: 1 ty: 1 name: dlarg var: y expand: y fill: y + end + end + $lbl name: srchstatus var: y label: "Ready to search" fill: TRUE + $hr pad: 5 + $mlbl label: "Search _results:" mwidget: srchres + :sw expand: TRUE fill: TRUE + :treeview name: srchres var: y rules: TRUE searchcol: 3 sig(row-activated): cb_main_srchres_activate + :tvcol title: "#" sortcol: 7 resizable: FALSE + $textrend func: hidezerofunc funcdata: "(gpointer)7" + end + $tvcol title: "Peer name" text: 2 sortcol: 2 resizable: TRUE + $tvcol title: "File name" text: 3 sortcol: 3 resizable: TRUE expander: y + :tvcol title: "Size" sortcol: 4 resizable: TRUE + $textrend func: transnicebytefunc funcdata: "(gpointer)4" + end + $tvcol title: "Slots" text: 5 sortcol: 5 resizable: TRUE + :tvcol title: "Known speed" sortcol: 8 resizable: TRUE + $textrend func: speedtimefunc + end + $tvcol title: "Rsp. time" text: 6 sortcol: 6 resizable: TRUE + end + end + end + end + :notebook + :vbox nblabel: "_Downloads" + $mlbl label: "_List of downloads:" mwidget: downloads + :sw fill: TRUE expand: TRUE + :treeview name: downloads var: y sig(key-press-event): cb_main_trlist_keypress + :tvcol title: "User Name" + $pixbufrend stock_id: 8 + $textrend text: 4 + end + $tvcol title: "File Name" text: 5 + :tvcol title: "Size" + $textrend func: transnicebytefunc funcdata: "(gpointer)6" + end + :tvcol title: "Position" +#ifdef ENABLE_GTK2PBAR + $custrend newfunc: custom_cell_renderer_progress_new attr(percentage): 9 +#else + $textrend func: percentagefunc funcdata: "(gpointer)9" expand: FALSE +#endif + $textrend func: transnicebytefunc funcdata: "(gpointer)7" + end + :tvcol title: "Error" + $textrend func: transerrorinfo + end + end + end + end + :vbox nblabel: "_Uploads" + $mlbl label: "_List of uploads:" mwidget: uploads + :sw fill: TRUE expand: TRUE + :treeview name: uploads var: y sig(key-press-event): cb_main_trlist_keypress + :tvcol title: "User Name" + $pixbufrend stock_id: 8 + $textrend text: 4 + end + $tvcol title: "File Name" text: 5 + :tvcol title: "Size" + $textrend func: transnicebytefunc funcdata: "(gpointer)6" + end + :tvcol title: "Position" +#ifdef ENABLE_GTK2PBAR + $custrend newfunc: custom_cell_renderer_progress_new attr(percentage): 9 +#else + $textrend func: percentagefunc funcdata: "(gpointer)9" expand: FALSE +#endif + $textrend func: transnicebytefunc funcdata: "(gpointer)7" + end + end + end + end + end + end + $sbar var: y name: statusbar + end +end diff --git a/clients/gtk2/makegdesc b/clients/gtk2/makegdesc new file mode 100755 index 0000000..31cd9a0 --- /dev/null +++ b/clients/gtk2/makegdesc @@ -0,0 +1,422 @@ +#!/usr/bin/perl + +$tempvar = 0; + +sub printwidgets +{ + my($widget, $sl, $p, $sig, $cb, $data, $pf, $cpf, $mod, $key, @delayedlines); + $sl = $_[1]; + $p = " " . (" " x $sl); + $cpf = $_[2]; + @delayedlines = (); + foreach $widget (@{$_[0]}) + { + if($widget->{"type"} eq "wnd") + { + print "${p}stack[$sl] = gtk_window_new(GTK_WINDOW_TOPLEVEL);\n"; + if($options{"hasaccels"}) { + print "${p}gtk_window_add_accel_group(GTK_WINDOW(stack[$sl]), accel_group);\n"; + } + if($widget->{"title"}) { + print "${p}gtk_window_set_title(GTK_WINDOW(stack[$sl]), \"" . $widget->{"title"} . "\");\n"; + } + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + } + } elsif($widget->{"type"} =~ /[hv]box/) { + print "${p}stack[$sl] = gtk_" . $widget->{"type"} . "_new("; + print $widget->{"homo"}?"TRUE, ":"FALSE, "; + print $widget->{"spacing"} || "0"; + print ");\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_box_pack_start(GTK_BOX(stack[" . ($sl - 1) . "]), stack[$sl], "; + print (($widget->{"expand"} || $widget->{"parent"}->{"dexpand"})?"TRUE, ":"FALSE, "); + print (($widget->{"fill"} || $widget->{"parent"}->{"dfill"})?"TRUE, ":"FALSE, "); + print $widget->{"pad"} || "0"; + print ");\n"; + } + } elsif($widget->{"type"} eq "table") { + print "${p}stack[$sl] = gtk_table_new(" . $widget->{"rows"} . ", " . $widget->{"cols"}; + print ", " . (($widget->{"homo"} eq "TRUE")?"TRUE":"FALSE"); + print ");\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_table_attach(GTK_TABLE(stack[" . ($sl - 1) . "]), stack[$sl]"; + print ", " . $widget->{"tx"}; + print ", " . ($widget->{"tx"} + (defined($widget->{"tw"})?$widget->{"tw"}:1)); + print ", " . $widget->{"ty"}; + print ", " . ($widget->{"ty"} + (defined($widget->{"th"})?$widget->{"th"}:1)); + if($widget->{"fill"} eq "y") { + $widget->{"fillx"} = "y"; + $widget->{"filly"} = "y"; + } + if($widget->{"shrink"} eq "y") { + $widget->{"shrinkx"} = "y"; + $widget->{"shrinky"} = "y"; + } + if($widget->{"expand"} eq "y") { + $widget->{"expandx"} = "y"; + $widget->{"expandy"} = "y"; + } + print ", 0"; + print " | GTK_FILL" if $widget->{"fillx"} eq "y"; + print " | GTK_SHRINK" if $widget->{"shrinkx"} eq "y"; + print " | GTK_EXPAND" if $widget->{"expandx"} eq "y"; + print ", 0"; + print " | GTK_FILL" if $widget->{"filly"} eq "y"; + print " | GTK_SHRINK" if $widget->{"shrinky"} eq "y"; + print " | GTK_EXPAND" if $widget->{"expandy"} eq "y"; + print ", " . (defined($widget->{"padx"})?$widget->{"padx"}:"0"); + print ", " . (defined($widget->{"pady"})?$widget->{"pady"}:"0"); + print ");\n"; + } + } elsif($widget->{"type"} eq "btn") { + $widget->{"label"} || die("Can't have button without label\n"); + print "${p}stack[$sl] = gtk_button_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n"; + } elsif($widget->{"type"} eq "chk") { + $widget->{"label"} || die("Can't have check button without label\n"); + print "${p}stack[$sl] = gtk_check_button_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n"; + } elsif($widget->{"type"} eq "sbtn") { + $widget->{"stock"} || die("Can't have button without stock\n"); + print "${p}stack[$sl] = gtk_button_new_from_stock(GTK_STOCK_" . $widget->{"stock"} . ");\n"; + } elsif($widget->{"type"} eq "simg") { + $widget->{"stock"} || die("Can't have image without stock\n"); + $widget->{"size"} || die("Can't have image without size\n"); + print "${p}stack[$sl] = gtk_image_new_from_stock(GTK_STOCK_" . $widget->{"stock"} . ", GTK_ICON_SIZE_" . $widget->{"size"} . ");\n"; + } elsif($widget->{"type"} eq "lbl") { + $widget->{"label"} || die("Can't have label without label\n"); + print "${p}stack[$sl] = gtk_label_new(_(\"" . $widget->{"label"} . "\"));\n"; + } elsif($widget->{"type"} eq "mlbl") { + $widget->{"label"} || die("Can't have label without label\n"); + print "${p}stack[$sl] = gtk_label_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n"; + if(defined($widget->{"mwidget"})) + { + if($widget->{"var"} ne "y") { + $widget->{"var"} = "l"; + } + if(!defined($widget->{"name"})) { + $widget->{"name"} = "temp" . $tempvar++; + } + $str = "gtk_label_set_mnemonic_widget(GTK_LABEL("; + if($widget->{"var"} eq "y") { + $str .= $options{"prefix"}; + } + $str .= $widget->{"name"}; + $str .= "), " . $options{"prefix"} . $widget->{"mwidget"} . ");"; + push @delayedlines, ($str); + } + } elsif($widget->{"type"} eq "text") { + print "${p}stack[$sl] = gtk_entry_new();\n"; + if($widget->{"default"}) { + print "${p}gtk_entry_set_text(GTK_ENTRY(stack[$sl]), \"" . $widget->{"default"} . "\");\n"; + } + } elsif($widget->{"type"} eq "menubar") { + print "${p}stack[$sl] = gtk_menu_bar_new();\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_menu_shell_append(GTK_MENU_SHELL(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + } + } elsif($widget->{"type"} eq "menuitem") { + print "${p}stack[$sl] = gtk_menu_item_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_menu_item_set_submenu(GTK_MENU_ITEM(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + } + } elsif($widget->{"type"} eq "smenuitem") { + print "${p}stack[$sl] = gtk_image_menu_item_new_from_stock(GTK_STOCK_" . $widget->{"stock"} . ", accel_group);\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_menu_item_set_submenu(GTK_MENU_ITEM(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + } + } elsif($widget->{"type"} eq "menusep") { + print "${p}stack[$sl] = gtk_separator_menu_item_new();\n"; + } elsif($widget->{"type"} eq "menu") { + print "${p}stack[$sl] = gtk_menu_new();\n"; + if($options{"hasaccels"}) { + print "${p}gtk_menu_set_accel_group(GTK_MENU(stack[$sl]), accel_group);\n"; + } + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_menu_shell_append(GTK_MENU_SHELL(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + }; + $widget->{"noshow"} = 1; + } elsif($widget->{"type"} =~ /^[hv]paned$/) { + print "${p}stack[$sl] = gtk_" . $widget->{"type"} . "_new();\n"; + $widget->{"cur"} = 1; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_paned_pack" . ($widget->{"parent"}->{"cur"}) . "(GTK_PANED(stack[" . ($sl - 1) . "]), stack[$sl]"; + print ", " . ((index($widget->{"parent"}->{"resize"}, $widget->{"parent"}->{"cur"}) < 0)?"FALSE":"TRUE"); + print ", " . ((index($widget->{"parent"}->{"shrink"}, $widget->{"parent"}->{"cur"}) < 0)?"FALSE":"TRUE"); + print ");\n"; + $widget->{"parent"}->{"cur"}++; + } + } elsif($widget->{"type"} eq "notebook") { + print "${p}stack[$sl] = gtk_notebook_new();\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_notebook_append_page(GTK_NOTEBOOK(stack[" . ($sl - 1) . "]), stack[$sl]"; + print ", gtk_label_new_with_mnemonic(_(\"" . $widget->{"nblabel"} . "\"))"; + print ");\n"; + } + } elsif($widget->{"type"} eq "sw") { + print "${p}stack[$sl] = gtk_scrolled_window_new(NULL, NULL);\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + } + } elsif($widget->{"type"} eq "frame") { + print "${p}stack[$sl] = gtk_frame_new(_(\"" . $widget->{"label"} . "\"));\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + } + } elsif($widget->{"type"} eq "exp") { + print "${p}stack[$sl] = gtk_expander_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n"; + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n"; + } + } elsif($widget->{"type"} eq "treeview") { + print "${p}stack[$sl] = gtk_tree_view_new();\n"; + if(defined($widget->{"hvis"})) { + print "${p}gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(stack[$sl]), " . $widget->{"hvis"} . ");\n"; + } + if(defined($widget->{"rules"})) { + print "${p}gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(stack[$sl]), " . $widget->{"rules"} . ");\n"; + } + if(defined($widget->{"searchcol"})) { + print "${p}gtk_tree_view_set_search_column(GTK_TREE_VIEW(stack[$sl]), " . $widget->{"searchcol"} . ");\n"; + print "${p}gtk_tree_view_set_enable_search(GTK_TREE_VIEW(stack[$sl]), TRUE);\n"; + } + $pf = sub + { + my($widget, $p, $sl) = @_; + print "${p}gtk_tree_view_append_column(GTK_TREE_VIEW(stack[" . ($sl - 1) . "]), column);\n"; + if($widget->{"expander"} eq "y") { + print "${p}gtk_tree_view_set_expander_column(GTK_TREE_VIEW(stack[" . ($sl - 1) . "]), column);\n"; + } + } + } elsif($widget->{"type"} eq "tvcol") { + if(!defined($widget->{"subwidgets"})) + { + print "${p}column = gtk_tree_view_column_new_with_attributes("; + print "_(\"" . $widget->{"title"} . "\")"; + print ", gtk_cell_renderer_text_new()"; + if(defined($widget->{"text"})) { + print ", \"text\", " . $widget->{"text"}; + } + print ", NULL);\n"; + } else { + print "${p}column = gtk_tree_view_column_new();\n"; + print "${p}gtk_tree_view_column_set_title(column, _(\"" . $widget->{"title"} . "\"));\n"; + } + if(defined($widget->{"sortcol"})) { + print "${p}gtk_tree_view_column_set_sort_column_id(column, " . $widget->{"sortcol"} . ");\n"; + } + if(defined($widget->{"resizable"})) { + print "${p}gtk_tree_view_column_set_resizable(column, " . $widget->{"resizable"} . ");\n"; + } + $widget->{"noshow"} = 1; + $pf = sub + { + } + } elsif($widget->{"type"} eq "textrend") { + print "${p}renderer = gtk_cell_renderer_text_new();\n"; + print "${p}gtk_tree_view_column_pack_start(column, renderer, " . (defined($widget->{"expand"})?$widget->{"expand"}:"TRUE") . ");\n"; + if(defined($widget->{"text"})) { + print "${p}gtk_tree_view_column_add_attribute(column, renderer, \"text\", " . $widget->{"text"} . ");\n"; + } + if(defined($widget->{"func"})) { + print "${p}gtk_tree_view_column_set_cell_data_func(column, renderer, " . $widget->{"func"} . ", " . ($widget->{"funcdata"} || "NULL") . ", NULL);\n"; + } + $widget->{"noshow"} = 1; + } elsif($widget->{"type"} eq "custrend") { + print "${p}renderer = GTK_CELL_RENDERER(" . $widget->{"newfunc"} . "());\n"; + print "${p}gtk_tree_view_column_pack_start(column, renderer, " . (defined($widget->{"expand"})?$widget->{"expand"}:"FALSE") . ");\n"; + foreach $attr (keys %{$widget}) + { + if($attr =~ /attr\((\S+)\)/) + { + print "${p}gtk_tree_view_column_add_attribute(column, renderer, \"" . $1 . "\", " . $widget->{$attr} . ");\n"; + } + } + $widget->{"noshow"} = 1; + } elsif($widget->{"type"} eq "pixbufrend") { + print "${p}renderer = gtk_cell_renderer_pixbuf_new();\n"; + print "${p}gtk_tree_view_column_pack_start(column, renderer, FALSE);\n"; + if(defined($widget->{"stock_id"})) { + print "${p}gtk_tree_view_column_add_attribute(column, renderer, \"stock_id\", " . $widget->{"stock_id"} . ");\n"; + } + $widget->{"noshow"} = 1; + } elsif($widget->{"type"} eq "textview") { + print "${p}stack[$sl] = gtk_text_view_new();\n"; + if(defined($widget->{"editable"})) { + print "${p}gtk_text_view_set_editable(GTK_TEXT_VIEW(stack[$sl]), " . $widget->{"editable"} . ");\n"; + } + } elsif($widget->{"type"} eq "hr") { + print "${p}stack[$sl] = gtk_hseparator_new();\n"; + } elsif($widget->{"type"} eq "sbar") { + print "${p}stack[$sl] = gtk_statusbar_new();\n"; + if($widget->{"grip"} eq "n") { + print "${p}gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(stack[$sl]), FALSE);\n"; + } + } else { + print STDERR "Unknown widget: " . $widget->{"type"} ."\n"; + } + if($widget->{"sensitive"}) { + print "${p}gtk_widget_set_sensitive(stack[$sl], " . $widget->{"sensitive"} . ");\n"; + } + if($widget->{"var"} eq "y") { + print $p . $options{"prefix"} . $widget->{"name"} . " = stack[$sl];\n"; + } + if($widget->{"var"} eq "l") { + print $p . "GtkWidget *" . $widget->{"name"} . " = stack[$sl];\n"; + } + if($widget->{"sig"}) + { + while($widget->{"sig"} =~ /\G([\w_]+),?/g) { + print "${p}g_signal_connect(G_OBJECT(stack[$sl]), \"$1\", G_CALLBACK(cb_" . $options{"prefix"} . $widget->{"name"} . "_" . $1 . "), (gpointer)NULL);\n"; + } + } + if($widget->{"accel"}) + { + $mod = ""; + while($widget->{"accel"} =~ /\G(\w+)\+/gc) + { + $mod .= " | " if($mod); + $mod = $mod . "GDK_" . $1 . "_MASK"; + } + $mod || ($mod = "0"); + $widget->{"accel"} =~ /\G(\w+)/g; + $key = $1; + print "${p}gtk_widget_add_accelerator(stack[$sl], \"activate\", accel_group, GDK_$key, $mod, GTK_ACCEL_VISIBLE);\n"; + } + foreach $attr (keys %{$widget}) + { + if($attr =~ /^sig\((\S+)\)/) + { + $sig = $1; + if($widget->{$attr} =~ /([^,]*),(.*)/) + { + $cb = $1; + $data = $2; + } else { + $cb = $widget->{$attr}; + $data = "NULL"; + } + print "${p}g_signal_connect(G_OBJECT(stack[$sl]), \"$1\", G_CALLBACK($cb), (gpointer)$data);\n"; + } + } + if($widget->{"subwidgets"}) + { + print "$p\n"; + printwidgets($widget->{"subwidgets"}, $sl + 1, $pf); + } + if($sl > 0) + { + &$cpf($widget, $p, $sl); + if(!$widget->{"noshow"}) { + print "${p}gtk_widget_show(stack[$sl]);\n"; + } + } + print "$p\n"; + } + foreach $line (@delayedlines) + { + print $p . $line . "\n"; + } +} + +sub printvars +{ + my($widget); + foreach $widget (@{$_[0]}) + { + if($widget->{"var"}) + { + print "GtkWidget *" . $options{"prefix"} . $widget->{"name"} .";\n"; + } + printvars($widget->{"subwidgets"}) if($widget->{"subwidgets"}); + } +} + +sub dequote +{ + my($text); + ($text) = @_; + $text =~ s/([^\\]|^)\"/$1/g; + $text =~ s/\\(.)/$1/g; + return $text; +} + +$rootwidgets = []; +@estack = ($rootwidgets); +@wstack = (); +$curwidget = 0; +$maxstack = 1; + +while(<>) +{ + chomp; + s/(^|\s+)\#.*$//; + s/^\s*//; + if(/^;\s*(\w+)\s*:\s*(\w.*)/) + { + $options{$1} = $2; + } elsif(/^([:\$])\s*(\w+)/g) { + $curwidget = {"type" => $2}; + push @{$estack[0]}, $curwidget; + if((scalar @wstack) > 0) { + $curwidget->{"parent"} = $wstack[0]; + } + if($1 eq ":") + { + unshift @estack, ($curwidget->{"subwidgets"} = []); + unshift @wstack, $curwidget; + } + $maxstack = (scalar @estack) if((scalar @estack) > $maxstack); + while(/\G\s*(\S+)\s*:\s*((\w+|\"([^\\\"]+|\\.)*([^\\]|)\"|\\.)+)/g) + { + $curwidget->{$1} = dequote($2); + } + } elsif(/^%\s*(\S+)\s*:\s*((\w+|\"([^\\\"]+|\\.)*([^\\]|)\"|\\.)+)/) { + $curwidget || die("No current widget\n"); + $curwidget->{$1} = dequote($2); + } elsif(/^end/) { + shift @estack; + shift @wstack; + $curwidget = $wstack[0] if((scalar @wstack) > 0); + } elsif(!$_) { + } else { + print STDERR "Invalid construct: $_\n"; + } +} + +printvars $rootwidgets; +print "\n"; +print "GtkWidget *create_" . $options{"prefix"} . "wnd(void)\n"; +print "{\n"; +print " GtkWidget *stack[$maxstack];\n"; +print " GtkAccelGroup *accel_group;\n" if $options{"hasaccels"}; +print " GtkTreeViewColumn *column;\n" if $options{"hascolumns"}; +print " GtkCellRenderer *renderer;\n" if $options{"hasrenderers"}; +print " \n"; +print " accel_group = gtk_accel_group_new();\n" if $options{"hasaccels"}; +printwidgets $rootwidgets, 0; +print " return(stack[0]);\n"; +print "}\n"; diff --git a/clients/gtk2/pref.desc b/clients/gtk2/pref.desc new file mode 100644 index 0000000..f14aaeb --- /dev/null +++ b/clients/gtk2/pref.desc @@ -0,0 +1,10 @@ +;prefix: pref_ +:table rows: 4 cols: 2 + $mlbl label: "_Public hub list URL:" tx: 0 ty: 0 mwidget: pubhuburl + $text name: pubhuburl var: y expand: y fill: y tx: 1 ty: 0 + $mlbl label: "_Dolda connect user name:" tx: 0 ty: 1 mwidget: connectas + $text name: connectas var: y expand: y fill: y tx: 1 ty: 1 + $mlbl label: "Dolda Connect _server:" tx: 0 ty: 2 mwidget: dcserver + $text name: dcserver var: y expand: y fill: y tx: 1 ty: 2 + $chk name: autoconn var: y label: "Connect _automatically on startup" tx: 0 ty: 3 tw: 2 +end diff --git a/clients/gtk2/progressbar.c b/clients/gtk2/progressbar.c new file mode 100644 index 0000000..2dff9b2 --- /dev/null +++ b/clients/gtk2/progressbar.c @@ -0,0 +1,344 @@ +/* Taken from the GTK TreeView tutorial on gtk.org. + * Slightly modified. + */ + +#include "progressbar.h" + +/* This is based mainly on GtkCellRendererProgress + * in GAIM, written and (c) 2002 by Sean Egan + * (Licensed under the GPL), which in turn is + * based on Gtk's GtkCellRenderer[Text|Toggle|Pixbuf] + * implementation by Jonathan Blandford */ + +/* Some boring function declarations: GObject type system stuff */ + +static void custom_cell_renderer_progress_init (CustomCellRendererProgress *cellprogress); + +static void custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass); + +static void custom_cell_renderer_progress_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void custom_cell_renderer_progress_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void custom_cell_renderer_progress_finalize (GObject *gobject); + + +/* These functions are the heart of our custom cell renderer: */ + +static void custom_cell_renderer_progress_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); + +static void custom_cell_renderer_progress_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + guint flags); + + +enum +{ + PROP_PERCENTAGE = 1, +}; + +static gpointer parent_class; + + +/*************************************************************************** + * + * custom_cell_renderer_progress_get_type: here we register our type with + * the GObject type system if we + * haven't done so yet. Everything + * else is done in the callbacks. + * + ***************************************************************************/ + +GType +custom_cell_renderer_progress_get_type (void) +{ + static GType cell_progress_type = 0; + + if (cell_progress_type) + return cell_progress_type; + + if (1) + { + static const GTypeInfo cell_progress_info = + { + sizeof (CustomCellRendererProgressClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) custom_cell_renderer_progress_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (CustomCellRendererProgress), + 0, /* n_preallocs */ + (GInstanceInitFunc) custom_cell_renderer_progress_init, + }; + + /* Derive from GtkCellRenderer */ + cell_progress_type = g_type_register_static (GTK_TYPE_CELL_RENDERER, + "CustomCellRendererProgress", + &cell_progress_info, + 0); + } + + return cell_progress_type; +} + + +/*************************************************************************** + * + * custom_cell_renderer_progress_init: set some default properties of the + * parent (GtkCellRenderer). + * + ***************************************************************************/ + +static void +custom_cell_renderer_progress_init (CustomCellRendererProgress *cellrendererprogress) +{ + GTK_CELL_RENDERER(cellrendererprogress)->mode = GTK_CELL_RENDERER_MODE_INERT; + GTK_CELL_RENDERER(cellrendererprogress)->xpad = 2; + GTK_CELL_RENDERER(cellrendererprogress)->ypad = 2; +} + + +/*************************************************************************** + * + * custom_cell_renderer_progress_class_init: + * + * set up our own get_property and set_property functions, and + * override the parent's functions that we need to implement. + * And make our new "percentage" property known to the type system. + * If you want cells that can be activated on their own (ie. not + * just the whole row selected) or cells that are editable, you + * will need to override 'activate' and 'start_editing' as well. + * + ***************************************************************************/ + +static void +custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass) +{ + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + parent_class = g_type_class_peek_parent (klass); + object_class->finalize = custom_cell_renderer_progress_finalize; + + /* Hook up functions to set and get our + * custom cell renderer properties */ + object_class->get_property = custom_cell_renderer_progress_get_property; + object_class->set_property = custom_cell_renderer_progress_set_property; + + /* Override the two crucial functions that are the heart + * of a cell renderer in the parent class */ + cell_class->get_size = custom_cell_renderer_progress_get_size; + cell_class->render = custom_cell_renderer_progress_render; + + /* Install our very own properties */ + g_object_class_install_property (object_class, + PROP_PERCENTAGE, + g_param_spec_double ("percentage", + "Percentage", + "The fractional progress to display", + 0, 1, 0, + G_PARAM_READWRITE)); +} + + +/*************************************************************************** + * + * custom_cell_renderer_progress_finalize: free any resources here + * + ***************************************************************************/ + +static void +custom_cell_renderer_progress_finalize (GObject *object) +{ +/* + CustomCellRendererProgress *cellrendererprogress = CUSTOM_CELL_RENDERER_PROGRESS(object); +*/ + + /* Free any dynamically allocated resources here */ + + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + + +/*************************************************************************** + * + * custom_cell_renderer_progress_get_property: as it says + * + ***************************************************************************/ + +static void +custom_cell_renderer_progress_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *psec) +{ + CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS(object); + + switch (param_id) + { + case PROP_PERCENTAGE: + g_value_set_double(value, cellprogress->progress); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, psec); + break; + } +} + + +/*************************************************************************** + * + * custom_cell_renderer_progress_set_property: as it says + * + ***************************************************************************/ + +static void +custom_cell_renderer_progress_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (object); + + switch (param_id) + { + case PROP_PERCENTAGE: + cellprogress->progress = g_value_get_double(value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } +} + +/*************************************************************************** + * + * custom_cell_renderer_progress_new: return a new cell renderer instance + * + ***************************************************************************/ + +GtkCellRenderer * +custom_cell_renderer_progress_new (void) +{ + return g_object_new(CUSTOM_TYPE_CELL_RENDERER_PROGRESS, NULL); +} + + +/*************************************************************************** + * + * custom_cell_renderer_progress_get_size: crucial - calculate the size + * of our cell, taking into account + * padding and alignment properties + * of parent. + * + ***************************************************************************/ + +#define FIXED_WIDTH 100 +#define FIXED_HEIGHT 12 + +static void +custom_cell_renderer_progress_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + gint calc_width; + gint calc_height; + + calc_width = (gint) cell->xpad * 2 + FIXED_WIDTH; + calc_height = (gint) cell->ypad * 2 + FIXED_HEIGHT; + + if (width) + *width = calc_width; + + if (height) + *height = calc_height; + + if (cell_area) + { + if (x_offset) + { + *x_offset = cell->xalign * (cell_area->width - calc_width); + *x_offset = MAX (*x_offset, 0); + } + + if (y_offset) + { + *y_offset = cell->yalign * (cell_area->height - calc_height); + *y_offset = MAX (*y_offset, 0); + } + } +} + + +/*************************************************************************** + * + * custom_cell_renderer_progress_render: crucial - do the rendering. + * + ***************************************************************************/ + +static void +custom_cell_renderer_progress_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + guint flags) +{ + CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (cell); + GtkStateType state; + gint width, height; + gint x_offset, y_offset; + + custom_cell_renderer_progress_get_size (cell, widget, cell_area, + &x_offset, &y_offset, + &width, &height); + + if (GTK_WIDGET_HAS_FOCUS (widget)) + state = GTK_STATE_ACTIVE; + else + state = GTK_STATE_NORMAL; + + width -= cell->xpad*2; + height -= cell->ypad*2; + + gtk_paint_box (widget->style, + window, + GTK_STATE_NORMAL, GTK_SHADOW_IN, + NULL, widget, "trough", + cell_area->x + x_offset + cell->xpad, + cell_area->y + y_offset + cell->ypad, + width - 1, height - 1); + + gtk_paint_box (widget->style, + window, + state, GTK_SHADOW_OUT, + NULL, widget, "bar", + cell_area->x + x_offset + cell->xpad, + cell_area->y + y_offset + cell->ypad, + width * cellprogress->progress, + height - 1); +} diff --git a/clients/gtk2/progressbar.h b/clients/gtk2/progressbar.h new file mode 100644 index 0000000..4b773f3 --- /dev/null +++ b/clients/gtk2/progressbar.h @@ -0,0 +1,44 @@ +/* Taken from the GTK TreeView tutorial on gtk.org. */ + +#ifndef _custom_cell_renderer_progressbar_included_ +#define _custom_cell_renderer_progressbar_included_ + +#include + +/* Some boilerplate GObject type check and type cast macros. + * 'klass' is used here instead of 'class', because 'class' + * is a c++ keyword */ + +#define CUSTOM_TYPE_CELL_RENDERER_PROGRESS (custom_cell_renderer_progress_get_type()) +#define CUSTOM_CELL_RENDERER_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgress)) +#define CUSTOM_CELL_RENDERER_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass)) +#define CUSTOM_IS_CELL_PROGRESS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS)) +#define CUSTOM_IS_CELL_PROGRESS_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TYPE_CELL_RENDERER_PROGRESS)) +#define CUSTOM_CELL_RENDERER_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass)) + +typedef struct _CustomCellRendererProgress CustomCellRendererProgress; +typedef struct _CustomCellRendererProgressClass CustomCellRendererProgressClass; + +/* CustomCellRendererProgress: Our custom cell renderer + * structure. Extend according to need */ + +struct _CustomCellRendererProgress +{ + GtkCellRenderer parent; + gdouble progress; +}; + + +struct _CustomCellRendererProgressClass +{ + GtkCellRendererClass parent_class; +}; + + +GType custom_cell_renderer_progress_get_type (void); + +GtkCellRenderer *custom_cell_renderer_progress_new (void); + + +#endif /* _custom_cell_renderer_progressbar_included_ */ + diff --git a/clients/hellodolda.jpg b/clients/hellodolda.jpg new file mode 100644 index 0000000..cab979a Binary files /dev/null and b/clients/hellodolda.jpg differ diff --git a/clients/test.c b/clients/test.c new file mode 100644 index 0000000..25deedb --- /dev/null +++ b/clients/test.c @@ -0,0 +1,51 @@ +#include +#include +#include + +#include +#include + +void authcallback(int err, wchar_t *reason, void *data) +{ + printf("Logged in: %i\n", err); +} + +int main(int argc, char **argv) +{ + int i; + struct pollfd pfd; + int fd, done; + struct dc_response *resp; + struct dc_intresp *ires; + + dc_init(); + fd = dc_connect("localhost", -1); + done = 0; + while(!done) + { + pfd.fd = fd; + pfd.events = POLLIN; + if(dc_wantwrite()) + pfd.events = POLLOUT; + if(poll(&pfd, 1, -1) < 0) + { + perror("poll"); + exit(1); + } + if((pfd.revents & POLLIN) && dc_handleread()) + done = 1; + if((pfd.revents & POLLOUT) && dc_handlewrite()) + done = 1; + while((resp = dc_getresp()) != NULL) + { + if(resp->cmdname == NULL) + { + printf("Connected\n"); + dc_loginasync(NULL, 0, NULL, authcallback, NULL); + } + dc_freeresp(resp); + } + } + dc_cleanup(); + return(0); +} diff --git a/config/CVS/Entries b/config/CVS/Entries new file mode 100644 index 0000000..f757bc9 --- /dev/null +++ b/config/CVS/Entries @@ -0,0 +1,7 @@ +/Makefile.am/1.5/Sun Dec 19 02:58:01 2004// +/dc-filter/1.2/Wed Dec 15 18:59:51 2004// +/dc-filtercmd/1.1/Wed Dec 15 19:01:20 2004// +/doldacond.conf/1.1/Sat Sep 18 14:53:07 2004// +/locktouch.c/1.1/Sat Sep 18 03:36:41 2004// +/speedrec.c/1.2/Fri Sep 24 09:48:38 2004// +D diff --git a/config/CVS/Repository b/config/CVS/Repository new file mode 100644 index 0000000..65bc6a8 --- /dev/null +++ b/config/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/config diff --git a/config/CVS/Root b/config/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/config/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/config/Makefile.am b/config/Makefile.am new file mode 100644 index 0000000..56c357c --- /dev/null +++ b/config/Makefile.am @@ -0,0 +1,21 @@ +bin_PROGRAMS=locktouch +libexec_PROGRAMS=speedrec + +speedrec_SOURCES=speedrec.c + +locktouch_SOURCES=locktouch.c + +EXTRA_DIST=doldacond.conf dc-filter dc-filtercmd + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(sysconfdir); \ + for file in doldacond.conf; do \ + if [ ! -e $(DESTDIR)$(sysconfdir)/$$file ]; then \ + $(INSTALL_DATA) $(srcdir)/$$file $(DESTDIR)$(sysconfdir)/$$file; \ + fi; \ + done; \ + for file in dc-filter dc-filtercmd; do \ + if [ ! -e $(DESTDIR)$(sysconfdir)/$$file ]; then \ + $(INSTALL) $(srcdir)/$$file $(DESTDIR)$(sysconfdir)/$$file; \ + fi; \ + done diff --git a/config/dc-filter b/config/dc-filter new file mode 100644 index 0000000..0b31bbf --- /dev/null +++ b/config/dc-filter @@ -0,0 +1,99 @@ +#!/bin/sh +trap "" SIGHUP SIGPIPE +if [ ! -d $HOME/dc ]; then mkdir $HOME/dc; fi +if [ ! -d $HOME/dc/done ]; then mkdir $HOME/dc/done; fi +if [ ! -d $HOME/dc/resume ]; then mkdir $HOME/dc/resume; fi +if [ ! -d $HOME/dc/users ]; then mkdir $HOME/dc/users; fi +exec 2>>$HOME/dc/filterlog +cd $HOME/dc +unset speedrec +for dir in /usr/libexec /usr/local/libexec; do + if [ -x "${dir}/speedrec" ]; then + speedrec="${dir}/speedrec" + fi +done +if [ -z "$speedrec" ]; then + echo "could not find speedrec - using cat instead" >&2 +fi +maxsize=0 +unset resfile +unset infofile +found=y +while [ -z "$resfile" -a "$found" = y ]; do + unset found + for file in resume/*.info; do + if [ ! -r "$file" ]; then continue; fi + . "$file" + if [ "$filesize" -eq "$2" ]; then + thisfile="${file%.info}" + if [ ! -e "${thisfile}.lock" ]; then + size="$(wc -c <"$thisfile")" + found=y + if [ "$size" -gt "$maxsize" ]; then + maxsize="$size" + resfile="$thisfile" + infofile="$file" + fi + fi + fi + done + if [ -n "$resfile" ]; then + if ! locktouch "${resfile}.lock"; then + unset resfile + fi + fi +done +unset "${!dcarg_@}" +if [ -n "$resfile" ]; then + . "${resfile}.info" +fi +origname="$1" +shift +filesize="$1" +shift +peername="$1" +shift +while [ $# -gt 1 ]; do + rec="$1" + shift + val="$1" + shift + declare "dcarg_$rec"="$val" +done +if [ -z "$resfile" ]; then + resfile="$(mktemp resume/resXXXXXX)" + chmod 644 "$resfile" + maxsize=0 + >"${resfile}.lock" +fi +declare -p origname filesize "${!dcarg_@}" >"${resfile}.info" +echo "resume $maxsize" +if [ -z "$speedrec" ]; then + cat >>"$resfile" +else + peerfile="$(tr / _ <<<"$peername")" + "$speedrec" "$HOME/dc/users/$peerfile" >>"$resfile" +fi +size="$(wc -c <"$resfile")" +if [ "$size" -eq 0 ]; then + rm -f "$resfile" "${resfile}.info" "${resfile}.lock" + exit 1 +fi +rm -f "${resfile}.lock" +if [ "$size" -lt "$filesize" ]; then + exit 1 # Exit code != 0 means restart transfer +fi +rm -f "${resfile}.info" + +destdir="$HOME/dc/done" + +newname="$destdir"/"$origname" +if [ -e "$newname" ]; then + newname="$(mktemp "${newname}XXXXXX")" +fi +mv "$resfile" "$newname" +if [ -x $HOME/dc/complete ]; then + export "${!dcarg_@}" + exec $HOME/dc/complete "$newname" +fi +exit 0 diff --git a/config/dc-filtercmd b/config/dc-filtercmd new file mode 100755 index 0000000..96444eb --- /dev/null +++ b/config/dc-filtercmd @@ -0,0 +1,42 @@ +#!/bin/sh +cmd="$1" +shift + +if [ "$cmd" = userspeeda ]; then + while [ $# -gt 0 ]; do + peerfile="$(tr / _ <<<"$1")" + if [ -r "$HOME/dc/users/$peerfile" ]; then + speed="$((read num; read max; read nent; avg=0; for i in $(seq 1 $nent); do read ent; let avg+=$ent; done; let avg/=$nent; echo $avg) < "$HOME/dc/users/$peerfile")" + echo $speed + else + echo -1 + fi + shift + done +fi + +if [ "$cmd" = userspeedm ]; then + while [ $# -gt 0 ]; do + peerfile="$(tr / _ <<<"$1")" + if [ -r "$HOME/dc/users/$peerfile" ]; then + speed="$((read num; read max; echo $max) < "$HOME/dc/users/$peerfile")" + echo $speed + else + echo -1 + fi + shift + done +fi + +if [ "$cmd" = rmtag ]; then + for f in "$HOME"/dc/resume/*.info; do + if [ ! -e "${f%.info}.lock" ]; then + unset dcarg_tag + . "$f" + if [ "$dcarg_tag" = "$1" ]; then + rm "$f" + rm "${f%.info}" + fi + fi + done +fi diff --git a/config/doldacond.conf b/config/doldacond.conf new file mode 100644 index 0000000..dd7b332 --- /dev/null +++ b/config/doldacond.conf @@ -0,0 +1,56 @@ +# Dolda Connect default configuration file + +# Default nick name +set cli.defnick DoldaConnectUser + +# Net mode: +# 0 - Active +# 1 - Passive +# 2 - Will be SOCKS proxy when implemented +set net.mode 0 + +# If 1, then accept UI connections only over the loopback interface +set ui.onlylocal 0 + +# If 1, then enable authenticationless login (don't use without turning on ui.onlylocal, unless you know what you're doing) +set auth.authless 0 + +set transfer.slots 6 + + +# Settings specific to the Direct Connect network: + +# Valid strings are: +# 56Kbps +# Satellite +# DSL +# Cable +# LAN(T1) +# LAN(T3) +set dc.speedstring LAN(T1) +# Description string: +set dc.desc Dolda Connect User + +# Shares: "share sharename sharepath", where sharename is the name of the share as the peers see it, and sharepath is the local path to the shared files +share Video /home/pub/divx +share Music /home/pub/MP3 + +# Allowed users and privileges +# Syntax is "user username [-]privs..." +# username can be default to match any user that doesn't match the other entries +# privs can be any of the following: +# disallow - The only negative permission, which disallows a user from logging in +# admin - Involves commands controlling the function of the daemon, such as shutting it down remotely +# fnetctl - Allows connecting and disconnecting fnetnodes (or "Hubs") +# trans - Allows queuing of transfers +# transcu - Allows cancelling of uploads +# chat - Allows sending and receiving of chat messages +# srch - Allows submitting of search requests +# all - Equivalent of specifying all the above permissions, including disallow +# A minus sign preceding a privilege specification revokes that privilege (or, for "all", revokes all privileges, _including_ "disallow"). +# The privileges are scanned from left to right, so "all -disallow" is not the same as "-disallow all". + +# In this default configuration, the user called "youruser" (that is, change it), +# has all privileges except, of course, "disallow", and all other users are disallowed from logging in. +user default disallow +user youruser all -disallow diff --git a/config/locktouch.c b/config/locktouch.c new file mode 100644 index 0000000..da7b862 --- /dev/null +++ b/config/locktouch.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int fd; + + if(argc < 2) + { + fprintf(stderr, "usage: locktouch lockfile\n"); + exit(1); + } + if((fd = open(argv[1], O_CREAT | O_EXCL, 0666)) < 0) + { + if(errno != EEXIST) + { + perror(argv[1]); + exit(2); + } + exit(1); + } + close(fd); + return(0); +} diff --git a/config/speedrec.c b/config/speedrec.c new file mode 100644 index 0000000..be0ed2d --- /dev/null +++ b/config/speedrec.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char buf[4096]; +volatile int eof; + +void sighandler(int sig) +{ + eof = 1; +} + +int main(int argc, char **argv) +{ + int i; + int ret, fd; + time_t starttime, endtime; + long long numbytes; + size_t datalen; + struct pollfd pfd[2]; + FILE *recfile; + int thisrec, numrecs, numuses, maxrec; + int recs[5]; + + if(argc < 2) + { + fprintf(stderr, "usage: speedrec recfile\n"); + exit(1); + } + numbytes = 0; + starttime = endtime = 0; + datalen = 0; + eof = 0; + signal(SIGHUP, sighandler); + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); + while(1) + { + pfd[0].fd = 0; + if(eof || (datalen >= sizeof(buf))) + pfd[0].events = 0; + else + pfd[0].events = POLLIN; + pfd[1].fd = 1; + if(datalen > 0) + pfd[1].events = POLLOUT; + else + pfd[1].events = 0; + pfd[0].revents = pfd[1].revents = 0; + ret = poll(pfd, 2, -1); + if((ret < 0) && (errno != EINTR)) + { + perror("cannot poll"); + exit(1); + } + if(pfd[0].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL)) + { + ret = read(0, buf + datalen, sizeof(buf) - datalen); + if((ret < 0) && (errno != EINTR)) + { + perror("cannot read"); + exit(1); + } + if(ret == 0) + eof = 1; + if(ret > 0) + { + datalen += ret; + if(starttime == 0) + starttime = time(NULL); + endtime = time(NULL); + } + numbytes += ret; + } + if(pfd[1].revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL)) + { + ret = write(1, buf, datalen); + if((ret < 0) && (errno != EINTR)) + { + perror("cannot write"); + exit(1); + } + memmove(buf, buf + ret, datalen -= ret); + } + if(eof && (datalen == 0)) + break; + } + if((starttime == 0) || (endtime == 0) || (endtime == starttime)) + exit(0); + if(numbytes == 0) + exit(0); + thisrec = (int)(numbytes / ((long long)(endtime - starttime))); + if((fd = open(argv[1], O_RDWR | O_CREAT, 0666)) < 0) + { + perror(argv[1]); + exit(1); + } + recfile = fdopen(fd, "r+"); + close(0); + close(1); + flock(fd, LOCK_EX); + if(fscanf(recfile, "%i\n", &numuses) < 1) + numuses = 0; + if(fscanf(recfile, "%i\n", &maxrec) < 1) + maxrec = 0; + if(fscanf(recfile, "%i\n", &numrecs) < 1) + numrecs = 0; + for(i = 0; i < numrecs; i++) + fscanf(recfile, "%i\n", &recs[i]); + if(numrecs == 5) + { + for(i = 0; i < 4; i++) + recs[i] = recs[i + 1]; + numrecs = 4; + } + recs[numrecs++] = thisrec; + rewind(recfile); + ftruncate(fd, 0); + fprintf(recfile, "%i\n", numuses + 1); + fprintf(recfile, "%i\n", (thisrec > maxrec)?thisrec:maxrec); + fprintf(recfile, "%i\n", numrecs); + for(i = 0; i < numrecs; i++) + fprintf(recfile, "%i\n", recs[i]); + flock(fd, LOCK_UN); + fclose(recfile); + return(0); +} diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..139635b --- /dev/null +++ b/configure.in @@ -0,0 +1,213 @@ +AC_INIT(daemon/main.c) +AM_INIT_AUTOMAKE([doldaconnect], [0.1]) +AM_CONFIG_HEADER(config.h) + +AC_PROG_CC +AC_PROG_INSTALL + +AM_GNU_GETTEXT_VERSION(0.12.1) +AM_GNU_GETTEXT([external]) +AM_PROG_LIBTOOL + +AC_CHECK_LIB(pam, pam_start, , AC_MSG_ERROR([*** must have PAM])) +AC_CHECK_LIB(dl, dlopen, , AC_MSG_ERROR([*** PAM requires a working dynamic loader (libdl)])) +AC_CHECK_LIB(z, deflate, , AC_MSG_ERROR([*** must have zlib])) +AC_CHECK_LIB(bz2, BZ2_bzWriteOpen, , AC_MSG_ERROR([*** must have bzlib])) + +clients= +extlibs= +experimental=no + +AC_CHECK_PROG([PKG_CONFIG], pkg-config, yes, no) + +AC_ARG_WITH(gtk2, [ --with-gtk2 Enable GTK2 support]) +if test "$with_gtk2" = no; then + HAS_GTK2=no +fi +if test "$HAS_GTK2" != no; then + if test "$PKG_CONFIG" = yes; then + AC_MSG_CHECKING([for GTK2 package information]) + if pkg-config --modversion gtk+-2.0 >/dev/null 2>&1; then + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + HAS_GTK2=no + fi + if test "$HAS_GTK2" != no; then + AC_CHECK_LIB(gtk-x11-2.0, gtk_init, [HAS_GTK2=yes], [HAS_GTK2=no], `pkg-config --libs gtk+-2.0`) + fi + if test "$HAS_GTK2" = yes; then + cpp_bak="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS `pkg-config --cflags gtk+-2.0`" + AC_CHECK_HEADER(gtk/gtk.h, [], [HAS_GTK2=no]) + CPPFLAGS="$cpp_bak" + fi + else + HAS_GTK2=no + fi +fi +if test "$with_gtk2" = yes -a "$HAS_GTK2" = no; then + AC_MSG_ERROR([*** cannot find GTK2 on this system]) +fi + +gtk2ui_msg=No +AC_ARG_ENABLE(gtk2ui, [ --enable-gtk2ui Enable the GTK2 user interface]) +if test "$enable_gtk2ui" = yes -a "$HAS_GTK2" = no; then + AC_MSG_ERROR([*** cannot build the GTK2 UI without a GTK2 library]) +fi +if test "$enable_gtk2ui" != no -a "$HAS_GTK2" = yes; then + clients="$clients gtk2" + gtk2ui_msg=Yes +fi + +gtk2pbar_msg=No +AH_TEMPLATE(ENABLE_GTK2PBAR, [define to compile GTK2 progress bars (experimental)]) +AC_ARG_ENABLE(gtk2pbar, [ --enable-gtk2pbar Enable GTK2 progress bars (experimental)]) +if test "$enable_gtk2pbar" = yes; then + if test "$HAS_GTK2" = no; then + AC_MSG_ERROR([*** cannot build GTK2 progress bars without GTK2]) + fi + experimental=yes + gtk2pbar_msg=Yes + AC_DEFINE(ENABLE_GTK2PBAR) +fi + +gnometrapplet_msg=No +AC_ARG_ENABLE(gnomeapplet, [ --enable-gnomeapplet Enable GNOME transfer applet (experimental)]) +if test "$enable_gnomeapplet" = yes; then + experimental=yes + gnometrapplet_msg=Yes + clients="$clients gnome-trans-applet" +fi + +guile_msg=No +if test "$with_guile" = yes; then + GUILE_FLAGS + extlibs="$extlibs guile" + guile_msg=Yes +fi + +krb_msg=No +AH_TEMPLATE(HAVE_KRB5, [define to compile support for Kerberos 5 (not GSS-API) authentication]) +AC_ARG_WITH(krb5, [ --with-krb5[=PATH] Enable Kerberos 5 (not GSSAPI) authentication]) +if test "$with_krb5" != no; then + cpp_bak="$CPPFLAGS" + ld_bak="$LDFLAGS" + if test "$with_krb5" != yes; then + CPPFLAGS="$CPPFLAGS -I${with_krb5}/include" + LDFLAGS="$LDFLAGS -L${with_krb5}/lib" + fi + AC_CHECK_LIB(krb5, krb5_init_context, [HAS_KRB5=yes], [HAS_KRB5=no]) + if test "$HAS_KRB5" = yes; then + AC_CHECK_HEADER(com_err.h, [HAS_COMERR=yes], [HAS_COMERR=no]) + if test "$HAS_COMERR" = no; then + AC_CHECK_HEADER(et/com_err.h, [HAS_COMERR=yes; CPPFLAGS="$CPPFLAGS -I/usr/include/et"], []) + fi + fi + if test "$HAS_COMERR" = no; then + HAS_KRB5=no + fi + if test "$HAS_KRB5" = no; then + CPPFLAGS="$cpp_bak" + LDFLAGS="$ld_bak" + fi +fi +KRB5_LDADD= +if test -n "$with_krb5" -a "$with_krb5" != no -a "$HAS_KRB5" != yes; then + AC_MSG_ERROR([*** cannot find Kerberos 5 on this system - try passing --with-krb5=/path/to/kerberos]) +fi +if test "$with_krb5" != no -a "$HAS_KRB5" = yes; then + AC_DEFINE(HAVE_KRB5) + KRB5_LDADD=-lkrb5 + krb_msg=Yes +fi +AC_SUBST([KRB5_LDADD]) + +AC_CHECK_FUNC(vswprintf, , AC_MSG_ERROR([*** must have vswprintf])) +AH_TEMPLATE(HAVE_WCSCASECMP, [define if your system implements wcscasecmp]) +AC_CHECK_FUNC(wcscasecmp, [ AC_DEFINE(HAVE_WCSCASECMP) ]) + +AH_TEMPLATE(HAVE_LINUX_SOCKIOS_H, [define if you have linux/sockios.h on your system]) +AC_CHECK_HEADER([linux/sockios.h], [ AC_DEFINE(HAVE_LINUX_SOCKIOS_H) ]) + +AH_TEMPLATE(HAVE_IPV6, [define if your system supports IPv6 and you wish to compile with support for it]) +AC_CHECK_MEMBER(struct sockaddr_in6.sin6_family, [ AC_DEFINE(HAVE_IPV6) ], , [#include ]) + +AH_TEMPLATE(HAVE_RESOLVER, [define if your system supports the res_* functions to fetch DNS RRs]) +AC_CHECK_LIB(resolv, res_query, [ AC_DEFINE(HAVE_RESOLVER) + LDFLAGS="$LDFLAGS -lresolv" ]) + +AC_HEADER_STDC +AC_HEADER_DIRENT +AC_HEADER_SYS_WAIT + +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_HEADER_TIME +AC_TYPE_SIGNAL + +CPPFLAGS="-I\$(top_srcdir)/include $CPPFLAGS" + +AC_SUBST([clients extlibs]) +AC_OUTPUT([ +Makefile +daemon/Makefile +lib/Makefile +lib/guile/Makefile +lib/guile/dolcon/Makefile +clients/Makefile +clients/gtk2/Makefile +clients/gnome-trans-applet/Makefile +include/Makefile +po/Makefile.in +config/Makefile +autopackage/dolcon.apspec +]) + +echo +echo "Dolda Connect has been configured with the following settings:" +echo +echo " Kerberos 5 support: $krb_msg" +echo " GTK2 user interface: $gtk2ui_msg" +echo " GTK2 progress bars: $gtk2pbar_msg" +echo " Guile extension library: $guile_msg" +echo " GNOME transfer applet: $gnometrapplet_msg" +echo + +if tput bold >/dev/null 2>&1 && tty <&2 >/dev/null 2>&1; then + hastput=y +fi +if test "$HAS_GTK2" = no -a "$with_gtk2" != no -a "$enable_gtk2ui" != no; then + if test "$hastput" = y; then + tput bold + tput setf 4 2>/dev/null + fi + echo -n " Warning: " >&2 + if test "$hastput" = y; then + tput sgr0 + fi + echo "Could not find a GTK2 development installation on this system." >&2 + echo " That means you won't get a UI." >&2 + echo " Make absolutely sure this is what you want!" >&2 + if test "$hastput" = y; then + tput bel + fi + sleep 1 +fi +if test "$experimental" = yes; then + if test "$hastput" = y; then + tput bold + tput setf 4 2>/dev/null + fi + echo -n " Warning: " >&2 + if test "$hastput" = y; then + tput sgr0 + fi + echo "You have enabled one or more experimental features!" >&2 + echo " Please don't complain that it doesn't work, unless" >&2 + echo " you have something constructive to add about the situation." >&2 + if test "$hastput" = y; then + tput bel + fi + sleep 1 +fi diff --git a/daemon/CVS/Entries b/daemon/CVS/Entries new file mode 100644 index 0000000..186e6b2 --- /dev/null +++ b/daemon/CVS/Entries @@ -0,0 +1,31 @@ +/Makefile.am/1.8/Fri Oct 14 14:34:30 2005// +/auth-krb5.c/1.10/Thu Oct 13 03:07:59 2005// +/auth-pam.c/1.1.1.1/Tue May 11 15:47:16 2004// +/auth.c/1.2/Fri Aug 6 18:51:28 2004// +/auth.h/1.2/Fri Aug 6 18:51:45 2004// +/client.c/1.7/Mon Oct 3 03:13:14 2005// +/client.h/1.5/Mon Oct 3 03:13:19 2005// +/conf.c/1.3/Fri Aug 13 11:37:34 2004// +/conf.h/1.2/Tue Jul 27 23:49:40 2004// +/emacs-local/1.1.1.1/Tue May 11 15:47:05 2004// +/filenet.c/1.9/Wed Jul 13 00:08:53 2005// +/filenet.h/1.7/Tue Oct 26 02:18:27 2004// +/fnet-dc.c/1.49/Thu Oct 13 15:39:39 2005// +/log.c/1.2/Thu Sep 30 01:19:08 2004// +/log.h/1.2/Tue Aug 9 11:06:22 2005// +/main.c/1.10/Wed Oct 12 20:33:22 2005// +/module.h/1.1.1.1/Tue May 11 15:47:31 2004// +/net.c/1.12/Wed Aug 10 12:38:38 2005// +/net.h/1.6/Wed Sep 22 02:25:29 2004// +/search.c/1.9/Thu Oct 13 03:08:31 2005// +/search.h/1.4/Tue Oct 26 02:22:10 2004// +/sysevents.h/1.3/Thu Sep 30 01:20:23 2004// +/tiger.c/1.3/Wed Sep 28 19:57:08 2005// +/tiger.h/1.3/Wed Sep 28 19:57:16 2005// +/transfer.c/1.18/Thu Oct 13 03:08:42 2005// +/transfer.h/1.9/Sun Oct 9 02:50:12 2005// +/ui.c/1.19/Fri Oct 14 23:21:24 2005// +/uiretref/1.6/Tue Aug 3 16:56:12 2004// +/utils.c/1.15/Fri Oct 14 23:20:43 2005// +/utils.h/1.15/Fri Oct 14 23:20:49 2005// +D diff --git a/daemon/CVS/Repository b/daemon/CVS/Repository new file mode 100644 index 0000000..b88236b --- /dev/null +++ b/daemon/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/daemon diff --git a/daemon/CVS/Root b/daemon/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/daemon/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/daemon/Makefile.am b/daemon/Makefile.am new file mode 100644 index 0000000..342ad7a --- /dev/null +++ b/daemon/Makefile.am @@ -0,0 +1,30 @@ +bin_PROGRAMS=doldacond +doldacond_SOURCES= main.c \ + search.c \ + search.h \ + transfer.c \ + transfer.h \ + sysevents.h \ + module.h \ + filenet.c \ + filenet.h \ + fnet-dc.c \ + auth.c \ + auth.h \ + auth-pam.c \ + auth-krb5.c \ + client.c \ + client.h \ + net.c \ + net.h \ + utils.c \ + utils.h \ + log.c \ + log.h \ + ui.c \ + conf.c \ + conf.h \ + tiger.c \ + tiger.h +EXTRA_DIST=emacs-local +doldacond_LDADD=@KRB5_LDADD@ -lbz2 -lz diff --git a/daemon/auth-krb5.c b/daemon/auth-krb5.c new file mode 100644 index 0000000..62827c6 --- /dev/null +++ b/daemon/auth-krb5.c @@ -0,0 +1,590 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "auth.h" +#include "utils.h" +#include "conf.h" +#include "log.h" +#include "module.h" +#include "sysevents.h" + +#ifdef HAVE_KRB5 + +#include +#include + +struct krb5data +{ + int state; + krb5_auth_context context; + krb5_ticket *ticket; + krb5_creds *creds; + krb5_ccache ccache; + int renew; + struct timer *renewtimer; + char *username, *cname; +}; + +static void setrenew(struct krb5data *data); + +static krb5_context k5context; +static krb5_principal myprinc; +static krb5_keytab keytab; + +static void releasekrb5(struct krb5data *data) +{ + if(data->renewtimer != NULL) + canceltimer(data->renewtimer); + if(data->context != NULL) + krb5_auth_con_free(k5context, data->context); + if(data->ticket != NULL) + krb5_free_ticket(k5context, data->ticket); + if(data->creds != NULL) + krb5_free_creds(k5context, data->creds); + if(data->username != NULL) + free(data->username); + if(data->cname != NULL) + free(data->cname); + free(data); +} + +static void release(struct authhandle *auth) +{ + releasekrb5((struct krb5data *)auth->mechdata); +} + +static struct krb5data *newkrb5data(void) +{ + struct krb5data *new; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + return(new); +} + +static int inithandle(struct authhandle *auth, char *username) +{ + int ret; + struct krb5data *data; + + data = newkrb5data(); + if((ret = krb5_auth_con_init(k5context, &data->context)) != 0) + { + flog(LOG_ERR, "could initialize Kerberos auth context: %s", error_message(ret)); + releasekrb5(data); + return(1); + } + krb5_auth_con_setflags(k5context, data->context, KRB5_AUTH_CONTEXT_DO_SEQUENCE); + data->username = sstrdup(username); + data->state = 0; + auth->mechdata = data; + return(0); +} + +/* Copied from MIT Kerberos 5 1.3.3*/ +static krb5_boolean my_krb5_kuserok(krb5_context context, krb5_principal principal, const char *luser, const char *loginfile, int authbydef) +{ + struct stat sbuf; + struct passwd *pwd; + char pbuf[MAXPATHLEN]; + krb5_boolean isok = FALSE; + FILE *fp; + char kuser[65]; + char *princname; + char linebuf[BUFSIZ]; + char *newline; + int gobble; + + /* no account => no access */ + if ((pwd = getpwnam(luser)) == NULL) { + return(FALSE); + } + (void) strncpy(pbuf, pwd->pw_dir, sizeof(pbuf) - 1); + pbuf[sizeof(pbuf) - 1] = '\0'; + (void) strncat(pbuf, loginfile, sizeof(pbuf) - 1 - strlen(pbuf)); + + if (access(pbuf, F_OK)) { /* not accessible */ + /* + * if he's trying to log in as himself, and there is no .k5login file, + * let him. To find out, call + * krb5_aname_to_localname to convert the principal to a name + * which we can string compare. + */ + if (authbydef) { + if (!(krb5_aname_to_localname(context, principal, + sizeof(kuser), kuser)) + && (strcmp(kuser, luser) == 0)) { + return(TRUE); + } + } else { + return(FALSE); + } + } + if (krb5_unparse_name(context, principal, &princname)) + return(FALSE); /* no hope of matching */ + + /* open ~/.k5login */ + if ((fp = fopen(pbuf, "r")) == NULL) { + free(princname); + return(FALSE); + } + /* + * For security reasons, the .k5login file must be owned either by + * the user himself, or by root. Otherwise, don't grant access. + */ + if (fstat(fileno(fp), &sbuf)) { + fclose(fp); + free(princname); + return(FALSE); + } + if ((sbuf.st_uid != pwd->pw_uid) && sbuf.st_uid) { + fclose(fp); + free(princname); + return(FALSE); + } + + /* check each line */ + while (!isok && (fgets(linebuf, BUFSIZ, fp) != NULL)) { + /* null-terminate the input string */ + linebuf[BUFSIZ-1] = '\0'; + newline = NULL; + /* nuke the newline if it exists */ + if ((newline = strchr(linebuf, '\n'))) + *newline = '\0'; + if (!strcmp(linebuf, princname)) { + isok = TRUE; + continue; + } + /* clean up the rest of the line if necessary */ + if (!newline) + while (((gobble = getc(fp)) != EOF) && gobble != '\n'); + } + free(princname); + fclose(fp); + return(isok); +} + +static void renewcreds(int cancelled, struct krb5data *data) +{ + int ret; + char ccnambuf[50]; + krb5_ccache tmpcc; + krb5_creds newcreds; + static int ccserial = 0; + + data->renewtimer = NULL; + if(cancelled) + return; + memset(&newcreds, 0, sizeof(newcreds)); + snprintf(ccnambuf, sizeof(ccnambuf), "MEMORY:%i", ccserial++); + if((ret = krb5_cc_resolve(k5context, ccnambuf, &tmpcc)) != 0) + { + flog(LOG_ERR, "could not resolve a temporary ccache `%s': %s", ccnambuf, error_message(ret)); + data->renew = 0; + return; + } + if((ret = krb5_cc_initialize(k5context, tmpcc, data->ticket->enc_part2->client)) != 0) + { + flog(LOG_ERR, "could not initialize temporary ccache: %s", error_message(ret)); + krb5_cc_destroy(k5context, tmpcc); + data->renew = 0; + return; + } + if((ret = krb5_cc_store_cred(k5context, tmpcc, data->creds)) != 0) + { + flog(LOG_ERR, "could not store creds into temporary ccache: %s", error_message(ret)); + krb5_cc_destroy(k5context, tmpcc); + data->renew = 0; + return; + } + if((ret = krb5_get_renewed_creds(k5context, &newcreds, data->ticket->enc_part2->client, tmpcc, NULL)) != 0) + { + flog(LOG_ERR, "could not get renewed tickets for %s: %s", data->username, error_message(ret)); + krb5_cc_destroy(k5context, tmpcc); + data->renew = 0; + return; + } + krb5_free_creds(k5context, data->creds); + data->creds = NULL; + if((ret = krb5_copy_creds(k5context, &newcreds, &data->creds)) != 0) + { + flog(LOG_ERR, "could not copy renewed creds: %s", error_message(ret)); + krb5_cc_destroy(k5context, tmpcc); + data->renew = 0; + return; + } + krb5_free_cred_contents(k5context, &newcreds); + krb5_cc_destroy(k5context, tmpcc); + flog(LOG_ERR, "successfully renewed krb5 creds for %s", data->username); + setrenew(data); +} + +static void setrenew(struct krb5data *data) +{ + krb5_ticket_times times; + time_t now, good; + + times = data->creds->times; + if(!times.starttime) + times.starttime = times.authtime; + now = time(NULL); + if(times.endtime < now) + { + flog(LOG_DEBUG, "tickets already expired, cannot renew"); + data->renew = 0; + return; + } + good = times.starttime + (((times.endtime - times.starttime) * 9) / 10); + data->renewtimer = timercallback(good, (void (*)(int, void *))renewcreds, data); +} + +static int krbauth(struct authhandle *auth, char *passdata) +{ + int ret; + struct krb5data *data; + char *msg; + size_t msglen; + int authorized; + krb5_data k5d; + krb5_flags apopt; + krb5_creds **fwdcreds; + + data = auth->mechdata; + if(passdata == NULL) + { + auth->prompt = AUTH_PR_AUTO; + if(auth->text != NULL) + free(auth->text); + auth->text = swcsdup(L"Send hex-encoded krb5 data"); + data->state = 1; + return(AUTH_PASS); + } else { + if((msg = hexdecode(passdata, &msglen)) == NULL) + { + if(auth->text != NULL) + free(auth->text); + auth->text = swcsdup(L"Invalid hex encoding"); + return(AUTH_DENIED); + } + switch(data->state) + { + case 1: + k5d.length = msglen; + k5d.data = msg; + if((ret = krb5_rd_req(k5context, &data->context, &k5d, myprinc, keytab, &apopt, &data->ticket)) != 0) + { + flog(LOG_INFO, "kerberos authentication failed for %s: %s", data->username, error_message(ret)); + if(auth->text != NULL) + free(auth->text); + auth->text = icmbstowcs((char *)error_message(ret), NULL); + return(AUTH_DENIED); + } + free(msg); + if(apopt & AP_OPTS_MUTUAL_REQUIRED) + { + if((ret = krb5_mk_rep(k5context, data->context, &k5d)) != 0) + { + flog(LOG_WARNING, "krb5_mk_rep returned an error: %s", error_message(ret)); + return(AUTH_ERR); + } + msg = hexencode(k5d.data, k5d.length); + if(auth->text != NULL) + free(auth->text); + auth->text = icmbstowcs(msg, "us-ascii"); + free(msg); + free(k5d.data); + } else { + if(auth->text != NULL) + free(auth->text); + auth->text = swcsdup(L""); + } + data->state = 2; + return(AUTH_PASS); + case 2: + ret = atoi(msg); + free(msg); + if(ret == 1) + { + /* That is, the client has accepted us as a valid + * server. Now check if the client is authorized. */ + if((ret = krb5_unparse_name(k5context, data->ticket->enc_part2->client, &data->cname)) != 0) + { + flog(LOG_ERR, "krb_unparse_name returned an error: %s", error_message(ret)); + return(AUTH_ERR); + } + authorized = 0; + if(!authorized && my_krb5_kuserok(k5context, data->ticket->enc_part2->client, data->username, "/.k5login", 1)) + authorized = 1; + /* Allow a seperate ACL for DC principals */ + if(!authorized && my_krb5_kuserok(k5context, data->ticket->enc_part2->client, data->username, "/.dc-k5login", 0)) + authorized = 1; + if(authorized) + { + flog(LOG_INFO, "krb5 principal %s successfully authorized as %s", data->cname, data->username); + return(AUTH_SUCCESS); + } else { + flog(LOG_INFO, "krb5 principal %s not authorized as %s", data->cname, data->username); + } + } + if(ret == 2) + { + if(auth->text != NULL) + free(auth->text); + auth->text = swcsdup(L""); + data->state = 3; + return(AUTH_PASS); + } + return(AUTH_DENIED); + case 3: + k5d.length = msglen; + k5d.data = msg; + if((ret = krb5_rd_cred(k5context, data->context, &k5d, &fwdcreds, NULL)) != 0) + { + flog(LOG_ERR, "krb5_rd_cred returned an error: %s", error_message(ret)); + return(AUTH_ERR); + } + if(*fwdcreds == NULL) + { + flog(LOG_ERR, "forwarded credentials array was empty (from %s)", data->username); + krb5_free_tgt_creds(k5context, fwdcreds); + return(AUTH_ERR); + } + flog(LOG_INFO, "received forwarded credentials for %s", data->username); + /* Copy only the first credential. (Change this if it becomes a problem) */ + ret = krb5_copy_creds(k5context, *fwdcreds, &data->creds); + krb5_free_tgt_creds(k5context, fwdcreds); + if(ret != 0) + { + flog(LOG_ERR, "could not copy forwarded credentials: %s", error_message(ret)); + return(AUTH_ERR); + } + if(confgetint("auth-krb5", "renewcreds")) + { + data->renew = 1; + setrenew(data); + } + if(auth->text != NULL) + free(auth->text); + auth->text = swcsdup(L""); + data->state = 2; + return(AUTH_PASS); + default: + free(msg); + flog(LOG_ERR, "BUG? Invalid state encountered in krbauth: %i", data->state); + return(AUTH_ERR); + } + } +} + +static int opensess(struct authhandle *auth) +{ + int ret; + struct krb5data *data; + char *buf, *buf2; + int fd; + struct passwd *pwent; + + data = auth->mechdata; + if(data->creds != NULL) + { + if((pwent = getpwnam(data->username)) == NULL) + { + flog(LOG_ERR, "could not get passwd entry for forwarded tickets (user %s): %s", data->username, strerror(errno)); + return(AUTH_ERR); + } + buf = sprintf2("/tmp/krb5cc_dc_%i_XXXXXX", pwent->pw_uid); + if((fd = mkstemp(buf)) < 0) + { + free(buf); + flog(LOG_ERR, "could not create temporary file for ccache: %s", strerror(errno)); + return(AUTH_ERR); + } + close(fd); + buf2 = sprintf2("FILE:%s", buf); + if((ret = krb5_cc_resolve(k5context, buf2, &data->ccache)) != 0) + { + free(buf); + free(buf2); + flog(LOG_ERR, "could not resolve ccache name \"%s\": %s", buf2, error_message(ret)); + return(AUTH_ERR); + } + setenv("KRB5CCNAME", buf2, 1); + free(buf2); + if((ret = krb5_cc_initialize(k5context, data->ccache, data->ticket->enc_part2->client)) != 0) + { + free(buf); + flog(LOG_ERR, "could not initialize ccache: %s", error_message(ret)); + return(AUTH_ERR); + } + if((ret = krb5_cc_store_cred(k5context, data->ccache, data->creds)) != 0) + { + free(buf); + flog(LOG_ERR, "could not store forwarded TGT into ccache: %s", error_message(ret)); + return(AUTH_ERR); + } + if(chown(buf, pwent->pw_uid, pwent->pw_gid)) + { + free(buf); + flog(LOG_ERR, "could not chown new ccache to %i:%i: %s", pwent->pw_uid, pwent->pw_gid, strerror(errno)); + return(AUTH_ERR); + } + free(buf); + } + return(AUTH_SUCCESS); +} + +static int closesess(struct authhandle *auth) +{ + struct krb5data *data; + + data = auth->mechdata; + if(data->ccache != NULL) + { + krb5_cc_destroy(k5context, data->ccache); + data->ccache = NULL; + } + return(AUTH_SUCCESS); +} + +struct authmech authmech_krb5 = +{ + .inithandle = inithandle, + .release = release, + .authenticate = krbauth, + .opensess = opensess, + .closesess = closesess, + .name = L"krb5", + .enabled = 1 +}; + +static int init(int hup) +{ + int ret; + char *buf; + krb5_principal newprinc; + + if(!hup) + { + regmech(&authmech_krb5); + if((ret = krb5_init_context(&k5context))) + { + flog(LOG_CRIT, "could not initialize Kerberos context: %s", error_message(ret)); + return(1); + } + if((buf = icwcstombs(confgetstr("auth-krb5", "service"), NULL)) == NULL) + { + flog(LOG_CRIT, "could not convert service name (%ls) into local charset: %s", confgetstr("auth-krb5", "service"), strerror(errno)); + return(1); + } else { + if((ret = krb5_sname_to_principal(k5context, NULL, buf, KRB5_NT_SRV_HST, &myprinc)) != 0) + { + flog(LOG_CRIT, "could not get principal for service %s: %s", buf, error_message(ret)); + free(buf); + return(1); + } + free(buf); + } + if((buf = icwcstombs(confgetstr("auth-krb5", "keytab"), NULL)) == NULL) + { + flog(LOG_ERR, "could not convert keytab name (%ls) into local charset: %s, using default keytab instead", confgetstr("auth-krb5", "keytab"), strerror(errno)); + keytab = NULL; + } else { + if((ret = krb5_kt_resolve(k5context, buf, &keytab)) != 0) + { + flog(LOG_ERR, "could not open keytab %s: %s, using default keytab instead", buf, error_message(ret)); + keytab = NULL; + } + free(buf); + } + } + if(hup) + { + if((buf = icwcstombs(confgetstr("auth-krb5", "service"), NULL)) == NULL) + { + flog(LOG_CRIT, "could not convert service name (%ls) into local charset: %s, not updating principal", confgetstr("auth-krb5", "service"), strerror(errno)); + } else { + if((ret = krb5_sname_to_principal(k5context, NULL, buf, KRB5_NT_SRV_HST, &newprinc)) != 0) + { + flog(LOG_CRIT, "could not get principal for service %s: %s, not updating principal", buf, error_message(ret)); + } else { + krb5_free_principal(k5context, myprinc); + myprinc = newprinc; + } + free(buf); + } + if(keytab != NULL) + krb5_kt_close(k5context, keytab); + if((buf = icwcstombs(confgetstr("auth-krb5", "keytab"), NULL)) == NULL) + { + flog(LOG_ERR, "could not convert keytab name (%ls) into local charset: %s, using default keytab instead", confgetstr("auth-krb5", "keytab"), strerror(errno)); + keytab = NULL; + } else { + if((ret = krb5_kt_resolve(k5context, buf, &keytab)) != 0) + { + flog(LOG_ERR, "could not open keytab %s: %s, using default keytab instead", buf, error_message(ret)); + keytab = NULL; + } + free(buf); + } + } + return(0); +} + +static void terminate(void) +{ + if(keytab != NULL) + krb5_kt_close(k5context, keytab); + krb5_free_principal(k5context, myprinc); + krb5_free_context(k5context); +} + +static struct configvar myvars[] = +{ + {CONF_VAR_STRING, "service", {.str = L"doldacond"}}, + {CONF_VAR_STRING, "keytab", {.str = L""}}, + {CONF_VAR_BOOL, "renewcreds", {.num = 1}}, + {CONF_VAR_END} +}; + +static struct module me = +{ + .conf = + { + .vars = myvars + }, + .init = init, + .terminate = terminate, + .name = "auth-krb5" +}; + +MODULE(me); + +#endif /* HAVE_KRB5 */ diff --git a/daemon/auth-pam.c b/daemon/auth-pam.c new file mode 100644 index 0000000..48dbcbd --- /dev/null +++ b/daemon/auth-pam.c @@ -0,0 +1,332 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +*/ +/* + * I have decided that I don't like PAM. Maybe I'm inexperienced, so + * please correct me if I'm wrong, but is it not so that + * pam_authenticate blocks until the user has fully authenticated + * herself? That isn't very good in a program that wants to do other + * things at the same time. In my mind, pam_authenticate should return + * with a conversation struct every time it wants data. + * + * My solution here, for now, is to use the ucontext context switching + * functions to get back and forth from the conversation + * function. Ugly? Yes indeed, it most certainly is, but what am I to + * do, then? If there actually is a good way to do this that is built + * into PAM, _please_, do mail me about it. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "auth.h" +#include "utils.h" +#include "conf.h" +#include "log.h" + +struct pamdata +{ + pam_handle_t *pamh; + volatile int pamret; + ucontext_t mainctxt, pamctxt; + void *pamstack; + volatile int validctxt; + volatile int convdone, converr; + volatile char *passdata; +}; + +static int pamconv(int nmsg, const struct pam_message **msg, struct pam_response **resp, struct authhandle *auth) +{ + int i; + struct pamdata *data; + + data = auth->mechdata; + *resp = smalloc(sizeof(**resp) * nmsg); + for(i = 0; i < nmsg; i++) + { + switch(msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_OFF: + auth->prompt = AUTH_PR_NOECHO; + break; + case PAM_PROMPT_ECHO_ON: + auth->prompt = AUTH_PR_ECHO; + break; + case PAM_ERROR_MSG: + auth->prompt = AUTH_PR_ERROR; + break; + case PAM_TEXT_INFO: + auth->prompt = AUTH_PR_INFO; + break; + } + if(auth->text != NULL) + free(auth->text); + if((auth->text = icmbstowcs((char *)msg[i]->msg, NULL)) == NULL) + { + flog(LOG_ERR, "could not convert PAM error %s into wcs: %s", msg[i]->msg, strerror(errno)); + free(*resp); + *resp = NULL; + return(PAM_CONV_ERR); + } + if(swapcontext(&data->pamctxt, &data->mainctxt)) + { + flog(LOG_CRIT, "could not swap context in PAM conversation: %s", strerror(errno)); + free(*resp); + *resp = NULL; + return(PAM_CONV_ERR); + } + if(data->converr) + { + for(; i < nmsg; i++) + { + (*resp)[i].resp = sstrdup(""); + (*resp)[i].resp_retcode = PAM_SUCCESS; + } + return(PAM_CONV_ERR); + } + switch(msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + (*resp)[i].resp = sstrdup((char *)data->passdata); + memset((void *)data->passdata, 0, strlen((char *)data->passdata)); + (*resp)[i].resp_retcode = PAM_SUCCESS; + break; + } + } + return(PAM_SUCCESS); +} + +static void releasepam(struct pamdata *data) +{ + if(data->pamh != NULL) + { + if(data->validctxt) + { + data->converr = 1; + if(swapcontext(&data->mainctxt, &data->pamctxt)) + { + flog(LOG_CRIT, "could not switch back to PAM context while releasing: %s", strerror(errno)); + return; + } + } + pam_end(data->pamh, data->pamret); + } + if(data->pamstack != NULL) + free(data->pamstack); + free(data); +} + +static void release(struct authhandle *auth) +{ + releasepam((struct pamdata *)auth->mechdata); +} + +static struct pamdata *newpamdata(void) +{ + struct pamdata *new; + + new = smalloc(sizeof(*new)); + new->pamh = NULL; + new->pamret = PAM_SUCCESS; + new->pamstack = NULL; + new->validctxt = 0; + new->converr = 0; + return(new); +} + +static int inithandle(struct authhandle *auth, char *username) +{ + char *buf; + struct pamdata *data; + struct pam_conv conv; + + data = newpamdata(); + conv.conv = (int (*)(int, const struct pam_message **, struct pam_response **, void *))pamconv; + conv.appdata_ptr = auth; + if((buf = icwcstombs(confgetstr("auth", "pamserv"), NULL)) == NULL) + { + flog(LOG_ERR, "could not initialize pam since auth.pamserv cannot be translated into the current locale: %s", strerror(errno)); + releasepam(data); + return(1); + } + if((data->pamret = pam_start(buf, username, &conv, &data->pamh)) != PAM_SUCCESS) + { + flog(LOG_CRIT, "could not pam_start: %s", pam_strerror(NULL, data->pamret)); + releasepam(data); + free(buf); + errno = ENOTSUP; /* XXX */ + return(1); + } + free(buf); + auth->mechdata = data; + return(0); +} + +static void pamauththread(struct authhandle *auth) +{ + struct pamdata *data; + + data = (struct pamdata *)auth->mechdata; + data->validctxt = 1; + data->pamret = pam_authenticate(data->pamh, 0); + data->validctxt = 0; +} + +static int pamauth(struct authhandle *auth, char *passdata) +{ + struct pamdata *data; + + data = auth->mechdata; + if(!data->validctxt) + { + if(getcontext(&data->pamctxt)) + { + flog(LOG_CRIT, "could not get context: %s", strerror(errno)); + return(AUTH_ERR); + } + data->pamctxt.uc_link = &data->mainctxt; + if(data->pamstack == NULL) + data->pamstack = smalloc(65536); + data->pamctxt.uc_stack.ss_sp = data->pamstack; + data->pamctxt.uc_stack.ss_size = 65536; + makecontext(&data->pamctxt, (void (*)(void))pamauththread, 1, auth); + if(swapcontext(&data->mainctxt, &data->pamctxt)) + { + flog(LOG_CRIT, "Could not switch to PAM context: %s", strerror(errno)); + return(AUTH_ERR); + } + if(!data->validctxt) + { + if(data->pamret == PAM_AUTHINFO_UNAVAIL) + return(AUTH_ERR); + else if(data->pamret == PAM_SUCCESS) + return(AUTH_SUCCESS); + else + return(AUTH_DENIED); + } + return(AUTH_PASS); + } else { + data->passdata = passdata; + if(swapcontext(&data->mainctxt, &data->pamctxt)) + { + flog(LOG_CRIT, "could not switch back to PAM context: %s", strerror(errno)); + return(AUTH_ERR); + } + if(!data->validctxt) + { + if(data->pamret == PAM_AUTHINFO_UNAVAIL) + return(AUTH_ERR); + else if(data->pamret == PAM_SUCCESS) + return(AUTH_SUCCESS); + else + return(AUTH_DENIED); + } + return(AUTH_PASS); + } +} + +static int renewcred(struct authhandle *auth) +{ + struct pamdata *data; + + data = auth->mechdata; + if(data->pamh == NULL) + return(AUTH_SUCCESS); + data->pamret = pam_setcred(data->pamh, PAM_REFRESH_CRED); + if(data->pamret != PAM_SUCCESS) + { + flog(LOG_INFO, "could not refresh credentials: %s", pam_strerror(data->pamh, data->pamret)); + return(AUTH_ERR); + } + return(AUTH_SUCCESS); +} + +static int opensess(struct authhandle *auth) +{ + struct pamdata *data; + char **envp; + + data = auth->mechdata; + if(data->pamh == NULL) + { + flog(LOG_ERR, "bug: in auth-pam.c:opensess: called with NULL pamh"); + return(AUTH_ERR); + } + data->pamret = pam_setcred(data->pamh, PAM_ESTABLISH_CRED); + if(data->pamret != PAM_SUCCESS) + { + flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret)); + return(AUTH_ERR); + } + data->pamret = pam_open_session(data->pamh, 0); + if(data->pamret != PAM_SUCCESS) + { + flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret)); + return(AUTH_ERR); + } + for(envp = pam_getenvlist(data->pamh); *envp; envp++) + putenv(*envp); + return(AUTH_SUCCESS); +} + +static int closesess(struct authhandle *auth) +{ + int rc; + struct pamdata *data; + + data = auth->mechdata; + if(data->pamh == NULL) + { + flog(LOG_ERR, "bug: in auth-pam.c:closesess: called with NULL pamh"); + return(AUTH_ERR); + } + rc = AUTH_SUCCESS; + data->pamret = pam_close_session(data->pamh, 0); + if(data->pamret != PAM_SUCCESS) + { + flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret)); + rc = AUTH_ERR; + } + data->pamret = pam_setcred(data->pamh, PAM_DELETE_CRED); + if(data->pamret != PAM_SUCCESS) + { + flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret)); + rc = AUTH_ERR; + } + return(rc); +} + +struct authmech authmech_pam = +{ + .inithandle = inithandle, + .release = release, + .authenticate = pamauth, + .renewcred = renewcred, + .opensess = opensess, + .closesess = closesess, + .name = L"pam", + .enabled = 1 +}; diff --git a/daemon/auth.c b/daemon/auth.c new file mode 100644 index 0000000..0d34fbb --- /dev/null +++ b/daemon/auth.c @@ -0,0 +1,183 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "auth.h" +#include "utils.h" +#include "module.h" +#include "conf.h" + +struct authmech *mechs = NULL; + +static int authless_inithandle(struct authhandle *auth, char *username) +{ + return(0); +} + +static void authless_release(struct authhandle *auth) +{ +} + +static int authless_authenticate(struct authhandle *auth, char *data) +{ + return(AUTH_SUCCESS); +} + +static int authless_succeed_1param(struct authhandle *auth) +{ + return(AUTH_SUCCESS); +} + +static struct authmech authless = +{ + .name = L"authless", + .inithandle = authless_inithandle, + .release = authless_release, + .authenticate = authless_authenticate, + .renewcred = authless_succeed_1param, + .opensess = authless_succeed_1param, + .closesess = authless_succeed_1param +}; + +static struct authhandle *newhandle(void) +{ + struct authhandle *auth; + + auth = smalloc(sizeof(*auth)); + auth->refcount = 1; + auth->mech = NULL; + auth->text = NULL; + auth->mechdata = NULL; + return(auth); +} + +void authgethandle(struct authhandle *auth) +{ + auth->refcount++; +} + +void authputhandle(struct authhandle *auth) +{ + if(--auth->refcount) + return; + if(auth->text != NULL) + free(auth->text); + if(auth->mechdata != NULL) + auth->mech->release(auth); + free(auth); +} + +struct authhandle *initauth(wchar_t *mechname, char *username) +{ + struct authmech *mech; + struct authhandle *auth; + + for(mech = mechs; mech != NULL; mech = mech->next) + { + if(mech->enabled && !wcscmp(mechname, mech->name)) + break; + } + if(mech == NULL) + { + errno = ENOENT; + return(NULL); + } + auth = newhandle(); + auth->mech = mech; + if(mech->inithandle(auth, username)) + { + authputhandle(auth); + return(NULL); + } + return(auth); +} + +int authenticate(struct authhandle *handle, char *data) +{ + if(handle->mech == NULL) + return(AUTH_ERR); + return(handle->mech->authenticate(handle, data)); +} + +int authrenewcred(struct authhandle *handle) +{ + if((handle->mech == NULL) || (handle->mech->renewcred == NULL)) + return(AUTH_SUCCESS); + return(handle->mech->renewcred(handle)); +} + +int authopensess(struct authhandle *handle) +{ + if((handle->mech == NULL) || (handle->mech->opensess == NULL)) + return(AUTH_SUCCESS); + return(handle->mech->opensess(handle)); +} + +int authclosesess(struct authhandle *handle) +{ + if((handle->mech == NULL) || (handle->mech->closesess == NULL)) + return(AUTH_SUCCESS); + return(handle->mech->closesess(handle)); +} + +void regmech(struct authmech *mech) +{ + mech->next = mechs; + mechs = mech; +} + +static void preinit(int hup) +{ + extern struct authmech authmech_pam; + + if(hup) + return; + regmech(&authless); + regmech(&authmech_pam); +} + +static int init(int hup) +{ + authless.enabled = confgetint("auth", "authless"); + return(0); +} + +static struct configvar myvars[] = +{ + {CONF_VAR_STRING, "pamserv", {.str = L"doldacond"}}, + {CONF_VAR_BOOL, "authless", {.num = 1}}, + {CONF_VAR_END} +}; + +static struct module me = +{ + .name = "auth", + .conf = + { + .vars = myvars + }, + .preinit = preinit, + .init = init +}; + +MODULE(me) diff --git a/daemon/auth.h b/daemon/auth.h new file mode 100644 index 0000000..0763841 --- /dev/null +++ b/daemon/auth.h @@ -0,0 +1,70 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _AUTH_H +#define _AUTH_H + +#include + +#define AUTH_SUCCESS 0 /* Authentication successful and done */ +#define AUTH_DENIED 1 /* Ultimately failed - reason in handle->text */ +#define AUTH_PASS 2 /* Pass data - look in handle->prompt */ +#define AUTH_ERR 3 /* An error occurred that */ + +#define AUTH_PR_AUTO 0 +#define AUTH_PR_NOECHO 1 +#define AUTH_PR_ECHO 2 +#define AUTH_PR_INFO 3 +#define AUTH_PR_ERROR 4 + +struct authhandle; + +struct authmech +{ + struct authmech *next; + int enabled; + wchar_t *name; + int (*inithandle)(struct authhandle *handle, char *username); + void (*release)(struct authhandle *handle); + int (*authenticate)(struct authhandle *handle, char *data); + int (*renewcred)(struct authhandle *handle); + int (*opensess)(struct authhandle *handle); + int (*closesess)(struct authhandle *handle); +}; + +struct authhandle +{ + int refcount; + struct authmech *mech; + int prompt; + wchar_t *text; + void *mechdata; +}; + +int authenticate(struct authhandle *handle, char *data); +struct authhandle *initauth(wchar_t *mechname, char *username); +void authgethandle(struct authhandle *auth); +void authputhandle(struct authhandle *auth); +int authrenewcred(struct authhandle *handle); +int authopensess(struct authhandle *handle); +int authclosesess(struct authhandle *handle); +void regmech(struct authmech *mech); + +extern struct authmech *mechs; + +#endif diff --git a/daemon/client.c b/daemon/client.c new file mode 100644 index 0000000..e7fa0e7 --- /dev/null +++ b/daemon/client.c @@ -0,0 +1,998 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "client.h" +#include "conf.h" +#include "log.h" +#include "utils.h" +#include "module.h" +#include "tiger.h" +#include "net.h" +#include "sysevents.h" + +struct scanstate +{ + struct scanstate *next; + struct sharecache *node; + DIR *dd; +}; + +struct scanqueue +{ + struct scanqueue *next; + struct scanstate *state; +}; + +static int conf_share(int argc, wchar_t **argv); +static void freecache(struct sharecache *node); +static void checkhashes(void); + +static struct configvar myvars[] = +{ + {CONF_VAR_STRING, "defnick", {.str = L"DoldaConnect user"}}, + {CONF_VAR_INT, "scanfilemask", {.num = 0004}}, + {CONF_VAR_INT, "scandirmask", {.num = 0005}}, + {CONF_VAR_STRING, "hashcache", {.str = L"dc-hashcache"}}, + {CONF_VAR_END} +}; + +static struct configcmd mycmds[] = +{ + {"share", conf_share}, + {NULL} +}; + +static struct scanstate *scanjob = NULL; +static struct scanqueue *scanqueue = NULL; +static struct sharepoint *shares = NULL; +static struct hashcache *hashcache = NULL; +static pid_t hashjob = 0; +struct sharecache *shareroot = NULL; +unsigned long long sharesize = 0; +GCBCHAIN(sharechangecb, unsigned long long); + +static int conf_share(int argc, wchar_t **argv) +{ + struct sharepoint *share; + char *b; + + if(argc < 3) + { + flog(LOG_WARNING, "not enough arguments given for share command"); + return(1); + } + if((b = icwcstombs(argv[2], NULL)) == NULL) + { + flog(LOG_WARNING, "could not convert wcs path (%ls) to current locale's charset: %s", argv[2], strerror(errno)); + return(1); + } + for(share = shares; share != NULL; share = share->next) + { + if(!strcmp(share->path, b) && !wcscmp(share->name, argv[1])) + { + share->delete = 0; + free(b); + return(0); + } + } + share = smalloc(sizeof(*share)); + share->path = b; + share->delete = 0; + share->name = swcsdup(argv[1]); + share->next = shares; + share->prev = NULL; + if(shares != NULL) + shares->prev = share; + shares = share; + return(0); +} + +static void dumpsharecache(struct sharecache *node, int l) +{ + int i; + + for(; node != NULL; node = node->next) + { + for(i = 0; i < l; i++) + putc('\t', stdout); + printf("%ls\n", node->name); + if(node->f.b.type == FILE_DIR) + dumpsharecache(node->child, l + 1); + } +} + +static struct hashcache *newhashcache(void) +{ + struct hashcache *new; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + new->next = hashcache; + new->prev = NULL; + if(hashcache != NULL) + hashcache->prev = new; + hashcache = new; + return(new); +} + +static void freehashcache(struct hashcache *hc) +{ + if(hc->next != NULL) + hc->next->prev = hc->prev; + if(hc->prev != NULL) + hc->prev->next = hc->next; + if(hc == hashcache) + hashcache = hc->next; + free(hc); +} + +static char *findhashcachefile(int filldef) +{ + static char ret[128]; + char *hcname; + + if(getenv("HOME") != NULL) + { + snprintf(ret, sizeof(ret), "%s/.dc-hashcache", getenv("HOME")); + if(!access(ret, R_OK)) + return(ret); + } + if((hcname = icswcstombs(confgetstr("cli", "hashcache"), NULL, NULL)) == NULL) + { + flog(LOG_WARNING, "could not convert hash cache name into local charset: %s", strerror(errno)); + return(NULL); + } + if(strchr(hcname, '/') != NULL) + { + if(!access(hcname, R_OK)) + { + strcpy(ret, hcname); + return(ret); + } + } else { + snprintf(ret, sizeof(ret), "/etc/%s", hcname); + if(!access(ret, R_OK)) + return(ret); + snprintf(ret, sizeof(ret), "/usr/etc/%s", hcname); + if(!access(ret, R_OK)) + return(ret); + snprintf(ret, sizeof(ret), "/usr/local/etc/%s", hcname); + if(!access(ret, R_OK)) + return(ret); + } + if(filldef) + { + if(getenv("HOME") != NULL) + snprintf(ret, sizeof(ret), "%s/.dc-hashcache", getenv("HOME")); + else + snprintf(ret, sizeof(ret), "/etc/%s", hcname); + return(ret); + } else { + return(NULL); + } +} + +static struct hashcache *findhashcache(dev_t dev, ino_t inode) +{ + struct hashcache *hc; + + for(hc = hashcache; hc != NULL; hc = hc->next) + { + if((hc->dev == dev) && (hc->inode == inode)) + return(hc); + } + return(NULL); +} + +static void readhashcache(void) +{ + int i, wc, line; + char *hcname; + FILE *stream; + char linebuf[256]; + char *p, *p2, *wv[32], *hash; + struct hashcache *hc; + size_t len; + + if((hcname = findhashcachefile(0)) == NULL) + return; + if((stream = fopen(hcname, "r")) == NULL) + { + flog(LOG_WARNING, "could not open hash cache %s: %s", hcname, strerror(errno)); + return; + } + while(hashcache != NULL) + freehashcache(hashcache); + line = 0; + while(!feof(stream)) + { + fgets(linebuf, sizeof(linebuf), stream); + line++; + for(p = linebuf; *p; p++) + { + if(*p == '\n') + *p = ' '; + } + if(linebuf[0] == '#') + continue; + for(wc = 0, p = linebuf; (wc < 32) && ((p2 = strchr(p, ' ')) != NULL); p = p2 + 1) + { + if(p2 == p) + continue; + *p2 = 0; + wv[wc++] = p; + } + if(wc < 3) + continue; + hc = newhashcache(); + hc->dev = strtoll(wv[0], NULL, 10); + hc->inode = strtoll(wv[1], NULL, 10); + hc->mtime = strtoll(wv[2], NULL, 10); + for(i = 3; i < wc; i++) + { + if(!strcmp(wv[i], "tth")) + { + if(++i >= wc) + continue; + hash = base64decode(wv[i], &len); + if(len != 24) + { + free(hash); + continue; + } + memcpy(hc->tth, hash, 24); + free(hash); + } + } + } + fclose(stream); +} + +static void writehashcache(void) +{ + char *buf; + char *hcname; + FILE *stream; + struct hashcache *hc; + + hcname = findhashcachefile(1); + if((stream = fopen(hcname, "w")) == NULL) + { + flog(LOG_WARNING, "could not write hash cache %s: %s", hcname, strerror(errno)); + return; + } + fprintf(stream, "# Dolda Connect hash cache file\n"); + fprintf(stream, "# Generated automatically, do not edit\n"); + fprintf(stream, "# Format: DEVICE INODE MTIME [HASH...]\n"); + fprintf(stream, "# HASH := HASHTYPE HASHVAL\n"); + fprintf(stream, "# HASHTYPE can currently only be `tth'\n"); + for(hc = hashcache; hc != NULL; hc = hc->next) + { + buf = base64encode(hc->tth, 24); + fprintf(stream, "%lli %lli %li tth %s\n", hc->dev, (long long)hc->inode, hc->mtime, buf); + free(buf); + } + fclose(stream); +} + +static void hashread(struct socket *sk, void *uudata) +{ + static char *hashbuf; + static size_t hashbufsize = 0, hashbufdata = 0; + char *buf, *p, *p2, *lp; + size_t bufsize; + char *wv[32]; + int wc; + dev_t dev; + ino_t inode; + time_t mtime; + struct hashcache *hc; + + if((buf = sockgetinbuf(sk, &bufsize)) == NULL) + return; + bufcat(hashbuf, buf, bufsize); + free(buf); + while((lp = memchr(hashbuf, '\n', hashbufdata)) != NULL) + { + *(lp++) = 0; + wc = 0; + p = hashbuf; + while(1) + { + while((p2 = strchr(p, ' ')) == p) + p++; + wv[wc++] = p; + if(p2 == NULL) + { + break; + } else { + *p2 = 0; + p = p2 + 1; + } + } + if(wc != 4) + { + flog(LOG_ERR, "BUG: unexpected number of words (%i) arrived from hashing process", wc); + } else { + dev = strtoll(wv[0], NULL, 10); + inode = strtoll(wv[1], NULL, 10); + mtime = strtol(wv[2], NULL, 10); + if((hc = findhashcache(dev, inode)) == NULL) + { + hc = newhashcache(); + hc->dev = dev; + hc->inode = inode; + } + hc->mtime = mtime; + buf = base64decode(wv[3], NULL); + memcpy(hc->tth, buf, 24); + free(buf); + writehashcache(); + } + memmove(hashbuf, lp, hashbufdata -= (lp - hashbuf)); + } +} + +static void hashexit(pid_t pid, int status, void *uudata) +{ + if(pid != hashjob) + flog(LOG_ERR, "BUG: hashing process changed PID?! old: %i new %i", hashjob, pid); + if(status) + flog(LOG_WARNING, "hashing process exited with non-zero status: %i", status); + hashjob = 0; + checkhashes(); +} + +static int hashfile(char *path) +{ + int i, ret; + int fd; + int pfd[2]; + char buf[4096]; + struct stat sb; + struct tigertreehash tth; + char digest[24]; + struct socket *outsock; + + if((fd = open(path, O_RDONLY)) < 0) + { + flog(LOG_WARNING, "could not open %s for hashing: %s", path, strerror(errno)); + return(1); + } + if(fstat(fd, &sb) < 0) + { + flog(LOG_WARNING, "could not stat %s while hashing: %s", path, strerror(errno)); + close(fd); + return(1); + } + if(pipe(pfd) < 0) + { + flog(LOG_WARNING, "could not create pipe(!): %s", strerror(errno)); + close(fd); + return(1); + } + hashjob = fork(); + if(hashjob < 0) + { + flog(LOG_WARNING, "could not fork(!) hashing process: %s", strerror(errno)); + close(fd); + close(pfd[0]); + close(pfd[1]); + return(1); + } + if(hashjob == 0) + { + nice(10); + signal(SIGHUP, SIG_DFL); + fd = dup2(fd, 4); + pfd[1] = dup2(pfd[1], 3); + dup2(fd, 0); + dup2(pfd[1], 1); + for(i = 3; i < FD_SETSIZE; i++) + close(i); + initlog(); + inittigertree(&tth); + while((ret = read(0, buf, 4096)) > 0) + dotigertree(&tth, buf, ret); + if(ret < 0) + { + flog(LOG_WARNING, "could not read from %s while hashing: %s", path, strerror(errno)); + exit(1); + } + synctigertree(&tth); + restigertree(&tth, digest); + ret = snprintf(buf, sizeof(buf), "%lli %lli %li %s\n", sb.st_dev, (long long)sb.st_ino, sb.st_mtime, base64encode(digest, 24)); + write(1, buf, ret); + exit(0); + } + close(fd); + close(pfd[1]); + outsock = wrapsock(pfd[0]); + outsock->readcb = hashread; + childcallback(hashjob, hashexit, NULL); + return(0); +} + +/* + * Call only when hashjob == 0 + */ +static void checkhashes(void) +{ + struct sharecache *node; + struct hashcache *hc; + char *path; + + node = shareroot->child; + while(1) + { + if(node->child != NULL) + { + node = node->child; + continue; + } + if(!node->f.b.hastth) + { + if((hc = findhashcache(node->dev, node->inode)) != NULL) + { + memcpy(node->hashtth, hc->tth, 24); + node->f.b.hastth = 1; + GCBCHAINDOCB(sharechangecb, sharesize); + } else { + path = getfspath(node); + if(hashfile(path)) + { + flog(LOG_WARNING, "could not hash %s, unsharing it", path); + freecache(node); + } + free(path); + return; + } + } + while(node->next == NULL) + { + if((node = node->parent) == shareroot) + break; + } + if(node == shareroot) + break; + node = node->next; + } +} + +struct sharecache *nextscnode(struct sharecache *node) +{ + if(node->child != NULL) + return(node->child); + while(node->next == NULL) + { + node = node->parent; + if(node == shareroot) + return(NULL); + } + return(node->next); +} + +static void freescan(struct scanstate *job) +{ + if(job->dd != NULL) + closedir(job->dd); + free(job); +} + +/* No need for optimization; lookup isn't really that common */ +struct sharecache *findcache(struct sharecache *parent, wchar_t *name) +{ + struct sharecache *node; + + for(node = parent->child; node != NULL; node = node->next) + { + if(!wcscmp(node->name, name)) + return(node); + } + return(NULL); +} + +static void attachcache(struct sharecache *parent, struct sharecache *node) +{ + node->parent = parent; + node->next = parent->child; + if(parent->child != NULL) + parent->child->prev = node; + parent->child = node; +} + +static void detachcache(struct sharecache *node) +{ + if(node->next != NULL) + node->next->prev = node->prev; + if(node->prev != NULL) + node->prev->next = node->next; + if((node->parent != NULL) && (node->parent->child == node)) + node->parent->child = node->next; + node->parent = NULL; + node->next = NULL; + node->prev = NULL; +} + +static void freecache(struct sharecache *node) +{ + struct sharecache *cur, *next; + struct scanqueue *q, *nq, **fq; + + detachcache(node); + fq = &scanqueue; + for(q = scanqueue; q != NULL; q = nq) + { + nq = q->next; + if(q->state->node == node) + { + flog(LOG_DEBUG, "freed node %ls cancelled queued scan", node->name); + freescan(q->state); + *fq = q->next; + free(q); + continue; + } + fq = &q->next; + } + if(node->child != NULL) + { + for(cur = node->child; cur != NULL; cur = next) + { + next = cur->next; + freecache(cur); + } + } + CBCHAINDOCB(node, share_delete, node); + CBCHAINFREE(node, share_delete); + sharesize -= node->size; + if(node->path != NULL) + free(node->path); + if(node->name != NULL) + free(node->name); + free(node); +} + +static void freesharepoint(struct sharepoint *share) +{ + struct sharecache *node; + + if(share->next != NULL) + share->next->prev = share->prev; + if(share->prev != NULL) + share->prev->next = share->next; + if(share == shares) + shares = share->next; + if((node = findcache(shareroot, share->name)) != NULL) + freecache(node); + free(share->path); + free(share->name); + free(share); +} + +static struct sharecache *newcache(void) +{ + struct sharecache *new; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + CBCHAININIT(new, share_delete); + return(new); +} + +char *getfspath(struct sharecache *node) +{ + char *buf, *mbsname; + size_t bufsize; + + buf = smalloc(bufsize = 64); + *buf = 0; + while(node != NULL) + { + if(node->path != NULL) + { + if(bufsize < strlen(node->path) + strlen(buf) + 1) + buf = srealloc(buf, strlen(node->path) + strlen(buf) + 1); + memmove(buf + strlen(node->path), buf, strlen(buf) + 1); + memcpy(buf, node->path, strlen(node->path)); + return(buf); + } + if((mbsname = icwcstombs(node->name, NULL)) == NULL) + { + flog(LOG_WARNING, "could not map unicode share name (%ls) into filesystem charset: %s", node->name, strerror(errno)); + free(buf); + return(NULL); + } + while(bufsize < strlen(mbsname) + 1 + strlen(buf) + 1) + buf = srealloc(buf, bufsize *= 2); + memmove(buf + strlen(mbsname) + 1, buf, strlen(buf) + 1); + memcpy(buf + 1, mbsname, strlen(mbsname)); + *buf = '/'; + free(mbsname); + node = node->parent; + } + buf = srealloc(buf, strlen(buf) + 1); + return(buf); +} + +static int checknode(struct sharecache *node) +{ + char *path; + struct stat sb; + + if(node->parent == NULL) + { + return(1); + } else { + if(!checknode(node->parent)) + return(0); + path = getfspath(node); + if(stat(path, &sb) < 0) + { + flog(LOG_INFO, "%s was found to be broken (%s); scheduling rescan of parent", path, strerror(errno)); + queuescan(node->parent); + return(0); + } else { + return(1); + } + } +} + +int opensharecache(struct sharecache *node) +{ + char *path; + int fd, errbak; + + path = getfspath(node); + fd = open(path, O_RDONLY); + errbak = errno; + if(fd < 0) + { + flog(LOG_WARNING, "could not open %s: %s", path, strerror(errbak)); + checknode(node); + } + free(path); + errno = errbak; + return(fd); +} + +static struct scanstate *newscan(struct sharecache *node) +{ + struct scanstate *new; + + new = smalloc(sizeof(*new)); + new->next = NULL; + new->node = node; + new->dd = NULL; + return(new); +} + +void queuescan(struct sharecache *node) +{ + struct scanqueue *new; + + new = smalloc(sizeof(*new)); + new->state = newscan(node); + new->next = scanqueue; + scanqueue = new; +} + +/* For internal use in doscan() */ +static void removestale(struct sharecache *node) +{ + struct sharecache *cur, *next; + + for(cur = node->child; cur != NULL; cur = next) + { + next = cur->next; + if(!cur->f.b.found) + freecache(cur); + } +} + +/* For internal use in doscan() */ +static void jobdone(void) +{ + struct scanstate *jbuf; + + jbuf = scanjob; + scanjob = jbuf->next; + freescan(jbuf); + if(scanjob != NULL) + fchdir(dirfd(scanjob->dd)); +} + +int doscan(int quantum) +{ + char *path; + wchar_t *wcs; + int type; + struct sharecache *n; + struct scanstate *jbuf; + struct scanqueue *qbuf; + struct dirent *de; + struct stat sb; + struct hashcache *hc; + int dmask, fmask; + static int busybefore = 0; + + dmask = confgetint("cli", "scandirmask"); + fmask = confgetint("cli", "scanfilemask"); + if((scanjob != NULL) && (scanjob->dd != NULL)) + { + while(fchdir(dirfd(scanjob->dd)) < 0) + { + flog(LOG_WARNING, "could not fchdir to fd %i: %s", dirfd(scanjob->dd), strerror(errno)); + removestale(scanjob->node); + jobdone(); + } + } + while(quantum-- > 0) + { + if(scanjob != NULL) + { + busybefore = 1; + } else { + while(scanjob == NULL) + { + if(scanqueue == NULL) + { + if(busybefore) + { + flog(LOG_INFO, "sharing %lli bytes", sharesize); + busybefore = 0; + GCBCHAINDOCB(sharechangecb, sharesize); + if(hashjob == 0) + checkhashes(); + } + return(0); + } + busybefore = 1; + scanjob = scanqueue->state; + qbuf = scanqueue; + scanqueue = qbuf->next; + free(qbuf); + for(n = scanjob->node->child; n != NULL; n = n->next) + n->f.b.found = 0; + } + } + if(scanjob->dd == NULL) + { + path = getfspath(scanjob->node); + if((scanjob->dd = opendir(path)) == NULL) + { + flog(LOG_WARNING, "cannot open directory %s for scanning: %s, deleting from share", path, strerror(errno)); + freecache(scanjob->node); + free(path); + jobdone(); + continue; + } + free(path); + if(fchdir(dirfd(scanjob->dd)) < 0) + { + flog(LOG_WARNING, "could not fchdir to fd %i: %s", dirfd(scanjob->dd), strerror(errno)); + jobdone(); + continue; + } + } + if((de = readdir(scanjob->dd)) == NULL) + { + removestale(scanjob->node); + jobdone(); + continue; + } + if(*de->d_name == '.') + continue; + if((wcs = icmbstowcs(de->d_name, NULL)) == NULL) + { + flog(LOG_WARNING, "file name %s has cannot be converted to wchar: %s", de->d_name, strerror(errno)); + continue; + } + n = findcache(scanjob->node, wcs); + if(stat(de->d_name, &sb) < 0) + { + free(wcs); + if(n != NULL) + { + flog(LOG_WARNING, "could not stat %s: %s, deleting from share", de->d_name, strerror(errno)); + freecache(n); + } else { + flog(LOG_WARNING, "could not stat %s: %s", de->d_name, strerror(errno)); + } + continue; + } + if(S_ISDIR(sb.st_mode)) + { + if(~sb.st_mode & dmask) + { + free(wcs); + continue; + } + type = FILE_DIR; + } else if(S_ISREG(sb.st_mode)) { + if(~sb.st_mode & fmask) + { + free(wcs); + continue; + } + type = FILE_REG; + } else { + flog(LOG_WARNING, "unhandled file type: %i", sb.st_mode); + free(wcs); + continue; + } + if(n != NULL) + { + if((n->f.b.type != type) || (n->mtime != sb.st_mtime) || ((type == FILE_REG) && (n->size != sb.st_size))) + { + freecache(n); + n = NULL; + } + } + if(n == NULL) + { + n = newcache(); + n->name = wcs; + if(S_ISREG(sb.st_mode)) + { + sharesize += (n->size = sb.st_size); + } else { + n->size = 0; + } + n->mtime = sb.st_mtime; + n->dev = sb.st_dev; + n->inode = sb.st_ino; + n->f.b.type = type; + attachcache(scanjob->node, n); + } else { + free(wcs); + } + n->f.b.found = 1; + if(n->f.b.type == FILE_DIR) + { + jbuf = newscan(n); + jbuf->next = scanjob; + scanjob = jbuf; + } else if(n->f.b.type == FILE_REG) { + if(n->f.b.hastth && (n->mtime != sb.st_mtime)) + n->f.b.hastth = 0; + if(!n->f.b.hastth) + { + if((hc = findhashcache(sb.st_dev, sb.st_ino)) != NULL) + { + if(hc->mtime == n->mtime) + { + n->f.b.hastth = 1; + memcpy(n->hashtth, hc->tth, 24); + } else { + freehashcache(hc); + } + } + } + } + } + return(1); +} + +void scanshares(void) +{ + struct sharepoint *cur; + struct sharecache *node; + struct stat sb; + + for(cur = shares; cur != NULL; cur = cur->next) + { + if((node = findcache(shareroot, cur->name)) == NULL) + { + if(stat(cur->path, &sb)) + { + flog(LOG_WARNING, "could not stat share \"%ls\": %s", cur->name, strerror(errno)); + continue; + } + if(!S_ISDIR(sb.st_mode)) + { + flog(LOG_WARNING, "%s is not a directory; won't share it", cur->path); + continue; + } + node = newcache(); + node->name = swcsdup(cur->name); + node->path = sstrdup(cur->path); + if(node->path[strlen(node->path) - 1] == '/') + node->path[strlen(node->path) - 1] = 0; + node->f.b.type = FILE_DIR; + attachcache(shareroot, node); + } + queuescan(node); + } +} + +static void preinit(int hup) +{ + struct sharepoint *cur; + + if(hup) + { + for(cur = shares; cur != NULL; cur = cur->next) + cur->delete = 1; + } else { + shareroot = newcache(); + shareroot->name = swcsdup(L""); + shareroot->f.b.type = FILE_DIR; + } +} + +static int init(int hup) +{ + struct sharepoint *cur, *next; + + readhashcache(); + for(cur = shares; cur != NULL; cur = next) + { + next = cur->next; + if(cur->delete) + freesharepoint(cur); + } + scanshares(); + if(!hup) + while(doscan(100)); + return(0); +} + +static int run(void) +{ + return(doscan(10)); +} + +static void terminate(void) +{ + if(hashjob != 0) + kill(hashjob, SIGHUP); + while(shares != NULL) + freesharepoint(shares); + freecache(shareroot); +} + +static struct module me = +{ + .name = "cli", + .conf = + { + .vars = myvars, + .cmds = mycmds + }, + .preinit = preinit, + .init = init, + .run = run, + .terminate = terminate +}; + +MODULE(me) diff --git a/daemon/client.h b/daemon/client.h new file mode 100644 index 0000000..7aee5cf --- /dev/null +++ b/daemon/client.h @@ -0,0 +1,85 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _CLIENT_H +#define _CLIENT_H + +#include + +#include + +#define FILE_REG 0 +#define FILE_DIR 1 +#define FILE_INT 2 + +#define HASHHASHSIZE 12 + +struct sharepoint +{ + struct sharepoint *prev, *next; + char *path; + wchar_t *name; + int delete; +}; + +struct hashcache +{ + struct hashcache *next, *prev; + dev_t dev; + ino_t inode; + time_t mtime; + char tth[24]; +}; + +struct sharecache +{ + struct sharecache *next, *prev, *child, *parent; + char *path; + wchar_t *name; + size_t size; + time_t mtime; + dev_t dev; + ino_t inode; + char hashtth[24]; + union + { + struct + { + int type:3; + int hastth:1; + int found:1; + } b; + int w; + } f; + CBCHAIN(share_delete, struct sharecache *); +}; + +void clientpreinit(void); +int clientinit(void); +int doscan(int quantum); +int opensharecache(struct sharecache *node); +struct sharecache *findcache(struct sharecache *parent, wchar_t *name); +void queuescan(struct sharecache *node); +char *getfspath(struct sharecache *node); +struct sharecache *nextscnode(struct sharecache *node); + +extern struct sharecache *shareroot; +extern unsigned long long sharesize; +EGCBCHAIN(sharechangecb, unsigned long long); + +#endif diff --git a/daemon/conf.c b/daemon/conf.c new file mode 100644 index 0000000..ea3229e --- /dev/null +++ b/daemon/conf.c @@ -0,0 +1,392 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "conf.h" +#include "log.h" +#include "utils.h" + +static struct configmod *modules = NULL; + +#if 0 +static void dumpconfig(void) +{ + struct configmod *mod; + struct configvar *var; + + for(mod = modules; mod != NULL; mod = mod->next) + { + printf("%s:\n", mod->name); + for(var = mod->vars; var->type != CONF_VAR_END; var++) + { + switch(var->type) + { + case CONF_VAR_BOOL: + printf("\t%s: %s\n", var->name, var->val.num?"t":"f"); + break; + case CONF_VAR_INT: + printf("\t%s: %i\n", var->name, var->val.num); + break; + case CONF_VAR_STRING: + printf("\t%s: \"%ls\" (%i)\n", var->name, var->val.str, wcslen(var->val.str)); + break; + case CONF_VAR_IPV4: + printf("\t%s: %s\n", var->name, inet_ntoa(var->val.ipv4)); + break; + } + } + } +} +#endif + +struct configvar *confgetvar(char *modname, char *varname) +{ + struct configmod *m; + struct configvar *v; + + for(m = modules; m != NULL; m = m->next) + { + if(!strcmp(m->name, modname)) + { + for(v = m->vars; v->type != CONF_VAR_END; v++) + { + if(!strcmp(v->name, varname)) + return(v); + } + break; + } + } + return(NULL); +} + +void confregmod(struct configmod *mod) +{ + struct configvar *var; + + for(var = mod->vars; var->type != CONF_VAR_END; var++) + { + switch(var->type) + { + case CONF_VAR_BOOL: + case CONF_VAR_INT: + var->val.num = var->defaults.num; + break; + case CONF_VAR_STRING: + if(var->defaults.str != NULL) + { + var->val.str = swcsdup(var->defaults.str); + } else { + var->val.str = NULL; + } + break; + case CONF_VAR_IPV4: + var->val.ipv4.s_addr = var->defaults.ipv4.s_addr; + break; + } + CBCHAININIT(var, conf_update); + } + mod->next = modules; + modules = mod; +} + +int runconfcmd(int argc, wchar_t **argv) +{ + struct configmod *module; + struct configvar *var; + struct configcmd *cmd; + int ret, handled; + wchar_t *p; + char *cmdn, *buf, *buf2, *valbuf; + long num; + struct in_addr newipv4; + int cb; + + if(argc < 1) + return(0); + if((cmdn = icwcstombs(argv[0], "us-ascii")) == NULL) + { + flog(LOG_WARNING, "could not convert %ls to us-ascii", argv[0]); + return(1); + } + ret = 1; + handled = 0; + if(!strcmp(cmdn, "set")) + { + handled = 1; + ret = 0; + if((p = wcschr(argv[1], L'.')) == NULL) + { + flog(LOG_WARNING, "illegal configuration variable format: %ls", argv[1]); + errno = EINVAL; + free(cmdn); + return(1); + } + *(p++) = L'\0'; + if((buf = icwcstombs(argv[1], "us-ascii")) == NULL) + { + flog(LOG_WARNING, "could not convert %ls to us-ascii", argv[1]); + free(cmdn); + return(1); + } + if((buf2 = icwcstombs(p, "us-ascii")) == NULL) + { + free(buf); + flog(LOG_WARNING, "could not convert %ls to us-ascii", p); + free(cmdn); + return(1); + } + for(module = modules; module != NULL; module = module->next) + { + if(!strcmp(module->name, buf) && (module->vars != NULL)) + { + for(var = module->vars; var->type != CONF_VAR_END; var++) + { + if(!strcmp(var->name, buf2)) + { + cb = 0; + switch(var->type) + { + case CONF_VAR_BOOL: + wcstolower(argv[2]); + if(!wcscmp(argv[2], L"off") || + !wcscmp(argv[2], L"false") || + !wcscmp(argv[2], L"no") || + !wcscmp(argv[2], L"0")) + { + if(var->val.num) + cb = 1; + var->val.num = 0; + } else if(!wcscmp(argv[2], L"on") || + !wcscmp(argv[2], L"true") || + !wcscmp(argv[2], L"yes") || + !wcscmp(argv[2], L"1")) { + if(!var->val.num) + cb = 1; + var->val.num = 1; + } else { + flog(LOG_WARNING, "unrecognized boolean: %ls", argv[2]); + } + break; + case CONF_VAR_INT: + num = wcstol(argv[2], &p, 0); + if(p == argv[2]) + { + flog(LOG_WARNING, "%ls: not a number, ignoring", argv[2]); + ret = 1; + } else { + if(*p != L'\0') + flog(LOG_WARNING, "%ls: could not entirely parse as a number, ignoring trailing garbage", argv[2]); + if(num != var->val.num) + cb = 1; + var->val.num = num; + } + break; + case CONF_VAR_STRING: + if(wcscmp(var->val.str, argv[2])) + cb = 1; + free(var->val.str); + var->val.str = swcsdup(argv[2]); + break; + case CONF_VAR_IPV4: + if((valbuf = icwcstombs(argv[2], "us-ascii")) == NULL) + { + flog(LOG_WARNING, "could not convert IPv4 address to as-ascii in var %s, ignoring", buf2); + } else { + if(!inet_aton(valbuf, &newipv4)) + { + flog(LOG_WARNING, "could not parse IPv4 address (%s), ignoring", valbuf); + memcpy(&var->val.ipv4, &var->defaults.ipv4, sizeof(var->val.ipv4)); + } else { + if(memcmp(&newipv4, &var->val.ipv4, sizeof(newipv4))) + cb = 1; + memcpy(&var->val.ipv4, &newipv4, sizeof(newipv4)); + } + free(valbuf); + } + break; + } + if(cb) + CBCHAINDOCB(var, conf_update, var); + break; + } + } + if(var == NULL) + flog(LOG_WARNING, "variable %s not found, ignoring set command", buf2); + break; + } + } + if(module == NULL) + flog(LOG_WARNING, "module %s not found, ignoring set command", buf); + free(buf2); + free(buf); + } + for(module = modules; !handled && (module != NULL); module = module->next) + { + if(module->cmds != NULL) + { + for(cmd = module->cmds; cmd->name != NULL; cmd++) + { + if(!strcmp(cmd->name, cmdn)) + { + handled = 1; + ret = cmd->handler(argc, argv); + break; + } + } + } + } + if(!handled) + flog(LOG_WARNING, "command not found: %s", cmdn); + free(cmdn); + return(ret); +} + +char *findconfigfile(void) +{ + static char pathbuf[128]; + char *p, *p2; + + if(getenv("HOME") != NULL) + { + snprintf(pathbuf, sizeof(pathbuf), "%s/.doldacond", getenv("HOME")); + if(!access(pathbuf, R_OK)) + return(pathbuf); + } + p = CONFIG_PATH; + do + { + p2 = strchr(p, ':'); + if(p2 != NULL) + { + memcpy(pathbuf, p, p2 - p); + pathbuf[p2 - p] = 0; + if(!access(pathbuf, R_OK)) + return(pathbuf); + } else { + if(!access(p, R_OK)) + return(p); + } + p = p2 + 1; + } while(p2 != NULL); + return(NULL); +} + +void readconfig(FILE *stream) +{ + int state; + wint_t c; + wchar_t *words[16]; + wchar_t *buf, *p, *p2; + int w; + int line; + + buf = smalloc(sizeof(wchar_t) * 1024); + state = 0; + c = getwc(stream); + w = 0; + line = 1; + p = buf; + while(c != WEOF) + { + if(c == '#') + { + do + c = getwc(stream); + while((c != WEOF) && (c != L'\n')); + continue; + } + switch(state) + { + case 0: + if(iswspace(c)) + { + if(c == L'\n') + { + line++; + if(runconfcmd(w, words)) + flog(LOG_WARNING, "ignoring this command on line %i", line); + w = 0; + } + c = getwc(stream); + } else { + state = 1; + p2 = p; + } + break; + case 1: + if(c == L'\"') + { + state = 2; + c = getwc(stream); + } else if(iswspace(c)) { + if(w >= 16) + { + flog(LOG_WARNING, "too many words on config line %i, ignoring rest", line); + } else { + *(p++) = L'\0'; + words[w++] = p2; + } + state = 0; + } else { + if(c == L'\\') + c = getwc(stream); + if(p - buf < 1023) + *(p++) = c; + else + flog(LOG_WARNING, "too many characters on config line %i, ignoring rest", line); + c = getwc(stream); + } + break; + case 2: + if(c == L'\"') + { + c = getwc(stream); + state = 1; + } else { + if(c == L'\\') + c = getwc(stream); + if(p - buf < 1023) + *(p++) = c; + else + flog(LOG_WARNING, "too many characters on config line %i, ignoring rest", line); + c = getwc(stream); + } + break; + } + } + free(buf); + if(ferror(stream)) + flog(LOG_WARNING, "error on configuration stream: %s", strerror(errno)); + if(state != 0) + flog(LOG_WARNING, "unexpected end of file"); +} diff --git a/daemon/conf.h b/daemon/conf.h new file mode 100644 index 0000000..7edc835 --- /dev/null +++ b/daemon/conf.h @@ -0,0 +1,75 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _CONF_H +#define _CONF_H + +#include +#include +#include +#include + +#define CONFIG_PATH "/usr/local/etc/doldacond.conf:/usr/etc/doldacond.conf:/etc/doldacond.conf" + +#define CONF_VAR_END -1 +#define CONF_VAR_BOOL 0 +#define CONF_VAR_INT 1 +#define CONF_VAR_STRING 2 +#define CONF_VAR_IPV4 3 + +struct configvar +{ + int type; + char *name; + union + { + int num; + wchar_t *str; + struct in_addr ipv4; + } defaults; + union + { + int num; + wchar_t *str; + struct in_addr ipv4; + } val; + CBCHAIN(conf_update, struct configvar *); +}; + +struct configcmd +{ + char *name; + int (*handler)(int argc, wchar_t **argv); +}; + +struct configmod +{ + struct configmod *next; + char *name; + struct configvar *vars; + struct configcmd *cmds; +}; + +struct configvar *confgetvar(char *modname, char *varname); +#define confgetint(m, v) (confgetvar((m), (v))->val.num) +#define confgetstr(m, v) (confgetvar((m), (v))->val.str) +void confregmod(struct configmod *mod); +void readconfig(FILE *stream); +char *findconfigfile(void); + +#endif diff --git a/daemon/emacs-local b/daemon/emacs-local new file mode 100644 index 0000000..f7263a1 --- /dev/null +++ b/daemon/emacs-local @@ -0,0 +1,13 @@ +; -*-Lisp-*- + +; Use with: +; (add-hook 'find-file-hooks +; (lambda () +; (load (concat default-directory "emacs-local") t))) + +(if + (string-match "\\.[ch]$" (buffer-file-name (current-buffer))) + (progn + (make-local-variable 'compile-command) + (setq compile-command "make -k 'CFLAGS=-g -Wall'") +)) diff --git a/daemon/filenet.c b/daemon/filenet.c new file mode 100644 index 0000000..81a83c4 --- /dev/null +++ b/daemon/filenet.c @@ -0,0 +1,473 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "filenet.h" +#include "search.h" +#include "module.h" +#include "utils.h" +#include "net.h" + +static struct fnet *networks = NULL; +struct fnetnode *fnetnodes = NULL; +int numfnetnodes = 0; +GCBCHAIN(newfncb, struct fnetnode *); + +static struct fnetnode *newfn(struct fnet *fnet) +{ + static int curid = 0; + struct fnetnode *new; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + new->fnet = fnet; + new->refcount = 1; + new->id = curid++; + new->mynick = swcsdup(confgetstr("cli", "defnick")); + new->srchwait = confgetint("fnet", "srchwait"); + new->state = FNN_SYN; + CBCHAININIT(new, fnetnode_ac); + CBCHAININIT(new, fnetnode_chat); + CBCHAININIT(new, fnetnode_unlink); + CBCHAININIT(new, fnetnode_destroy); + new->next = NULL; + new->prev = NULL; + numfnetnodes++; + return(new); +} + +void killfnetnode(struct fnetnode *fn) +{ + fnetsetstate(fn, FNN_DEAD); + if(fn->sk != NULL) + { + fn->sk->close = 1; + if(fn->sk->data == fn) + putfnetnode(fn); + putsock(fn->sk); + fn->sk = NULL; + } +} + +void getfnetnode(struct fnetnode *fn) +{ + fn->refcount++; +#ifdef DEBUG + fprintf(stderr, "getfnetnode on id %i at %p, refcount=%i\n", fn->id, fn, fn->refcount); +#endif +} + +void putfnetnode(struct fnetnode *fn) +{ + struct fnetnode *cur; + +#ifdef DEBUG + fprintf(stderr, "putfnetnode on id %i at %p, refcount=%i\n", fn->id, fn, fn->refcount - 1); +#endif + if(--fn->refcount) + return; + for(cur = fnetnodes; cur != NULL; cur = cur->next) + { + if(cur == fn) + flog(LOG_CRIT, "BUG: fnetnode reached refcount 0 while still in list - id %i", fn->id); + } + CBCHAINDOCB(fn, fnetnode_destroy, fn); + CBCHAINFREE(fn, fnetnode_ac); + CBCHAINFREE(fn, fnetnode_chat); + CBCHAINFREE(fn, fnetnode_unlink); + CBCHAINFREE(fn, fnetnode_destroy); + if(fn->fnet->destroy != NULL) + fn->fnet->destroy(fn); + while(fn->peers != NULL) + fnetdelpeer(fn->peers); + if(fn->mynick != NULL) + free(fn->mynick); + if(fn->name != NULL) + free(fn->name); + if(fn->sk != NULL) + putsock(fn->sk); + free(fn); + numfnetnodes--; +} + +struct fnetnode *findfnetnode(int id) +{ + struct fnetnode *fn; + + for(fn = fnetnodes; (fn != NULL) && (fn->id != id); fn = fn->next); + return(fn); +} + +void linkfnetnode(struct fnetnode *fn) +{ + if(fn->linked) + return; + getfnetnode(fn); + fn->next = fnetnodes; + if(fnetnodes != NULL) + fnetnodes->prev = fn; + fnetnodes = fn; + fn->linked = 1; + GCBCHAINDOCB(newfncb, fn); +} + +void unlinkfnetnode(struct fnetnode *fn) +{ + if(!fn->linked) + return; + if(fnetnodes == fn) + fnetnodes = fn->next; + if(fn->next != NULL) + fn->next->prev = fn->prev; + if(fn->prev != NULL) + fn->prev->next = fn->next; + fn->linked = 0; + CBCHAINDOCB(fn, fnetnode_unlink, fn); + putfnetnode(fn); +} + +static void conncb(struct socket *sk, int err, struct fnetnode *data) +{ + if(err != 0) + { + killfnetnode(data); + putfnetnode(data); + return; + } + data->sk = sk; + fnetsetstate(data, FNN_HS); + socksettos(sk, confgetint("fnet", "fntos")); + data->fnet->connect(data); + putfnetnode(data); +} + +static void resolvecb(struct sockaddr *addr, int addrlen, struct fnetnode *data) +{ + if(addr == NULL) + { + killfnetnode(data); + putfnetnode(data); + } else { + netcsconn(addr, addrlen, (void (*)(struct socket *, int, void *))conncb, data); + } +} + +static struct fnetpeerdatum *finddatum(struct fnetnode *fn, wchar_t *id) +{ + struct fnetpeerdatum *datum; + + for(datum = fn->peerdata; datum != NULL; datum = datum->next) + { + if(!wcscmp(datum->id, id)) + break; + } + return(datum); +} + +static struct fnetpeerdatum *adddatum(struct fnetnode *fn, wchar_t *id, int datatype) +{ + struct fnetpeerdatum *new; + + new = smalloc(sizeof(*new)); + new->refcount = 0; + new->id = swcsdup(id); + new->datatype = datatype; + new->prev = NULL; + new->next = fn->peerdata; + if(fn->peerdata != NULL) + fn->peerdata->prev = new; + fn->peerdata = new; + return(new); +} + +static struct fnetpeerdi *difindoradd(struct fnetpeer *peer, struct fnetpeerdatum *datum) +{ + int i; + + for(i = 0; i < peer->dinum; i++) + { + if(peer->peerdi[i].datum == datum) + break; + } + if(i >= peer->dinum) + { + peer->peerdi = srealloc(peer->peerdi, sizeof(struct fnetpeerdi) * (peer->dinum + 1)); + memset(&peer->peerdi[peer->dinum], 0, sizeof(struct fnetpeerdi)); + peer->peerdi[peer->dinum].datum = datum; + datum->refcount++; + return(&peer->peerdi[peer->dinum++]); + } else { + return(&peer->peerdi[i]); + } +} + +void fnetpeersetstr(struct fnetpeer *peer, wchar_t *id, wchar_t *value) +{ + struct fnetpeerdatum *datum; + struct fnetpeerdi *di; + + if((datum = finddatum(peer->fn, id)) == NULL) + datum = adddatum(peer->fn, id, FNPD_STR); + di = difindoradd(peer, datum); + if(di->data.str != NULL) + free(di->data.str); + di->data.str = swcsdup(value); +} + +void fnetpeersetnum(struct fnetpeer *peer, wchar_t *id, int value) +{ + struct fnetpeerdatum *datum; + struct fnetpeerdi *di; + + if((datum = finddatum(peer->fn, id)) == NULL) + datum = adddatum(peer->fn, id, FNPD_INT); + di = difindoradd(peer, datum); + di->data.num = value; +} + +void fnetpeersetlnum(struct fnetpeer *peer, wchar_t *id, long long value) +{ + struct fnetpeerdatum *datum; + struct fnetpeerdi *di; + + if((datum = finddatum(peer->fn, id)) == NULL) + datum = adddatum(peer->fn, id, FNPD_LL); + di = difindoradd(peer, datum); + di->data.lnum = value; +} + +static void putdatum(struct fnetpeer *peer, struct fnetpeerdatum *datum) +{ + if(--datum->refcount > 0) + return; + if(datum->next != NULL) + datum->next->prev = datum->prev; + if(datum->prev != NULL) + datum->prev->next = datum->next; + if(datum == peer->fn->peerdata) + peer->fn->peerdata = datum->next; + free(datum->id); + free(datum); +} + +void fnetpeerunset(struct fnetpeer *peer, wchar_t *id) +{ + int i; + struct fnetpeerdatum *datum; + + if((datum = finddatum(peer->fn, id)) == NULL) + return; + for(i = 0; i < peer->dinum; i++) + { + if(peer->peerdi[i].datum == datum) + break; + } + if(i >= peer->dinum) + return; + if((datum->datatype == FNPD_STR) && (peer->peerdi[i].data.str != NULL)) + free(peer->peerdi[i].data.str); + peer->dinum--; + memmove(&peer->peerdi[i], &peer->peerdi[i + 1], sizeof(struct fnetpeerdi) * (peer->dinum - i)); + putdatum(peer, datum); +} + +struct fnetpeer *fnetaddpeer(struct fnetnode *fn, wchar_t *id, wchar_t *nick) +{ + struct fnetpeer *new; + + new = smalloc(sizeof(*new)); + new->fn = fn; + new->id = swcsdup(id); + new->nick = swcsdup(nick); + new->flags.w = 0; + new->dinum = 0; + new->peerdi = NULL; + new->next = fn->peers; + new->prev = NULL; + if(fn->peers != NULL) + fn->peers->prev = new; + fn->peers = new; + fn->numpeers++; + CBCHAINDOCB(fn, fnetnode_ac, fn, L"numpeers"); + return(new); +} + +void fnetdelpeer(struct fnetpeer *peer) +{ + int i; + + if(peer->next != NULL) + peer->next->prev = peer->prev; + if(peer->prev != NULL) + peer->prev->next = peer->next; + if(peer->fn->peers == peer) + peer->fn->peers = peer->next; + peer->fn->numpeers--; + CBCHAINDOCB(peer->fn, fnetnode_ac, peer->fn, L"numpeers"); + free(peer->id); + free(peer->nick); + for(i = 0; i < peer->dinum; i++) + { + if((peer->peerdi[i].datum->datatype == FNPD_STR) && (peer->peerdi[i].data.str != NULL)) + free(peer->peerdi[i].data.str); + putdatum(peer, peer->peerdi[i].datum); + } + if(peer->peerdi != NULL) + free(peer->peerdi); + free(peer); +} + +struct fnetpeer *fnetfindpeer(struct fnetnode *fn, wchar_t *id) +{ + struct fnetpeer *cur; + + for(cur = fn->peers; (cur != NULL) && wcscmp(cur->id, id); cur = cur->next); + return(cur); +} + +int fnetsetnick(struct fnetnode *fn, wchar_t *newnick) +{ + int ret; + + if(fn->fnet->setnick != NULL) + ret = fn->fnet->setnick(fn, newnick); + else + ret = 0; + if(!ret) + { + if(fn->mynick != NULL) + free(fn->mynick); + fn->mynick = swcsdup(newnick); + } + return(ret); +} + +int fnetsendchat(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string) +{ + if(fn->fnet->sendchat == NULL) + { + errno = ENOTSUP; + return(-1); + } + return(fn->fnet->sendchat(fn, public, to, string)); +} + +int fnetsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln) +{ + if(fn->fnet->search == NULL) + { + errno = ENOTSUP; + return(-1); + } + return(fn->fnet->search(fn, srch, ln)); +} + +void fnetsetname(struct fnetnode *fn, wchar_t *newname) +{ + if(fn->name != NULL) + free(fn->name); + fn->name = swcsdup(newname); + CBCHAINDOCB(fn, fnetnode_ac, fn, L"name"); +} + +void fnetsetstate(struct fnetnode *fn, int newstate) +{ + fn->state = newstate; + CBCHAINDOCB(fn, fnetnode_ac, fn, L"state"); +} + +struct fnet *findfnet(wchar_t *name) +{ + struct fnet *fnet; + + for(fnet = networks; fnet != NULL; fnet = fnet->next) + { + if(!wcscmp(name, fnet->name)) + break; + } + return(fnet); +} + +struct fnetnode *fnetinitconnect(wchar_t *name, char *addr) +{ + struct fnet *fnet; + struct fnetnode *fn; + + if((fnet = findfnet(name)) == NULL) + { + errno = EPROTONOSUPPORT; + return(NULL); + } + fn = newfn(fnet); + getfnetnode(fn); + if(netresolve(addr, (void (*)(struct sockaddr *, int, void *))resolvecb, fn) < 0) + return(NULL); + return(fn); +} + +void regfnet(struct fnet *fnet) +{ + fnet->next = networks; + networks = fnet; +} + +/* + * Note on the chat string: Must be in UNIX text file format - that + * is, LF line endings. The filenet-specific code must see to it that + * any other kind of format is converted into that. In the future, + * certain control characters and escape sequences will be parsed by + * the client. Make sure that any filenet-specific code strips any + * such that aren't supposed to be in the protocol. + * + * Note on "name": This is supposed to be an identifier for the + * source. If the chat is a public message, set "public" to non-zero + * and "name" to whatever "chat room" name is appropriate for the + * fnetnode, but not NULL. If there is a "default" channel in this + * filenet, set "name" to the empty string. If the chat is a private + * message, name is ignored. + */ +void fnethandlechat(struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *chat) +{ + CBCHAINDOCB(fn, fnetnode_chat, fn, public, name, peer, chat); +} + +static struct configvar myvars[] = +{ + {CONF_VAR_INT, "srchwait", {.num = 15}}, + {CONF_VAR_INT, "fntos", {.num = 0}}, + {CONF_VAR_INT, "fnptos", {.num = 0}}, + {CONF_VAR_END} +}; + +static struct module me = +{ + .conf = + { + .vars = myvars + }, + .name = "fnet" +}; + +MODULE(me) diff --git a/daemon/filenet.h b/daemon/filenet.h new file mode 100644 index 0000000..8e3da7b --- /dev/null +++ b/daemon/filenet.h @@ -0,0 +1,138 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _FILENET_H +#define _FILENET_H + +#include +#include "net.h" +#include "utils.h" +#include "search.h" + +#define FNN_SYN 0 +#define FNN_HS 1 +#define FNN_EST 2 +#define FNN_DEAD 3 + +#define FNPD_INT 0 +#define FNPD_LL 1 +#define FNPD_STR 2 + +struct fnetnode; +struct fnetpeer; + +struct fnet +{ + struct fnet *next; + wchar_t *name; + void (*connect)(struct fnetnode *fn); + void (*destroy)(struct fnetnode *fn); + int (*setnick)(struct fnetnode *fn, wchar_t *newnick); + int (*reqconn)(struct fnetpeer *peer); + int (*sendchat)(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string); + int (*search)(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln); + wchar_t *(*filebasename)(wchar_t *filename); +}; + +struct fnetpeerdatum +{ + struct fnetpeerdatum *next, *prev; + int refcount; + wchar_t *id; + int datatype; +}; + +struct fnetpeerdi +{ + struct fnetpeerdatum *datum; + union + { + int num; + long long lnum; + wchar_t *str; + } data; +}; + +struct fnetpeer +{ + struct fnetpeer *next, *prev; + struct fnetnode *fn; + wchar_t *id; + wchar_t *nick; + union + { + struct + { + int delete:1; + int op:1; + } b; + int w; + } flags; + int dinum; + struct fnetpeerdi *peerdi; +}; + +struct fnetnode +{ + struct fnetnode *next, *prev; + int refcount; + int id; + int state; + int linked; + time_t srchwait, lastsrch; + wchar_t *name; + wchar_t *mynick; + struct fnet *fnet; + struct socket *sk; + struct fnetpeerdatum *peerdata; + struct fnetpeer *peers; + CBCHAIN(fnetnode_ac, struct fnetnode *fn, wchar_t *attrib); + CBCHAIN(fnetnode_chat, struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *string); + CBCHAIN(fnetnode_unlink, struct fnetnode *fn); + CBCHAIN(fnetnode_destroy, struct fnetnode *fn); + int numpeers; + void *data; +}; + +void regfnet(struct fnet *fnet); +void fnetsetname(struct fnetnode *fn, wchar_t *newname); +void fnetsetstate(struct fnetnode *fn, int newstate); +int fnetsetnick(struct fnetnode *fn, wchar_t *newnick); +struct fnet *findfnet(wchar_t *name); +struct fnetnode *fnetinitconnect(wchar_t *name, char *addr); +void linkfnetnode(struct fnetnode *fn); +void unlinkfnetnode(struct fnetnode *fn); +void getfnetnode(struct fnetnode *fn); +void putfnetnode(struct fnetnode *fn); +void killfnetnode(struct fnetnode *fn); +struct fnetpeer *fnetaddpeer(struct fnetnode *fn, wchar_t *id, wchar_t *nick); +void fnetdelpeer(struct fnetpeer *peer); +struct fnetpeer *fnetfindpeer(struct fnetnode *fn, wchar_t *id); +void fnetpeersetstr(struct fnetpeer *peer, wchar_t *id, wchar_t *value); +void fnetpeersetnum(struct fnetpeer *peer, wchar_t *id, int value); +void fnetpeersetlnum(struct fnetpeer *peer, wchar_t *id, long long value); +void fnetpeerunset(struct fnetpeer *peer, wchar_t *id); +struct fnetnode *findfnetnode(int id); +void fnethandlechat(struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *chat); +int fnetsendchat(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string); +int fnetsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln); + +extern struct fnetnode *fnetnodes; +EGCBCHAIN(newfncb, struct fnetnode *); + +#endif diff --git a/daemon/fnet-dc.c b/daemon/fnet-dc.c new file mode 100644 index 0000000..4afd0c0 --- /dev/null +++ b/daemon/fnet-dc.c @@ -0,0 +1,3302 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "filenet.h" +#include "log.h" +#include "module.h" +#include "utils.h" +#include "client.h" +#include "transfer.h" +#include "sysevents.h" +#include "net.h" + +/* + * The Direct Connect protocol is extremely ugly. Thus, this code must + * also be a bit ugly in certain places. Please forgive me. =/ + * + * This also means that there might be some pieces of the code that + * look completely illogical to you, and you think that you may make + * them much neater/better. However, in many cases it might just be + * that it's required to cope with some oddity of the protocol, such + * as the amazingly ugly delayed key response in the peer protocol. + */ + +/* I assume this is the correct character set to use for DC, + * considering it was developed without i18n support under Windows */ +#define DCCHARSET "windows-1252" + +#ifdef DCPP_MASQUERADE +/* + * I honestly don't want to pretend being a client that I'm not, but + * there are so many hubs that simply do not accept any clients + * outside their whitelists, for no obvious reasons, so I feel that I + * am left with little choice. Anyhow, as long as I actually support + * all the features that my faked DC++ version does, there should be + * very little harm done. + */ +#define DCIDTAG "++" +#define DCIDTAGV "0.674" +#define DCIDFULL "DC++ 0.674" +#else +#define DCIDTAG "Dolda" +#define DCIDTAGV VERSION +#define DCIDFULL "DoldaConnect " VERSION +#endif + +#define PEER_CMD 0 +#define PEER_TRNS 1 +#define PEER_SYNC 2 + +#define CPRS_NONE 0 +#define CPRS_ZLIB 1 + +struct command +{ + char *name; + void (*handler)(struct socket *sk, void *data, char *cmd, char *args); +}; + +struct qcommand +{ + struct qcommand *next; + char *string; +}; + +struct dchub +{ + char *inbuf; + size_t inbufdata, inbufsize; + struct qcommand *queue; + int extended; + char *nativename; + char *nativenick; +}; + +struct dcexppeer +{ + struct dcexppeer *next, *prev; + char *nick; + struct fnetnode *fn; + struct timer *expire; +}; + +struct dcpeer +{ + struct dcpeer *next, *prev; + struct socket *sk; + struct fnetnode *fn; + char *inbuf; + size_t inbufdata, inbufsize; + int freeing; + struct qcommand *queue; + struct transfer *transfer; + int state; + int ptclose; /* Close after transfer is complete */ + int accepted; /* If false, we connected, otherwise, we accepted */ + int extended; + int direction; /* Using the constants from transfer.h */ + int compress; + void *cprsdata; + char *mbspath; + char *key; + char *nativename; + char **supports; + wchar_t *wcsname; +}; + +static struct fnet dcnet; +static struct transferiface dctransfer; +static struct socket *udpsock = NULL; +static struct socket *tcpsock = NULL; +static struct dcpeer *peers = NULL; +int numdcpeers = 0; +static struct dcexppeer *expected = NULL; +static char *hmlistname = NULL; +static char *xmllistname = NULL; +static char *xmlbz2listname = NULL; + +static void peerconnect(struct socket *sk, int err, struct fnetnode *fn); +static void freedcpeer(struct dcpeer *peer); +static void transread(struct socket *sk, struct dcpeer *peer); +static void transerr(struct socket *sk, int err, struct dcpeer *peer); +static void transwrite(struct socket *sk, struct dcpeer *peer); +static void updatehmlist(void); +static void updatexmllist(void); +static void updatexmlbz2list(void); + +static int reservedchar(unsigned char c) +{ + return((c == 0) || (c == 5) || (c == 124) || (c == 96) || (c == 126) || (c == 36)); +} + +/* Oh, how I despise having to do this... */ +static char *dcmakekey(char *lock) +{ + int i, len, offset; + char *buf, *key; + char save; + + buf = smalloc(strlen(lock)); + save = 5; + len = 0; + for(i = 0; lock[i]; i++) + { + buf[i] = lock[i] ^ save; + buf[i] = ((buf[i] & 0x0F) << 4) | ((buf[i] & 0xF0) >> 4); + save = lock[i]; + if((i != 0) && reservedchar(buf[i])) + len += 10; + else + len++; + } + buf[0] ^= buf[i - 1]; + if(reservedchar(buf[0])) + len += 10; + else + len++; + key = smalloc(len + 1); + offset = 0; + for(i = 0; lock[i] != 0; i++) + { + if(reservedchar(buf[i])) + offset += sprintf(key + offset, "/%%DCN%03i%%/", buf[i]); + else + key[offset++] = buf[i]; + } + key[offset] = 0; + free(buf); + return(key); +} + +static void endcompress(struct dcpeer *peer) +{ + if(peer->compress == CPRS_ZLIB) + { + deflateEnd(peer->cprsdata); + free(peer->cprsdata); + } + peer->compress = CPRS_NONE; +} + +static void initcompress(struct dcpeer *peer, int algo) +{ + int ret; + + endcompress(peer); + peer->compress = algo; + if(algo == CPRS_ZLIB) + { + peer->cprsdata = smalloc(sizeof(z_stream)); + memset(peer->cprsdata, 0, sizeof(z_stream)); + if((ret = deflateInit(peer->cprsdata, 3)) != Z_OK) + { + flog(LOG_CRIT, "Aiya! zlib refuses to init (%i)!", ret); + abort(); + } + } +} + +static void unquote(wchar_t *in) +{ + wchar_t *p, *p2, nc; + + for(p = in; *p != L'\0'; p++) + { + if(*p == L'&') + { + for(p2 = p + 1; (*p2 != L'\0') && (*p2 != L';') && (*p2 != L'&'); p2++); + if(*p2 == L'&') + continue; + if(*p2 == L'\0') + return; + *p2 = L'\0'; + nc = L'\0'; + if(!wcscmp(p + 1, L"amp")) + { + nc = L'&'; + } else if(p[1] == L'#') { + nc = ucptowc(wcstol(p + 2, NULL, 10)); + } + if(nc == L'\0') + { + *p2 = L';'; + p = p2; + continue; + } + *p = nc; + memmove(p + 1, p2 + 1, (wcslen(p2 + 1) + 1) * sizeof(wchar_t)); + } + } +} + +static void freeexppeer(struct dcexppeer *ep) +{ + if(ep->next != NULL) + ep->next->prev = ep->prev; + if(ep->prev != NULL) + ep->prev->next = ep->next; + if(ep == expected) + expected = ep->next; + free(ep->nick); + putfnetnode(ep->fn); + if(ep->expire != NULL) + canceltimer(ep->expire); + free(ep); +} + +static void exppeerexpire(int cancelled, struct dcexppeer *ep) +{ + ep->expire = NULL; + if(!cancelled) + freeexppeer(ep); +} + +static struct dcexppeer *expectpeer(char *nick, struct fnetnode *fn) +{ + struct dcexppeer *ep; + + ep = smalloc(sizeof(*ep)); + ep->nick = sstrdup(nick); + getfnetnode(ep->fn = fn); + ep->expire = timercallback(ntime() + 300, (void (*)(int, void *))exppeerexpire, ep); + ep->next = expected; + ep->prev = NULL; + if(expected != NULL) + expected->prev = ep; + expected = ep; + return(ep); +} + +static struct qcommand *newqcmd(struct qcommand **queue, char *string) +{ + struct qcommand *new; + + while(*queue != NULL) + queue = &(*queue)->next; + new = smalloc(sizeof(*new)); + new->string = sstrdup(string); + new->next = *queue; + *queue = new; + return(new); +} + +static struct qcommand *ulqcmd(struct qcommand **queue) +{ + struct qcommand *qcmd; + + if((qcmd = *queue) == NULL) + return(NULL); + *queue = qcmd->next; + return(qcmd); +} + +static void freeqcmd(struct qcommand *qcmd) +{ + free(qcmd->string); + free(qcmd); +} + +static void hubrecvchat(struct socket *sk, struct fnetnode *fn, char *from, char *string) +{ + wchar_t *chat, *wfrom, *wpeer; + char *p, *end; + struct fnetpeer *peer; + + end = string + strlen(string); + while((p = strchr(string, 13)) != NULL) + memmove(p, p + 1, (end-- - p)); + if(from != NULL) + { + if((strlen(string) > strlen(from) + 2) && (*string == '<') && !memcmp(string + 1, from, strlen(from)) && (*(string + strlen(from) + 1) == '>')) + string += strlen(from) + 2; + if((wfrom = icmbstowcs(from, DCCHARSET)) == NULL) + return; + wpeer = swcsdup(wfrom); + } else { + wfrom = NULL; + wpeer = NULL; + if(*string == '<') + { + for(p = string + 1; *p; p++) + { + if((*p == ' ') || (*p == '>')) + break; + } + if(*p == '>') + { + *(p++) = 0; + if(*p == ' ') + p++; + if((wpeer = icmbstowcs(string + 1, DCCHARSET)) == NULL) + return; + string = p; + } + } + if(wpeer == NULL) + wpeer = swcsdup(L""); + } + if((chat = icmbstowcs(string, DCCHARSET)) == NULL) + { + if(wfrom != NULL) + free(wfrom); + free(wpeer); + return; + } + unquote(chat); + if(wfrom != NULL) + { + if((peer = fnetfindpeer(fn, wfrom)) == NULL) /* Assume public chat */ + fnethandlechat(fn, 1, wfrom, wpeer, chat); + else + fnethandlechat(fn, 0, wfrom, wpeer, chat); + } else { + fnethandlechat(fn, 1, L"", wpeer, chat); + } + if(wfrom != NULL) + free(wfrom); + free(wpeer); + free(chat); +} + +static void sendadc(struct socket *sk, char *arg) +{ + char *buf; + size_t bufsize, bufdata; + + buf = NULL; + bufsize = bufdata = 0; + addtobuf(buf, ' '); + for(; *arg; arg++) + { + if(*arg == ' ') + { + bufcat(buf, "\\s", 2); + } else if(*arg == '\n') { + bufcat(buf, "\\n", 2); + } else if(*arg == '\\') { + bufcat(buf, "\\\\", 2); + } else { + addtobuf(buf, *arg); + } + } + sockqueue(sk, buf, bufdata); + free(buf); +} + +static void sendadcf(struct socket *sk, char *arg, ...) +{ + char *buf; + va_list args; + + va_start(args, arg); + buf = vsprintf2(arg, args); + va_end(args); + if(buf == NULL) + return; + sendadc(sk, buf); + free(buf); +} + +static char **parseadc(char *args) +{ + char **retbuf; + size_t retbufsize, retbufdata; + char *buf; + size_t bufsize, bufdata; + int state; + + retbuf = NULL; + buf = NULL; + retbufsize = retbufdata = bufsize = bufdata = 0; + state = 0; + while(state != 3) + { + switch(state) + { + case 0: + if(*args == 0) + state = 3; + else if(*args != ' ') + state = 1; + break; + case 1: + if((*args == ' ') || (*args == 0)) + { + addtobuf(buf, 0); + addtobuf(retbuf, buf); + buf = NULL; + bufsize = bufdata = 0; + if(*args == 0) + state = 3; + } else if(*args == '\\') { + state = 2; + } else { + addtobuf(buf, *args); + } + args++; + break; + case 2: + if(*args == 0) + { + if(buf != NULL) + free(buf); + addtobuf(retbuf, NULL); + freeparr(retbuf); + return(NULL); + } else if(*args == 's') { + addtobuf(buf, ' '); + } else if(*args == 'n') { + addtobuf(buf, '\n'); + } else if(*args == '\\') { + addtobuf(buf, '\\'); + } + state = 1; + } + } + if(buf != NULL) + free(buf); + addtobuf(retbuf, NULL); + return(retbuf); +} + +/* Macros useful in command handlers */ +#define skipspace(s) ({if(((s) = strchr((s), ' ')) == NULL) return; else (s)++;}) +#define qstr(sk, str) sockqueue(sk, str, strlen(str)) +#define qstrf(sk, strandargs...) \ +do { \ + char *__buf__; \ + if((__buf__ = sprintf2(strandargs)) != NULL) { \ + sockqueue(sk, __buf__, strlen(__buf__)); \ + free(__buf__); \ + } \ +} while(0) + +static char *tr(char *str, char *trans) +{ + char *p; + + for(; *trans; trans += 2) + { + for(p = strchr(str, trans[0]); p != NULL; p = strchr(p, trans[0])) + *p = trans[1]; + } + return(str); +} + +static int trresumecb(struct transfer *transfer, wchar_t *cmd, wchar_t *arg, struct dcpeer *peer) +{ + if(!wcscmp(cmd, L"resume")) + { + if(arg == NULL) + { + flog(LOG_WARNING, "filter returned no position for \"resume\" on transfer %i", transfer->id); + freedcpeer(peer); + } else { + transfer->curpos = wcstol(arg, NULL, 10); + qstrf(peer->sk, "$Get %s$%i|", peer->mbspath, transfer->curpos + 1); + } + free(peer->mbspath); + peer->mbspath = NULL; + return(1); + } + return(0); +} + +static void peerhandleaction(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + struct dchub *hub; + struct dcexppeer *expect; + struct transfer *transfer; + wchar_t tbuf[128]; + char *mbsbuf; + + hub = NULL; + if(peer->fn != NULL) + { + if(peer->fn->fnet != &dcnet) + { + peer->sk->close = 1; + return; + } + hub = peer->fn->data; + } + if(peer->transfer != NULL) + { + swprintf(tbuf, 128, L"hs: dc-%s", cmd); + transfersetactivity(peer->transfer, tbuf); + } + if(peer->accepted) + { + if(cmd == NULL) /* Connect event */ + { + } else if(!strcmp(cmd, "$MyNick")) { + for(expect = expected; expect != NULL; expect = expect->next) + { + if(!strcmp(expect->nick, args)) + break; + } + if(expect == NULL) + { + peer->fn = NULL; + } else { + peer->fn = expect->fn; + getfnetnode(peer->fn); + freeexppeer(expect); + } + } else if(!strcmp(cmd, "$Lock")) { + if(peer->wcsname == NULL) + { + freedcpeer(peer); + return; + } + if(hub == NULL) + qstrf(sk, "$MyNick %s|", icswcstombs(confgetstr("cli", "defnick"), DCCHARSET, "DoldaConnectUser-IN")); + else + qstrf(sk, "$MyNick %s|", hub->nativenick); +#ifdef DCPP_MASQUERADE + qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCPLUSPLUS0.674ABCABC|"); +#else + qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DOLDA%sABCABCABC|", VERSION); +#endif + if(peer->extended) + { +#ifdef DCPP_MASQUERADE + qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG |"); +#else + qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG|"); +#endif + } + for(transfer = transfers; transfer != NULL; transfer = transfer->next) + { + if((transfer->dir == TRNSD_DOWN) && (transfer->iface == NULL) && !wcscmp(peer->wcsname, transfer->peerid)) + break; + } + if(transfer == NULL) + { + peer->direction = TRNSD_UP; + transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer); + transfersetnick(transfer, peer->wcsname); + peer->transfer = transfer; + } else { + peer->direction = TRNSD_DOWN; + peer->transfer = transfer; + transferattach(transfer, &dctransfer, peer); + transfersetnick(transfer, peer->wcsname); + transfersetstate(transfer, TRNS_HS); + } + qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000); + if(peer->key != NULL) + { + /* I hate the DC protocol so much... */ + qstrf(sk, "$Key %s|", peer->key); + free(peer->key); + peer->key = NULL; + } + if(peer->direction == TRNSD_DOWN) + { + if((mbsbuf = icwcstombs(peer->transfer->path, DCCHARSET)) == NULL) + { + /* I believe that NOTFOUND should be used + * since giving a path that cannot be + * represented in the protocol's charset is + * literally the same as giving a path that + * the client doesn't have. */ + transferseterror(peer->transfer, TRNSE_NOTFOUND); + freedcpeer(peer); + return; + } + if(peer->transfer->size == -1) + { + /* The transfer will be restarted later from + * cmd_filelength when it detects that the sizes + * don't match. */ + qstrf(sk, "$Get %s$1|", mbsbuf); + } else { + if(forkfilter(transfer)) + { + flog(LOG_WARNING, "could not fork filter for transfer %i: %s", transfer->id, strerror(errno)); + freedcpeer(peer); + free(mbsbuf); + return; + } + peer->mbspath = sstrdup(mbsbuf); + CBREG(transfer, trans_filterout, (int (*)(struct transfer *, wchar_t *, wchar_t *, void *))trresumecb, NULL, peer); + } + free(mbsbuf); + } + } else if(!strcmp(cmd, "$FileLength")) { + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + transfersetstate(peer->transfer, TRNS_MAIN); + socksettos(peer->sk, confgetint("transfer", "dltos")); + peer->state = PEER_TRNS; + peer->sk->readcb = (void (*)(struct socket *, void *))transread; + peer->sk->errcb = (void (*)(struct socket *, int, void *))transerr; + qstr(peer->sk, "$Send|"); + } + } else { + if(cmd == NULL) /* Connect event */ + { + if(hub == NULL) + qstrf(sk, "$MyNick %s|", icswcstombs(confgetstr("cli", "defnick"), DCCHARSET, "DoldaConnectUser-IN")); + else + qstrf(sk, "$MyNick %s|", hub->nativenick); +#ifdef DCPP_MASQUERADE + qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCPLUSPLUS0.674ABCABC|"); +#else + qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DOLDA%sABCABCABC|", VERSION); +#endif + } else if(!strcmp(cmd, "$Direction")) { + if(peer->wcsname == NULL) + { + freedcpeer(peer); + return; + } + if(peer->direction == TRNSD_UP) + { + transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer); + transfersetnick(transfer, peer->wcsname); + peer->transfer = transfer; + } else { + for(transfer = transfers; transfer != NULL; transfer = transfer->next) + { + if((transfer->dir == TRNSD_DOWN) && (transfer->state == TRNS_WAITING) && !wcscmp(peer->wcsname, transfer->peerid)) + break; + } + if(transfer == NULL) + { + freedcpeer(peer); + return; + } + peer->transfer = transfer; + transferattach(transfer, &dctransfer, peer); + transfersetnick(transfer, peer->wcsname); + transfersetstate(transfer, TRNS_HS); + } + if(peer->extended) + { +#ifdef DCPP_MASQUERADE + qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG |"); +#else + qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG|"); +#endif + } + qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000); + if(peer->key != NULL) + qstrf(sk, "$Key %s|", peer->key); + if(peer->direction == TRNSD_DOWN) + { + if((mbsbuf = icwcstombs(peer->transfer->path, DCCHARSET)) == NULL) + { + /* I believe that NOTFOUND should be used + * since giving a path that cannot be + * represented in the protocol's charset is + * literally the same as giving a path that + * the client doesn't have. */ + transferseterror(peer->transfer, TRNSE_NOTFOUND); + freedcpeer(peer); + return; + } + if(peer->transfer->size == -1) + { + /* The transfer will be restarted later from + * cmd_filelength when it detects that the sizes + * don't match. */ + qstrf(sk, "$Get %s$1|", mbsbuf); + } else { + if(forkfilter(transfer)) + { + flog(LOG_WARNING, "could not fork filter for transfer %i: %s", transfer->id, strerror(errno)); + freedcpeer(peer); + free(mbsbuf); + return; + } + peer->mbspath = sstrdup(mbsbuf); + CBREG(transfer, trans_filterout, (int (*)(struct transfer *, wchar_t *, wchar_t *, void *))trresumecb, NULL, peer); + } + free(mbsbuf); + } + } else if(!strcmp(cmd, "$FileLength")) { + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + transfersetstate(peer->transfer, TRNS_MAIN); + socksettos(peer->sk, confgetint("transfer", "dltos")); + peer->state = PEER_TRNS; + peer->sk->readcb = (void (*)(struct socket *, void *))transread; + peer->sk->errcb = (void (*)(struct socket *, int, void *))transerr; + qstr(peer->sk, "$Send|"); + } + } +} + +static void sendmyinfo(struct socket *sk, struct fnetnode *fn) +{ + struct dchub *hub; + char *buf; + struct fnetnode *cfn; + int numhubs; + + hub = fn->data; + qstrf(sk, "$MyINFO $ALL %s ", hub->nativenick); + buf = tr(icswcstombs(confgetstr("dc", "desc"), DCCHARSET, "Charset_conv_failure"), "$_|_"); + qstrf(sk, "%s", buf); + numhubs = 0; + for(cfn = fnetnodes; cfn != NULL; cfn = cfn->next) + { + if((cfn->state == FNN_EST) || (cfn->state == FNN_HS)) + numhubs++; + } + qstrf(sk, "<%s V:%s,M:%c,H:%i/0/0,S:%i>", + DCIDTAG, + DCIDTAGV, + (tcpsock == NULL)?'P':'A', + numhubs, + confgetint("transfer", "slots") + ); + qstrf(sk, "$ $"); + buf = tr(icswcstombs(confgetstr("dc", "speedstring"), DCCHARSET, "Charset_conv_failure"), "$_|_"); + qstrf(sk, "%s\x01$", buf); + buf = tr(icswcstombs(confgetstr("dc", "email"), DCCHARSET, "Charset_conv_failure"), "$_|_"); + qstrf(sk, "%s$", buf); + qstrf(sk, "%llu$|", sharesize); +} + +static void hubhandleaction(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + + hub = fn->data; + if(!strcmp(cmd, "$Lock")) + { + qstrf(sk, "$ValidateNick %s|", hub->nativenick); + } else if(!strcmp(cmd, "$Hello")) { + if(fn->state == FNN_HS) + { + qstrf(sk, "$Version 1,0091|"); + qstrf(sk, "$GetNickList|"); + sendmyinfo(sk, fn); + fnetsetstate(fn, FNN_EST); + } else { + qstrf(sk, "$GetINFO %s %s|", args, hub->nativenick); + } + } +} + +static void cmd_lock(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + char *key; + char *p; + + hub = fn->data; + if(!strncmp(args, "EXTENDEDPROTOCOL", 16)) + hub->extended = 1; + if((p = strchr(args, ' ')) != NULL) + *(p++) = 0; + if(hub->extended) + { +#ifdef DCPP_MASQUERADE + qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock |"); +#else + qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock|"); +#endif + } + key = dcmakekey(args); + qstrf(sk, "$Key %s|", key); + free(key); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_hubname(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + wchar_t *buf; + struct dchub *hub; + + hub = fn->data; + if(hub->nativename == NULL) + free(hub->nativename); + hub->nativename = sstrdup(args); + buf = icmbstowcs(args, DCCHARSET); + fnetsetname(fn, (buf == NULL)?L"Hubname conv error":buf); + if(buf != NULL) + free(buf); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_hello(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + wchar_t *nick; + struct dchub *hub; + + hub = fn->data; + if((nick = icmbstowcs(args, DCCHARSET)) == NULL) + return; + if(strcmp(args, hub->nativenick) && (fnetfindpeer(fn, nick) == NULL)) + fnetaddpeer(fn, nick, nick); + free(nick); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_quit(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + wchar_t *nick; + struct fnetpeer *peer; + struct dchub *hub; + + hub = fn->data; + if((nick = icmbstowcs(args, DCCHARSET)) == NULL) + return; + if((peer = fnetfindpeer(fn, nick)) != NULL) + fnetdelpeer(peer); + free(nick); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_nicklist(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + char *p; + wchar_t *buf; + struct fnetpeer *peer, *npeer; + + hub = fn->data; + for(peer = fn->peers; peer != NULL; peer = peer->next) + peer->flags.b.delete = 1; + while((p = strstr(args, "$$")) != NULL) + { + *p = 0; + if((buf = icmbstowcs(args, DCCHARSET)) != NULL) + { + if((peer = fnetfindpeer(fn, buf)) == NULL) + peer = fnetaddpeer(fn, buf, buf); + else + peer->flags.b.delete = 0; + free(buf); + qstrf(sk, "$GetINFO %s %s|", args, hub->nativenick); + } + args = p + 2; + } + for(peer = fn->peers; peer != NULL; peer = npeer) + { + npeer = peer->next; + if(peer->flags.b.delete) + fnetdelpeer(peer); + } + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_oplist(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + char *p; + wchar_t *buf; + struct fnetpeer *peer; + + hub = fn->data; + for(peer = fn->peers; peer != NULL; peer = peer->next) + peer->flags.b.op = 0; + while((p = strstr(args, "$$")) != NULL) + { + *p = 0; + if((buf = icmbstowcs(args, DCCHARSET)) != NULL) + { + if((peer = fnetfindpeer(fn, buf)) != NULL) + peer->flags.b.op = 1; + free(buf); + } + args = p + 2; + } + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_myinfo(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + char *p, *p2; + wchar_t *buf, *wp, *wp2; + wchar_t abuf[10]; + struct fnetpeer *peer; + struct dchub *hub; + + hub = fn->data; + p = args; + if(strncmp(p, "$ALL ", 5)) + return; + p += 5; + if((p2 = strchr(p, ' ')) == NULL) + return; + *p2 = 0; + if((buf = icmbstowcs(p, DCCHARSET)) == NULL) + return; + if((peer = fnetfindpeer(fn, buf)) == NULL) + peer = fnetaddpeer(fn, buf, buf); + free(buf); + p = p2 + 1; + if((p2 = strstr(p, "$ $")) == NULL) + return; + *p2 = 0; + if((buf = icmbstowcs(p, DCCHARSET)) == NULL) + return; + if((wcslen(buf) > 0) && (buf[wcslen(buf) - 1] == L'>') && ((wp = wcschr(buf, L'<')) != NULL)) + { + buf[wcslen(buf) - 1] = L'\0'; + *(wp++) = L'\0'; + if((wp2 = wcschr(wp, L' ')) != NULL) + { + *(wp2++) = L'\0'; + fnetpeersetstr(peer, L"dc-client", wp); + wp = wp2; + do + { + if((wp2 = wcschr(wp, L',')) != NULL) + *(wp2++) = L'\0'; + if(wp[1] != L':') + continue; + swprintf(abuf, 10, L"dc-tag-%lc", wp[0]); + fnetpeersetstr(peer, abuf, wp + 2); + wp = wp2; + } while(wp2 != NULL); + } + } + fnetpeersetstr(peer, L"descr", buf); + free(buf); + p = p2 + 3; + if((p2 = strchr(p, '$')) == NULL) + return; + *(p2 - 1) = 0; + if((buf = icmbstowcs(p, DCCHARSET)) == NULL) + return; + fnetpeersetstr(peer, L"dc-speed", buf); + free(buf); + p = p2 + 1; + if((p2 = strchr(p, '$')) == NULL) + return; + *p2 = 0; + if((buf = icmbstowcs(p, DCCHARSET)) == NULL) + return; + fnetpeersetstr(peer, L"email", buf); + free(buf); + p = p2 + 1; + if(strlen(p) < 1) + return; + fnetpeersetlnum(peer, L"share", strtoll(p, NULL, 10)); + hubhandleaction(sk, fn, cmd, args); +} + +/* I do not implement the fully in that I do not disconnect from the + * old hub. I do this since I believe that if the hub owner really + * wants to get rid of us, then it is his responsibility to disconnect + * us. */ +static void cmd_forcemove(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + struct fnetnode *newfn; + int freeargs; + + hub = fn->data; + if(strchr(args, ':') == NULL) + { + args = strcpy(smalloc(strlen(args) + 5), args); + strcat(args, ":411"); + freeargs = 1; + } else { + freeargs = 0; + } + if((newfn = fnetinitconnect(L"dc", args)) != NULL) + { + linkfnetnode(newfn); + putfnetnode(newfn); + } + hubhandleaction(sk, fn, cmd, args); + if(freeargs) + free(args); +} + +static char *getdcpath(struct sharecache *node, size_t *retlen) +{ + char *buf, *buf2; + size_t len, len2; + + if(node->parent == NULL) + return(NULL); + if(node->parent == shareroot) + { + if((buf = icwcstombs(node->name, DCCHARSET)) == NULL) + return(NULL); + if(retlen != NULL) + *retlen = strlen(buf); + return(buf); + } else { + if((buf2 = icwcstombs(node->name, DCCHARSET)) == NULL) + return(NULL); + if((buf = getdcpath(node->parent, &len)) == NULL) + { + free(buf2); + return(NULL); + } + len2 = strlen(buf2); + buf = srealloc(buf, len + 1 + len2 + 1); + buf[len++] = '\\'; + strcpy(buf + len, buf2); + buf[len + len2] = 0; + free(buf2); + if(retlen != NULL) + *retlen = len + len2; + return(buf); + } +} + +/* + * This is the main share searching function for Direct Connect + * peers. Feel free to optimize it if you feel the need for it. I + * haven't ever seen it take any noticable CPU time anyway, so I + * haven't felt that need. + * + * It may feel dubious to just return the directory when all terms are + * satisfied rather than all the files in it, but it really benefits + * everyone: Less cycles used for us, less UDP squelching for active + * searchers, and less bandwidth waste for hubs when serving passive + * searchers. + */ +static void cmd_search(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + int i, done; + struct dchub *hub; + char *p, *p2; + char *prefix, *infix, *postfix, *buf, *buf2; + struct socket *dsk; + struct sockaddr_in addr; + struct sharecache *node; + int minsize, maxsize; + int dotth, buflen; + int termnum, satisfied, skipcheck; + int level, tersat[32]; + wchar_t *terms[32]; + char hashtth[24]; + + hub = fn->data; + if((p = strchr(args, ' ')) == NULL) + return; + *(p++) = 0; + + memset(terms, 0, sizeof(terms)); + prefix = infix = postfix = NULL; + dsk = NULL; + dotth = 0; + + if(!strncmp(args, "Hub:", 4)) + { + if(!strcmp(cmd, "$MultiSearch")) + goto out; + if(!strcmp(args + 4, hub->nativenick)) + goto out; + prefix = sprintf2("$SR %s ", hub->nativenick); + infix = sprintf2(" %i/%i\005", slotsleft(), confgetint("transfer", "slots")); + postfix = sprintf2(" (%s)\005%s|", formataddress(fn->sk->remote, fn->sk->remotelen), args + 4); + dsk = sk; + getsock(dsk); + } else { + if((p2 = strchr(args, ':')) == NULL) + goto out; + *(p2++) = 0; + addr.sin_family = AF_INET; + if(!inet_aton(args, &addr.sin_addr)) + goto out; + addr.sin_port = htons(atoi(p2)); + prefix = sprintf2("$SR %s ", hub->nativenick); + infix = sprintf2(" %i/%i\005", slotsleft(), confgetint("transfer", "slots")); + postfix = sprintf2(" (%s)|", formataddress(fn->sk->remote, fn->sk->remotelen)); + netdgramconn(dsk = netdupsock(udpsock), (struct sockaddr *)&addr, sizeof(addr)); + } + + minsize = maxsize = -1; + if(*p == 0) + goto out; + if(*(p++) == 'T') + minsize = 0; + if(*(p++) != '?') + goto out; + if(*p == 0) + goto out; + if(*(p++) == 'T') + { + maxsize = 0; + minsize = -1; + } + if(*(p++) != '?') + goto out; + if((p2 = strchr(p, '?')) == NULL) + goto out; + *(p2++) = 0; + if(minsize == 0) + minsize = atoi(p); + if(maxsize == 0) + maxsize = atoi(p); + p = p2 + 1; + if(*(p++) != '?') + goto out; + termnum = 0; + p2 = p; + done = 0; + while(!done) + { + if((*p2 == 0) || (*p2 == '$')) + { + if(*p2 == 0) + done = 1; + else + *p2 = 0; + if(*p) + { + if(!dotth && !strncmp(p, "TTH:", 4)) + { + dotth = 1; + if((buf = base32decode(p + 4, &buflen)) == NULL) + goto out; + if(buflen != 24) + goto out; + memcpy(hashtth, buf, 24); + free(buf); + } else { + if((terms[termnum] = icmbstowcs(p, DCCHARSET)) != NULL) + termnum++; + } + } + p = p2 + 1; + if(termnum == 32) + break; + } + p2++; + } + + node = shareroot->child; + level = 0; + for(i = 0; i < termnum; i++) + tersat[i] = -1; + satisfied = 0; + while(1) + { + skipcheck = 0; + if(node->f.b.type == FILE_REG) + { + if((minsize >= 0) && (node->size < minsize)) + skipcheck = 1; + if((maxsize >= 0) && (node->size > maxsize)) + skipcheck = 1; + } + if(!skipcheck && dotth) + { + if((node->f.b.type != FILE_REG) || (node->f.b.hastth && memcmp(hashtth, node->hashtth, 24))) + skipcheck = 1; + } + if(!skipcheck) + { + for(i = 0; i < termnum; i++) + { + if(tersat[i] >= 0) + continue; + if(wcsexists(node->name, terms[i])) + { + tersat[i] = level; + satisfied++; + } else if(node->child == NULL) { + break; + } + } + } + if(!skipcheck && (satisfied == termnum)) + { + if((buf = getdcpath(node, NULL)) != NULL) + { + if(node->f.b.hastth) + { + buf2 = base32encode(node->hashtth, 24); + qstrf(dsk, "%s%s\005%i%sTTH:%.39s%s", prefix, buf, node->size, infix, buf2, postfix); + free(buf2); + } else { + qstrf(dsk, "%s%s\005%i%s%s%s", prefix, buf, node->size, infix, hub->nativename, postfix); + } + free(buf); + } + } + if((!skipcheck && (satisfied == termnum)) || (node->child == NULL)) + { + while(node->next == NULL) + { + if((node = node->parent) == shareroot) + break; + level--; + } + if(node == shareroot) + break; + for(i = 0; i < termnum; i++) + { + if(tersat[i] >= level) + { + tersat[i] = -1; + satisfied--; + } + } + node = node->next; + } else { + node = node->child; + level++; + } + } + + hubhandleaction(sk, fn, cmd, args); + + out: + if(dsk != NULL) + putsock(dsk); + if(prefix != NULL) + free(prefix); + if(infix != NULL) + free(infix); + if(postfix != NULL) + free(postfix); + for(i = 0; (i < 32) && (terms[i] != NULL); i++) + free(terms[i]); +} + +static void cmd_connecttome(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + char *p; + struct dchub *hub; + struct socket *newsk; + struct sockaddr_in addr; + + hub = fn->data; + if((p = strchr(args, ' ')) == NULL) + return; + *(p++) = 0; + if(strcmp(args, hub->nativenick)) + return; + addr.sin_family = AF_INET; + args = p; + if((p = strchr(args, ':')) == NULL) + return; + *(p++) = 0; + addr.sin_port = htons(atoi(p)); + if(!inet_aton(args, &addr.sin_addr)) + return; + newsk = netcsconn((struct sockaddr *)&addr, sizeof(addr), (void (*)(struct socket *, int, void *))peerconnect, fn); + getfnetnode(fn); + hubhandleaction(sk, fn, cmd, args); +} + +static void sendctm(struct socket *sk, char *nick) +{ + struct sockaddr *addr; + socklen_t addrlen; + + if(tcpsock == NULL) + return; + if(sockgetremotename(tcpsock, &addr, &addrlen) < 0) + return; + if(addr->sa_family == AF_INET) + qstrf(sk, "$ConnectToMe %s %s|", nick, formataddress(addr, addrlen)); + else + flog(LOG_WARNING, "Direct Connect TCP socket is suddenly not AF_INET, but %i", addr->sa_family); + free(addr); +} + +static void cmd_revconnecttome(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + char *p; + + hub = fn->data; + if((p = strchr(args, ' ')) == NULL) + return; + *(p++) = 0; + if(strcmp(p, hub->nativenick)) + return; + sendctm(sk, args); + expectpeer(args, fn); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_getnetinfo(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + struct fnetnode *node; + int numhubs; + + hub = fn->data; + numhubs = 0; + for(node = fnetnodes; node != NULL; node = node->next) + { + if(node->state == FNN_EST) + numhubs++; + } + qstrf(sk, "$NetInfo %i$%i$%c$0|", confgetint("transfer", "slots"), numhubs, (tcpsock == NULL)?'P':'A'); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_to(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + char *p, *p2; + + hub = fn->data; + p = args; + if((p2 = strchr(p, ' ')) == NULL) + return; + *(p2++) = 0; + p = p2; + if((p2 = strchr(p, ' ')) == NULL) + return; + *(p2++) = 0; + if(strcmp(p, "From:")) + return; + p = p2; + if((p2 = strstr(p, " $")) == NULL) + return; + *p2 = 0; + p2 += 2; + hubrecvchat(fn->sk, fn, p, p2); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_sr(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + char *p, *p2; + char *nick, *filename, *hubname; + int size, slots; + struct srchres *sr; + wchar_t *wnick, *wfile; + + hub = fn->data; + nick = p = args; + if((p2 = strchr(p, ' ')) == NULL) + return; + *p2 = 0; + p = p2 + 1; + filename = p; + if((p2 = strchr(p, 5)) == NULL) + return; + *p2 = 0; + p = p2 + 1; + if((p2 = strchr(p, ' ')) == NULL) + return; + *p2 = 0; + size = atoi(p); + p = p2 + 1; + if((p2 = strchr(p, '/')) == NULL) + return; + *p2 = 0; + slots = atoi(p); + p = p2 + 1; + if((p2 = strchr(p, 5)) == NULL) + return; + p = p2 + 1; + hubname = p; + if((p2 = strstr(p, " (")) == NULL) + return; + *p2 = 0; + if((wnick = icmbstowcs(nick, DCCHARSET)) == NULL) + return; + if((wfile = icmbstowcs(filename, DCCHARSET)) == NULL) + { + free(wnick); + return; + } + sr = newsrchres(&dcnet, wfile, wnick); + if(sr->peernick != NULL) + free(sr->peernick); + sr->peernick = swcsdup(wnick); + sr->size = size; + sr->slots = slots; + free(wfile); + free(wnick); + getfnetnode(sr->fn = fn); + submitsrchres(sr); + freesrchres(sr); + hubhandleaction(sk, fn, cmd, args); +} + +static void cmd_usercommand(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + /* Do nothing for now. */ +} + +static void cmd_mynick(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + if(peer->nativename != NULL) + free(peer->nativename); + peer->nativename = sstrdup(args); + if(peer->wcsname != NULL) + free(peer->wcsname); + if((peer->wcsname = icmbstowcs(peer->nativename, DCCHARSET)) == NULL) + { + freedcpeer(peer); + return; + } + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_direction(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + char *p; + + if((p = strchr(args, ' ')) == NULL) + return; + *p = 0; + if(!strcmp(args, "Upload")) + peer->direction = TRNSD_DOWN; + if(!strcmp(args, "Download")) + peer->direction = TRNSD_UP; + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_peerlock(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + char *p; + + if((p = strchr(args, ' ')) == NULL) + return; + *p = 0; + if(!strncmp(args, "EXTENDEDPROTOCOL", 16)) + peer->extended = 1; + if(peer->key != NULL) + free(peer->key); + peer->key = dcmakekey(args); + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_key(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_filelength(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + int size; + + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + size = atoi(args); + if(peer->transfer->size == size) + { + + } else { + /* Will have to restart, then... I really hate this, but what + * am I to do then, considering the DC protocol requires the + * resume offset before it sends the filesize? */ + transfersetsize(peer->transfer, size); + freedcpeer(peer); + return; + } + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_error(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + if((peer->transfer != NULL) && (peer->transfer->dir == TRNSD_DOWN)) + { + transferseterror(peer->transfer, TRNSE_NOTFOUND); + resettransfer(peer->transfer); + return; + } + freedcpeer(peer); +} + +static void cmd_maxedout(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + if((peer->transfer != NULL) && (peer->transfer->dir == TRNSD_DOWN)) + { + transferseterror(peer->transfer, TRNSE_NOSLOTS); + resettransfer(peer->transfer); + return; + } + freedcpeer(peer); +} + +static struct +{ + char *name; + char **file; + void (*func)(void); +} lists[] = +{ + {"MyList.DcLst", &hmlistname, updatehmlist}, + {"files.xml", &xmllistname, updatexmllist}, + {"files.xml.bz2", &xmlbz2listname, updatexmlbz2list}, + {NULL, NULL} +}; + +static int openfilelist(char *name) +{ + int i, fd; + int errnobak; + + for(i = 0; lists[i].name != NULL; i++) + { + if(!strcmp(name, lists[i].name)) + break; + } + errno = 0; + if(lists[i].name == NULL) + return(-1); + fd = -1; + if((*lists[i].file == NULL) || ((fd = open(*lists[i].file, O_RDONLY)) < 0)) + lists[i].func(); + if((fd < 0) && ((*lists[i].file == NULL) || ((fd = open(*lists[i].file, O_RDONLY)) < 0))) + { + errnobak = errno; + flog(LOG_ERR, "could not open filelist tempfile: %s", strerror(errno)); + errno = errnobak; + return(-1); + } + return(fd); +} + +static struct sharecache *findbytth(char *tth32) +{ + char *buf; + size_t buflen; + struct sharecache *node; + + if((buf = base32decode(tth32, &buflen)) == NULL) + return(NULL); + if(buflen != 24) + { + free(buf); + return(NULL); + } + for(node = shareroot->child; node != NULL; node = nextscnode(node)) + { + if(node->f.b.hastth && !memcmp(node->hashtth, buf, 24)) + break; + } + free(buf); + return(node); +} + +static struct sharecache *resdcpath(char *path, char *charset, char sep) +{ + struct sharecache *node; + char *p, *p2; + + node = shareroot; + p = path; + while(p != NULL) + { + if((p2 = strchr(p, sep)) != NULL) + *(p2++) = 0; + if((node = findcache(node, icsmbstowcs(p, charset, L""))) == NULL) + return(NULL); + p = p2; + } + return(node); +} + +static void cmd_get(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + int offset; + char *p, *buf; + wchar_t *buf2; + struct sharecache *node; + struct socket *lesk; + int fd; + struct stat sb; + + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + if((p = strchr(args, '$')) == NULL) + { + freedcpeer(peer); + return; + } + *(p++) = 0; + if((offset = (atoi(p) - 1)) < 0) + { + freedcpeer(peer); + return; + } + if(((fd = openfilelist(args)) < 0) && (errno != 0)) + { + qstr(sk, "$Error Could not send file list|"); + freedcpeer(peer); + return; + } else if(fd >= 0) { + if((buf2 = icsmbstowcs(args, DCCHARSET, NULL)) != NULL) + transfersetpath(peer->transfer, buf2); + } + if(fd < 0) + { + if(slotsleft() < 1) + { + qstr(sk, "$MaxedOut|"); + freedcpeer(peer); + return; + } + if((node = resdcpath(args, DCCHARSET, '\\')) == NULL) + { + qstrf(sk, "$Error File not in share|"); + freedcpeer(peer); + return; + } + if((fd = opensharecache(node)) < 0) + { + qstrf(sk, "$Error %s|", strerror(errno)); + freedcpeer(peer); + return; + } + buf = getfspath(node); + if((buf2 = icsmbstowcs(buf, NULL, NULL)) == NULL) + flog(LOG_WARNING, "could not convert native path into a wcs (%s): %s", buf, strerror(errno)); + else + transfersetpath(peer->transfer, buf2); + free(buf); + } + if(fstat(fd, &sb) < 0) + { + close(fd); + flog(LOG_WARNING, "could not stat file %ls: %s", node->name, strerror(errno)); + qstrf(sk, "$Error|"); + freedcpeer(peer); + return; + } + if((offset != 0) && (lseek(fd, offset, SEEK_SET) < 0)) + { + close(fd); + qstrf(sk, "$Error Offset out of range|"); + freedcpeer(peer); + return; + } + lesk = wrapsock(fd); + transferprepul(peer->transfer, sb.st_size, offset, -1, lesk); + putsock(lesk); + qstrf(sk, "$FileLength %i|", peer->transfer->size); + peerhandleaction(sk, peer, cmd, args); +} + +static void starttrans(struct dcpeer *peer) +{ + peer->state = PEER_TRNS; + transferstartul(peer->transfer, peer->sk); + peer->sk->writecb = (void (*)(struct socket *, void *))transwrite; +} + +static void cmd_send(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + if(peer->transfer->localend == NULL) + { + freedcpeer(peer); + return; + } + peer->ptclose = 1; + starttrans(peer); + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_supports(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + int i; + char *p, *p2; + char **arr; + size_t arrsize, arrdata; + + if(peer->supports != NULL) + { + for(i = 0; peer->supports[i] != NULL; i++) + free(peer->supports[i]); + free(peer->supports); + } + arr = NULL; + arrsize = arrdata = 0; + p = args; + do + { + if((p2 = strchr(p, ' ')) != NULL) + *(p2++) = 0; + if(*p == 0) + continue; + addtobuf(arr, sstrdup(p)); + } while((p = p2) != NULL); + addtobuf(arr, NULL); + peer->supports = arr; + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_getblock(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + int fd; + char *p, *p2; + int start, numbytes; + char *charset, *buf; + wchar_t *buf2; + struct sharecache *node; + struct stat sb; + struct socket *lesk; + + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + p = args; + if((p2 = strchr(p, ' ')) == NULL) + { + freedcpeer(peer); + return; + } + *(p2++) = 0; + start = atoi(p); + p = p2; + if((p2 = strchr(p, ' ')) == NULL) + { + freedcpeer(peer); + return; + } + *(p2++) = 0; + numbytes = atoi(p); + p = p2; + if(!strcmp(cmd, "$UGetBlock") || !strcmp(cmd, "$UGetZBlock")) + charset = "UTF-8"; + else + charset = DCCHARSET; + if(!strcmp(cmd, "$GetZBlock") || !strcmp(cmd, "$UGetZBlock")) + initcompress(peer, CPRS_ZLIB); + if(((fd = openfilelist(p)) < 0) && (errno != 0)) + { + qstr(sk, "$Error Could not send file list|"); + return; + } else if(fd >= 0) { + if((buf2 = icsmbstowcs(args, charset, NULL)) != NULL) + transfersetpath(peer->transfer, buf2); + } + if(fd < 0) + { + if(slotsleft() < 1) + { + qstr(sk, "$MaxedOut|"); + return; + } + if((node = resdcpath(p, charset, '\\')) == NULL) + { + qstr(sk, "$Error File not in cache|"); + return; + } + if((fd = opensharecache(node)) < 0) + { + qstrf(sk, "$Error %s|", strerror(errno)); + return; + } + buf = getfspath(node); + if((buf2 = icsmbstowcs(buf, NULL, NULL)) == NULL) + flog(LOG_WARNING, "could not convert native path into a wcs (%s): %s", buf, strerror(errno)); + else + transfersetpath(peer->transfer, buf2); + free(buf); + } + if(fstat(fd, &sb) < 0) + { + close(fd); + flog(LOG_WARNING, "could not stat file %ls: %s", node->name, strerror(errno)); + qstr(sk, "$Error|"); + return; + } + if((start != 0) && ((start >= sb.st_size) || (lseek(fd, start, SEEK_SET) < 0))) + { + close(fd); + qstr(sk, "$Error Offset out of range|"); + return; + } + if((numbytes < 0) || (start + numbytes > sb.st_size)) + numbytes = sb.st_size - start; + lesk = wrapsock(fd); + transferprepul(peer->transfer, sb.st_size, start, start + numbytes, lesk); + putsock(lesk); + qstrf(sk, "$Sending %i|", numbytes); + starttrans(peer); + peerhandleaction(sk, peer, cmd, args); +} + +static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + int i; + char **argv, *buf; + int start, numbytes; + struct sharecache *node; + struct stat sb; + struct socket *lesk; + wchar_t *wbuf; + int fd; + + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + if((argv = parseadc(args)) == NULL) + { + freedcpeer(peer); + return; + } + if(parrlen(argv) < 4) + { + freedcpeer(peer); + goto out; + } + start = atoi(argv[2]); + numbytes = atoi(argv[3]); + node = NULL; + fd = -1; + if(((fd = openfilelist(argv[1])) < 0) && (errno != 0)) + { + qstr(sk, "$Error Could not send file list|"); + goto out; + } else if(fd >= 0) { + if((wbuf = icsmbstowcs(argv[1], "UTF-8", NULL)) != NULL) + transfersetpath(peer->transfer, wbuf); + } + if(fd < 0) + { + if(slotsleft() < 1) + { + qstr(sk, "$MaxedOut|"); + goto out; + } + if(!strncmp(argv[1], "TTH/", 4)) + { + if((node = findbytth(argv[1] + 4)) == NULL) + { + qstr(sk, "$Error File not in cache|"); + goto out; + } + } else { + if((node = resdcpath(argv[1], "UTF-8", '/')) == NULL) + { + qstr(sk, "$Error File not in cache|"); + goto out; + } + } + if((fd = opensharecache(node)) < 0) + { + qstrf(sk, "$Error %s|", strerror(errno)); + goto out; + } + buf = getfspath(node); + if((wbuf = icsmbstowcs(buf, NULL, NULL)) == NULL) + flog(LOG_WARNING, "could not convert native path into a wcs (%s): %s", buf, strerror(errno)); + else + transfersetpath(peer->transfer, wbuf); + free(buf); + } + if(!strcmp(argv[0], "file")) + { + for(i = 4; argv[i] != NULL; i++) + { + if(!strcmp(argv[i], "ZL1")) + initcompress(peer, CPRS_ZLIB); + } + if(fstat(fd, &sb) < 0) + { + flog(LOG_WARNING, "could not stat file %ls: %s", node->name, strerror(errno)); + qstr(sk, "$Error|"); + goto out; + } + if((start != 0) && ((start >= sb.st_size) || (lseek(fd, start, SEEK_SET) < 0))) + { + qstr(sk, "$Error Offset out of range|"); + goto out; + } + if((numbytes < 0) || (start + numbytes > sb.st_size)) + numbytes = sb.st_size - start; + lesk = wrapsock(fd); + transferprepul(peer->transfer, sb.st_size, start, start + numbytes, lesk); + putsock(lesk); + fd = -1; + qstr(sk, "$ADCSND"); + sendadc(sk, "file"); + sendadc(sk, argv[1]); + sendadcf(sk, "%i", start); + sendadcf(sk, "%i", numbytes); + if(peer->compress == CPRS_ZLIB) + sendadc(sk, "ZL1"); + qstr(sk, "|"); + starttrans(peer); + } else if(!strcmp(argv[0], "tthl")) { + /* + * XXX: Implement full TTHL support. + * + * In the meantime, this is better than nothing, since it at + * least allows fetching of the TTH root if it isn't already + * known. + */ + if(node == NULL) + { + qstr(sk, "$Error no TTHL data for virtual files|"); + goto out; + } + qstr(sk, "$ADCSND"); + sendadc(sk, "tthl"); + sendadc(sk, argv[1]); + sendadc(sk, "0"); + sendadc(sk, "24"); + qstr(sk, "|"); + sockqueue(sk, node->hashtth, 24); + } else { + qstr(sk, "$Error Namespace not implemented|"); + goto out; + } + peerhandleaction(sk, peer, cmd, args); + + out: + if(fd >= 0) + close(fd); + freeparr(argv); +} + +/* +Hub skeleton: +static void cmd_(struct socket *sk, struct fnetnode *fn, char *cmd, char *args) +{ + struct dchub *hub; + + hub = fn->data; + hubhandleaction(sk, fn, cmd, args); +} + +Peer skeleton: +static void cmd_(struct socket *sk, struct dcpeer *peer, char *cmd, char *args) +{ + peerhandleaction(sk, peer, cmd, args); +} + +*/ + +static int hubreqconn(struct fnetpeer *peer) +{ + struct dchub *hub; + char *mbsnick; + + if((peer->fn->state != FNN_EST) || (peer->fn->fnet != &dcnet)) + { + errno = EINVAL; + return(1); + } + if((hub = peer->fn->data) == NULL) + { + errno = EFAULT; + return(1); + } + if((mbsnick = icwcstombs(peer->id, DCCHARSET)) == NULL) + return(1); /* Shouldn't happen, of course, but who knows... */ + if(tcpsock != NULL) + { + sendctm(peer->fn->sk, mbsnick); + expectpeer(mbsnick, peer->fn); + } else { + qstrf(peer->fn->sk, "$RevConnectToMe %s %s|", hub->nativenick, mbsnick); + } + free(mbsnick); + return(0); +} + +static int hubsendchat(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string) +{ + struct dchub *hub; + char *mbsstring, *mbsto; + + hub = fn->data; + if((mbsto = icwcstombs(to, DCCHARSET)) == NULL) + { + errno = EILSEQ; + return(1); + } + if((mbsstring = icwcstombs(string, DCCHARSET)) == NULL) + { + errno = EILSEQ; + return(1); + } + if((strchr(mbsto, '|') != NULL) || (strchr(mbsto, ' ') != NULL)) + { + free(mbsto); + free(mbsstring); + errno = ESRCH; + return(1); + } + if(strchr(mbsstring, '|') != NULL) + { + free(mbsto); + free(mbsstring); + errno = EILSEQ; + return(1); + } + if(public) + { + if(*to == L'\0') + { + qstrf(fn->sk, "<%s> %s|", hub->nativenick, mbsstring); + } else { + qstrf(fn->sk, "$To: %s From: %s $<%s> %s|", mbsto, hub->nativenick, hub->nativenick, mbsstring); + } + } else { + qstrf(fn->sk, "$To: %s From: %s $<%s> %s|", mbsto, hub->nativenick, hub->nativenick, mbsstring); + } + free(mbsto); + free(mbsstring); + return(0); +} + +static void findsizelimit(struct sexpr *sexpr, int *min, int *max) +{ + int minl, maxl, minr, maxr, retmin, retmax; + + switch(sexpr->op) + { + case SOP_AND: + findsizelimit(sexpr->l, &minl, &maxl); + findsizelimit(sexpr->r, &minr, &maxr); + retmin = (minl > minr)?minl:minr; + if((maxl != -1) && (maxr != -1)) + retmax = (maxl < maxr)?maxl:maxr; + else if(maxl != -1) + retmax = maxl; + else if(maxr != -1) + retmax = maxr; + else + retmax = -1; + break; + case SOP_OR: + findsizelimit(sexpr->l, &minl, &maxl); + findsizelimit(sexpr->r, &minr, &maxr); + retmin = (minl < minr)?minl:minr; + if((maxl == -1) || (maxr == -1)) + retmax = -1; + else + retmax = (maxl > maxr)?maxl:maxr; + break; + case SOP_NOT: + findsizelimit(sexpr->l, &minl, &maxl); + if((minl == 0) && (maxl == -1)) /* Interval is unspecified */ + { + retmin = 0; + retmax = -1; + } else if((minl == 0) && (maxl != -1)) { + retmin = maxl + 1; + retmax = -1; + } else if((minl != 0) && (maxl == -1)) { + retmin = 0; + retmax = minl - 1; + } else { /* This would yield two seperate intervals, which DC cannot handle */ + retmin = 0; + retmax = -1; + } + case SOP_SIZELT: + retmin = 0; + retmax = sexpr->d.n - 1; + break; + case SOP_SIZEEQ: + retmin = sexpr->d.n; + retmax = sexpr->d.n; + break; + case SOP_SIZEGT: + retmin = sexpr->d.n + 1; + retmax = -1; + break; + default: + retmin = 0; + retmax = -1; + break; + } + if(min != NULL) + *min = retmin; + if(max != NULL) + *max = retmax; +} + +static int hubsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln) +{ + struct dchub *hub; + struct wcslist *list, *cur; + char *sstr, *buf, *p; + size_t sstrsize, sstrdata; + struct sockaddr *name; + socklen_t namelen; + int minsize, maxsize; + char sizedesc[32]; + + hub = fn->data; + if((fn->state != FNN_EST) || (fn->sk == NULL) || (fn->sk->state != SOCK_EST)) + return(1); + list = findsexprstrs(srch->sexpr); + findsizelimit(srch->sexpr, &minsize, &maxsize); + if((minsize != 0) && (maxsize != -1)) + { + /* Choose either minsize or maxsize by trying to determine + * which choice will be most restrictive. The result can be + * approximative at best anyway, so don't try too hard... */ + if((50000000 - maxsize) > (minsize - 50000000)) + minsize = 0; + else + maxsize = -1; + } + if(minsize != 0) + { + snprintf(sizedesc, sizeof(sizedesc), "T?F?%i", minsize); + } else if(maxsize != -1) { + snprintf(sizedesc, sizeof(sizedesc), "T?T?%i", maxsize); + } else { + strcpy(sizedesc, "F?F?0"); + } + sstr = NULL; + sstrsize = sstrdata = 0; + if(list != NULL) + { + for(cur = list; cur != NULL; cur = cur->next) + { + if((buf = icwcstombs(cur->str, DCCHARSET)) == NULL) + { + /* Can't find anything anyway if the search expression + * requires characters outside DC's charset. There's + * nothing technically wrong with the search itself, + * however, so return success. This should be + * considered as an optimization. */ + freesl(&list); + if(sstr != NULL) + free(sstr); + return(0); + } + if(cur != list) + addtobuf(sstr, '$'); + /* + * It probably doesn't hurt if buf contains any extra + * dollar signs - it will just result in extra search + * terms, and the extraneous results will be filtered by + * the search layer anyway. It hurts if it contains any + * pipes, though, so let's sell them for money. + */ + for(p = buf; *p; p++) + { + if(*p == '|') + *p = '$'; + } + bufcat(sstr, buf, strlen(buf)); + free(buf); + } + } else { + /* Will match all files... :-/ */ + addtobuf(sstr, '.'); + } + addtobuf(sstr, 0); + if(tcpsock != NULL) + { + if(sockgetremotename(udpsock, &name, &namelen) < 0) + { + flog(LOG_WARNING, "cannot get address of UDP socket"); + } else { + qstrf(fn->sk, "$Search %s %s?1?%s|", formataddress(name, namelen), sizedesc, sstr); + free(name); + } + } else { + qstrf(fn->sk, "$Search Hub:%s %s?1?%s|", hub->nativenick, sizedesc, sstr); + } + free(sstr); + freesl(&list); + fn->lastsrch = time(NULL); + return(0); +} + +#undef skipcmd +#undef qstr +#undef qstrf + +#define cc(c) ((void (*)(struct socket *, void *, char *, char *))(c)) +struct command hubcmds[] = +{ + {"$Lock", cc(cmd_lock)}, + {"$HubName", cc(cmd_hubname)}, + {"$Hello", cc(cmd_hello)}, + {"$Quit", cc(cmd_quit)}, + {"$NickList", cc(cmd_nicklist)}, + {"$OpList", cc(cmd_oplist)}, + {"$MyINFO", cc(cmd_myinfo)}, + {"$ForceMove", cc(cmd_forcemove)}, + {"$Search", cc(cmd_search)}, + {"$MultiSearch", cc(cmd_search)}, + {"$ConnectToMe", cc(cmd_connecttome)}, + {"$RevConnectToMe", cc(cmd_revconnecttome)}, + {"$GetNetInfo", cc(cmd_getnetinfo)}, + {"$To:", cc(cmd_to)}, + {"$SR", cc(cmd_sr)}, + {"$UserCommand", cc(cmd_usercommand)}, + {NULL, NULL} +}; + +struct command peercmds[] = +{ + {"$MyNick", cc(cmd_mynick)}, + {"$Lock", cc(cmd_peerlock)}, + {"$Direction", cc(cmd_direction)}, + {"$Key", cc(cmd_key)}, + {"$FileLength", cc(cmd_filelength)}, + {"$Error", cc(cmd_error)}, + {"$MaxedOut", cc(cmd_maxedout)}, + {"$Get", cc(cmd_get)}, + {"$Send", cc(cmd_send)}, + {"$Supports", cc(cmd_supports)}, + {"$GetBlock", cc(cmd_getblock)}, + {"$UGetBlock", cc(cmd_getblock)}, + {"$GetZBlock", cc(cmd_getblock)}, + {"$UGetZBlock", cc(cmd_getblock)}, + {"$ADCGET", cc(cmd_adcget)}, + {NULL, NULL} +}; +#undef cc + +static void dctransdetach(struct transfer *transfer, struct dcpeer *peer) +{ + CBUNREG(transfer, trans_filterout, peer); + if(peer->freeing) + return; + peer->transfer = NULL; + freedcpeer(peer); +} + +static void dctransgotdata(struct transfer *transfer, struct dcpeer *peer) +{ + int ret; + void *buf; + char outbuf[1024]; + z_stream *cstr; + size_t bufsize; + + if((peer->state == PEER_TRNS) || (peer->state == PEER_SYNC)) + { + if(sockqueuesize(peer->sk) < 65536) + { + if((buf = transfergetdata(transfer, &bufsize)) != NULL) + { + if(peer->compress == CPRS_NONE) + { + sockqueue(peer->sk, buf, bufsize); + } else if(peer->compress == CPRS_ZLIB) { + cstr = peer->cprsdata; + cstr->next_in = buf; + cstr->avail_in = bufsize; + while(cstr->avail_in > 0) + { + cstr->next_out = outbuf; + cstr->avail_out = sizeof(outbuf); + if((ret = deflate(cstr, 0)) != Z_OK) + { + flog(LOG_WARNING, "bug? deflate() did not return Z_OK (but rather %i)", ret); + freedcpeer(peer); + return; + } + sockqueue(peer->sk, outbuf, sizeof(outbuf) - cstr->avail_out); + } + } + free(buf); + } + if(peer->state == PEER_SYNC) + { + if(peer->compress == CPRS_ZLIB) + { + cstr = peer->cprsdata; + cstr->next_in = NULL; + cstr->avail_in = 0; + do + { + cstr->next_out = outbuf; + cstr->avail_out = sizeof(outbuf); + ret = deflate(cstr, Z_FINISH); + if((ret != Z_OK) && (ret != Z_STREAM_END)) + { + flog(LOG_WARNING, "bug? deflate(Z_FINISH) did not return Z_OK (but rather %i)", ret); + freedcpeer(peer); + return; + } + sockqueue(peer->sk, outbuf, sizeof(outbuf) - cstr->avail_out); + } while(ret != Z_STREAM_END); + } + if(peer->ptclose) + { + freedcpeer(peer); + } else { + peer->state = PEER_CMD; + endcompress(peer); + transfersetstate(transfer, TRNS_HS); + socksettos(peer->sk, confgetint("fnet", "fnptos")); + peer->sk->writecb = NULL; + } + } + } + } +} + +static void dctransendofdata(struct transfer *transfer, struct dcpeer *peer) +{ + peer->state = PEER_SYNC; + dctransgotdata(transfer, peer); +} + +static void dcwantdata(struct transfer *transfer, struct dcpeer *peer) +{ + if(transferdatasize(transfer) < 65536) + peer->sk->ignread = 0; +} + +static void transread(struct socket *sk, struct dcpeer *peer) +{ + void *buf; + size_t bufsize; + struct transfer *transfer; + + if((buf = sockgetinbuf(sk, &bufsize)) == NULL) + return; + if(peer->transfer == NULL) + { + free(buf); + freedcpeer(peer); + return; + } + transferputdata(peer->transfer, buf, bufsize); + free(buf); + if(peer->transfer->curpos >= peer->transfer->size) + { + transfer = peer->transfer; + transferdetach(transfer); + transferendofdata(transfer); + return; + } + if(transferdatasize(peer->transfer) > 65535) + sk->ignread = 1; +} + +static void transerr(struct socket *sk, int err, struct dcpeer *peer) +{ + struct transfer *transfer; + + if((transfer = peer->transfer) == NULL) + { + freedcpeer(peer); + return; + } + transferdetach(transfer); + transferendofdata(transfer); +} + +static void transwrite(struct socket *sk, struct dcpeer *peer) +{ + if((peer->state != PEER_TRNS) && (peer->state != PEER_SYNC)) + return; + if(peer->transfer == NULL) + { + freedcpeer(peer); + return; + } + dctransgotdata(peer->transfer, peer); +} + +static void udpread(struct socket *sk, void *data) +{ + char *buf, *p, *p2; + size_t buflen; + char *nick, *filename, *hubname; + int size, slots; + struct fnetnode *fn, *myfn; + struct dchub *hub; + struct srchres *sr; + wchar_t *wnick, *wfile; + + if((buf = sockgetinbuf(sk, &buflen)) == NULL) + return; + buf = srealloc(buf, buflen + 1); + buf[buflen] = 0; + if(!strncmp(buf, "$SR ", 4)) + { + p = buf + 4; + nick = p; + if((p2 = strchr(p, ' ')) == NULL) + { + free(buf); + return; + } + *p2 = 0; + p = p2 + 1; + filename = p; + if((p2 = strchr(p, 5)) == NULL) + { + free(buf); + return; + } + *p2 = 0; + p = p2 + 1; + if((p2 = strchr(p, ' ')) == NULL) + { + free(buf); + return; + } + *p2 = 0; + size = atoi(p); + p = p2 + 1; + if((p2 = strchr(p, '/')) == NULL) + { + free(buf); + return; + } + *p2 = 0; + slots = atoi(p); + p = p2 + 1; + if((p2 = strchr(p, 5)) == NULL) + { + free(buf); + return; + } + p = p2 + 1; + hubname = p; + if((p2 = strstr(p, " (")) == NULL) + { + free(buf); + return; + } + *p2 = 0; + if((wnick = icmbstowcs(nick, DCCHARSET)) == NULL) + { + free(buf); + return; + } + if((wfile = icmbstowcs(filename, DCCHARSET)) == NULL) + { + free(wnick); + free(buf); + return; + } + myfn = NULL; + for(fn = fnetnodes; fn != NULL; fn = fn->next) + { + if((fn->fnet == &dcnet) && ((hub = fn->data) != NULL)) + { + if((hub->nativename != NULL) && !strcmp(hub->nativename, hubname)) + { + if(myfn == NULL) + { + myfn = fn; + } else { + myfn = NULL; + break; + } + } + } + } + sr = newsrchres(&dcnet, wfile, wnick); + if(sr->peernick != NULL) + free(sr->peernick); + sr->peernick = swcsdup(wnick); + sr->size = size; + sr->slots = slots; + free(wfile); + free(wnick); + if(myfn != NULL) + getfnetnode(sr->fn = myfn); + submitsrchres(sr); + freesrchres(sr); + } + free(buf); +} + +static void hubread(struct socket *sk, struct fnetnode *fn) +{ + struct dchub *hub; + char *newbuf; + size_t datalen; + char *p; + + hub = (struct dchub *)fn->data; + if((newbuf = sockgetinbuf(sk, &datalen)) == NULL) + return; + if(hub->inbufdata > 500000) /* Discard possible malicious data */ + hub->inbufdata = 0; + sizebuf2(hub->inbuf, hub->inbufdata + datalen, 1); + memcpy(hub->inbuf + hub->inbufdata, newbuf, datalen); + free(newbuf); + p = hub->inbuf + hub->inbufdata; + hub->inbufdata += datalen; + while((datalen > 0) && ((p = memchr(p, '|', datalen)) != NULL)) + { + *(p++) = 0; + newqcmd(&hub->queue, hub->inbuf); + memmove(hub->inbuf, p, hub->inbufdata -= p - hub->inbuf); + datalen = hub->inbufdata; + p = hub->inbuf; + } +} + +static void huberr(struct socket *sk, int err, struct fnetnode *fn) +{ + killfnetnode(fn); +} + +static int hubsetnick(struct fnetnode *fn, wchar_t *newnick) +{ + struct dchub *hub; + char *buf; + + hub = fn->data; + if((buf = icwcstombs(newnick, DCCHARSET)) == NULL) + return(1); + if((strchr(buf, ' ') != NULL) || (strchr(buf, '|') != NULL) || (strchr(buf, '$') != NULL)) + { + free(buf); + return(1); + } + if(hub == NULL) /* Not yet connected */ + { + free(buf); + return(0); + } + if(hub->nativenick != NULL) + free(hub->nativenick); + hub->nativenick = buf; + return(0); +} + +static struct dchub *newdchub(struct fnetnode *fn) +{ + struct dchub *new; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + fn->data = new; + if(hubsetnick(fn, fn->mynick)) + fnetsetnick(fn, L"DoldaConnectUser-IN"); + /* IN as in Invalid Nick */ + return(new); +} + +static struct dcpeer *newdcpeer(struct socket *sk) +{ + struct dcpeer *new; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + new->transfer = NULL; + getsock(sk); + new->sk = sk; + new->next = peers; + new->prev = NULL; + if(peers != NULL) + peers->prev = new; + peers = new; + numdcpeers++; + return(new); +} + +static void freedcpeer(struct dcpeer *peer) +{ + int i; + struct qcommand *qcmd; + + peer->freeing = 1; + if(peers == peer) + peers = peer->next; + if(peer->next != NULL) + peer->next->prev = peer->prev; + if(peer->prev != NULL) + peer->prev->next = peer->next; + if(peer->transfer != NULL) + { + if(peer->transfer->dir == TRNSD_UP) + peer->transfer->close = 1; + if(peer->transfer->dir == TRNSD_DOWN) + resettransfer(peer->transfer); + transferdetach(peer->transfer); + } + if(peer->sk->data == peer) + peer->sk->data = NULL; + peer->sk->readcb = NULL; + peer->sk->writecb = NULL; + peer->sk->errcb = NULL; + putsock(peer->sk); + endcompress(peer); + if(peer->supports != NULL) + { + for(i = 0; peer->supports[i] != NULL; i++) + free(peer->supports[i]); + free(peer->supports); + } + if(peer->mbspath != NULL) + free(peer->mbspath); + if(peer->inbuf != NULL) + free(peer->inbuf); + if(peer->key != NULL) + free(peer->key); + if(peer->wcsname != NULL) + free(peer->wcsname); + if(peer->nativename != NULL) + free(peer->nativename); + if(peer->fn != NULL) + putfnetnode(peer->fn); + while((qcmd = ulqcmd(&peer->queue)) != NULL) + freeqcmd(qcmd); + free(peer); + numdcpeers--; +} + +static void hubconnect(struct fnetnode *fn) +{ + fn->sk->readcb = (void (*)(struct socket *, void *))hubread; + fn->sk->errcb = (void (*)(struct socket *, int, void *))huberr; + getfnetnode(fn); + fn->data = newdchub(fn); + fn->sk->data = fn; + return; +} + +static void hubdestroy(struct fnetnode *fn) +{ + struct dchub *hub; + struct qcommand *qcmd; + + hub = (struct dchub *)fn->data; + if(fn->sk != NULL) + { + if(fn->sk->data == fn) + { + fn->sk->data = NULL; + putfnetnode(fn); + } + } + if(hub == NULL) + return; + while((qcmd = ulqcmd(&hub->queue)) != NULL) + freeqcmd(qcmd); + if(hub->nativename != NULL) + free(hub->nativename); + if(hub->nativenick != NULL) + free(hub->nativenick); + if(hub->inbuf != NULL) + free(hub->inbuf); + free(hub); +} + +static wchar_t *dcbasename(wchar_t *filename) +{ + wchar_t *ret; + + if((ret = wcsrchr(filename, L'\\')) != NULL) + return(ret + 1); + return(filename); +} + +static struct transferiface dctransfer = +{ + .detach = (void (*)(struct transfer *, void *))dctransdetach, + .gotdata = (void (*)(struct transfer *, void *))dctransgotdata, + .endofdata = (void (*)(struct transfer *, void *))dctransendofdata, + .wantdata = (void (*)(struct transfer *, void *))dcwantdata +}; + +static struct fnet dcnet = +{ + .name = L"dc", + .connect = hubconnect, + .destroy = hubdestroy, + .setnick = hubsetnick, + .reqconn = hubreqconn, + .sendchat = hubsendchat, + .search = hubsearch, + .filebasename = dcbasename +}; + +static void peerread(struct socket *sk, struct dcpeer *peer) +{ + char *newbuf, *p; + size_t datalen; + + if((newbuf = sockgetinbuf(sk, &datalen)) == NULL) + return; + sizebuf2(peer->inbuf, peer->inbufdata + datalen, 1); + memcpy(peer->inbuf + peer->inbufdata, newbuf, datalen); + free(newbuf); + p = peer->inbuf + peer->inbufdata; + peer->inbufdata += datalen; + while((datalen > 0) && (p = memchr(p, '|', datalen)) != NULL) + { + *(p++) = 0; + newqcmd(&peer->queue, peer->inbuf); + memmove(peer->inbuf, p, peer->inbufdata -= p - peer->inbuf); + datalen = peer->inbufdata; + p = peer->inbuf; + } +} + +static void peererror(struct socket *sk, int err, struct dcpeer *peer) +{ + freedcpeer(peer); +} + +static void peerconnect(struct socket *sk, int err, struct fnetnode *fn) +{ + struct dcpeer *peer; + + if(err != 0) + { + putfnetnode(fn); + return; + } + peer = newdcpeer(sk); + peer->fn = fn; + peer->accepted = 0; + sk->readcb = (void (*)(struct socket *, void *))peerread; + sk->errcb = (void (*)(struct socket *, int, void *))peererror; + sk->data = peer; + socksettos(sk, confgetint("fnet", "fnptos")); + peerhandleaction(sk, peer, NULL, NULL); + putsock(sk); +} + +static void peeraccept(struct socket *sk, struct socket *newsk, void *data) +{ + struct dcpeer *peer; + + peer = newdcpeer(newsk); + peer->accepted = 1; + newsk->readcb = (void (*)(struct socket *, void *))peerread; + newsk->errcb = (void (*)(struct socket *, int, void *))peererror; + newsk->data = peer; + socksettos(newsk, confgetint("fnet", "fnptos")); + peerhandleaction(sk, peer, NULL, NULL); +} + +static void updatehmlist(void) +{ + int i, lev, ic, ret; + struct sharecache *node; + char *buf, *buf2, numbuf[32]; + size_t bufsize, bufdata; + int fd, ibuf; + + bufdata = 0; + buf = smalloc(bufsize = 65536); + node = shareroot->child; + lev = 0; + while(1) + { + ic = 0; + if((buf2 = icwcstombs(node->name, DCCHARSET)) != NULL) + { + for(i = 0; i < lev; i++) + addtobuf(buf, 9); + bufcat(buf, buf2, strlen(buf2)); + free(buf2); + if(node->f.b.type == FILE_REG) + { + addtobuf(buf, '|'); + sprintf(numbuf, "%i", node->size); + bufcat(buf, numbuf, strlen(numbuf)); + } + addtobuf(buf, 13); + addtobuf(buf, 10); + } else { + ic = 1; + } + if((node->child != NULL) && !ic) + { + lev++; + node = node->child; + } else if(node->next != NULL) { + node = node->next; + } else { + while(node->next == NULL) + { + lev--; + node = node->parent; + if(node == shareroot) + break; + } + if(node == shareroot) + break; + node = node->next; + } + } + if(hmlistname != NULL) + { + unlink(hmlistname); + free(hmlistname); + } + hmlistname = sstrdup("/tmp/dc-filelist-hm-XXXXXX"); + if((fd = mkstemp(hmlistname)) < 0) + { + flog(LOG_WARNING, "could not create HM file list tempfile: %s", strerror(errno)); + free(hmlistname); + hmlistname = NULL; + } else { + /* + * I do not want to implement a good Huffman encoder, and it's not + * like Huffman encoding actually yields any impressive results + * for DC file lists anyway, so I'll just output a bogus + * tree. Implement a good encoder if you want to. + */ + write(fd, "HE3\r\0", 5); + write(fd, &bufdata, 4); + ibuf = 256; + write(fd, &ibuf, 2); + ibuf = 8; + for(i = 0; i < 256; i++) + { + write(fd, &i, 1); + write(fd, &ibuf, 1); + } + for(i = 0; i < 256; i++) + write(fd, &i, 1); + for(buf2 = buf; bufdata > 0;) + { + if((ret = write(fd, buf2, bufdata)) < 0) + { + flog(LOG_WARNING, "could not write file list: %s", strerror(errno)); + break; + } + bufdata -= ret; + buf2 += ret; + } + close(fd); + } + free(buf); +} + +static struct xmlent +{ + wchar_t c; + wchar_t *ent; +} entities[] = { + {L'&', L"&"}, + {L'\"', L"""}, +/* {L'\'', L"""}, Emulate DC++ escaping by omitting this. */ + {L'\0', NULL} +}; + +static wchar_t *escapexml(wchar_t *src) +{ + static wchar_t *buf = NULL;; + int ret, c; + wchar_t nbuf[32]; + size_t bufsize, bufdata; + struct xmlent *ent; + + if(buf != NULL) + free(buf); + buf = NULL; + bufsize = bufdata = 0; + for(; *src != L'\0'; src++) + { + c = wctob(*src); + if((c > 0) && (c < 32)) + { + bufcat(buf, L"&#", 2); + ret = swprintf(nbuf, (sizeof(nbuf) / sizeof(*nbuf)), L"%i", c); + bufcat(buf, nbuf, ret); + addtobuf(buf, L';'); + } else { + for(ent = entities; ent->ent != NULL; ent++) + { + if(ent->c == *src) + { + bufcat(buf, ent->ent, wcslen(ent->ent)); + break; + } + } + if(ent->ent == NULL) + addtobuf(buf, *src); + } + } + addtobuf(buf, L'\0'); + return(buf); +} + +static void updatexmllist(void) +{ + int i, fd, lev; + FILE *fs; + char cidbuf[14], *namebuf; + char *hashbuf; + struct sharecache *node; + + if(xmllistname != NULL) + { + unlink(xmllistname); + free(xmllistname); + } + xmllistname = sstrdup("/tmp/dc-filelist-dcxml-XXXXXX"); + if((fd = mkstemp(xmllistname)) < 0) + { + flog(LOG_WARNING, "could not create XML file list tempfile: %s", strerror(errno)); + free(xmllistname); + xmllistname = NULL; + return; + } + if((fs = fdopen(fd, "w")) == NULL) + { + flog(LOG_WARNING, "could not fdopen XML list fd %i: %s", fd, strerror(errno)); + unlink(xmllistname); + free(xmllistname); + xmllistname = NULL; + close(fd); + return; + } + fprintf(fs, "\r\n"); + for(i = 0; i < sizeof(cidbuf) - 1; i++) + cidbuf[i] = (rand() % ('Z' - 'A' + 1)) + 'A'; + cidbuf[i] = 0; + fprintf(fs, "\r\n", cidbuf, DCIDFULL); + + node = shareroot->child; + lev = 0; + while(1) + { + if((namebuf = icswcstombs(escapexml(node->name), "UTF-8", NULL)) != NULL) + { + for(i = 0; i < lev; i++) + fputc('\t', fs); + if(node->child != NULL) + { + fprintf(fs, "\r\n", namebuf); + node = node->child; + lev++; + continue; + } else { + fprintf(fs, "size); + if(node->f.b.hastth) + { + hashbuf = base32encode(node->hashtth, 24); + fprintf(fs, " TTH=\"%.39s\"", hashbuf); + free(hashbuf); + } + fprintf(fs, "/>\r\n"); + } + while(node->next == NULL) + { + node = node->parent; + if(node == shareroot) + { + break; + } else { + lev--; + for(i = 0; i < lev; i++) + fputc('\t', fs); + fprintf(fs, "\r\n"); + } + } + if(node == shareroot) + break; + node = node->next; + } + } + +#ifdef DCPP_MASQUERADE + fprintf(fs, ""); +#else + fprintf(fs, "\r\n"); +#endif + fclose(fs); +} + +static void updatexmlbz2list(void) +{ + int err, fd; + FILE *in; + FILE *real; + BZFILE *out; + char buf[1024]; + size_t bufdata; + + if(xmllistname == NULL) + return; + if((in = fopen(xmllistname, "r")) == NULL) + { + flog(LOG_WARNING, "could not open XML file list for bzipping: %s", strerror(errno)); + return; + } + if(xmlbz2listname != NULL) + { + unlink(xmlbz2listname); + free(xmlbz2listname); + } + xmlbz2listname = sstrdup("/tmp/dc-filelist-dcxmlbz2-XXXXXX"); + if((fd = mkstemp(xmlbz2listname)) < 0) + { + flog(LOG_WARNING, "could not create bzipped XML file list tempfile: %s", strerror(errno)); + free(xmlbz2listname); + xmlbz2listname = NULL; + fclose(in); + return; + } + if((real = fdopen(fd, "w")) == NULL) + { + flog(LOG_WARNING, "could not fdopen bzipped XML list fd %i: %s", fd, strerror(errno)); + close(fd); + unlink(xmlbz2listname); + free(xmlbz2listname); + xmlbz2listname = NULL; + fclose(in); + return; + } + out = BZ2_bzWriteOpen(&err, real, 9, 0, 0); + if(err != BZ_OK) + { + flog(LOG_WARNING, "could not open bzip2 stream from XML list"); + fclose(real); + unlink(xmlbz2listname); + free(xmlbz2listname); + xmlbz2listname = NULL; + fclose(in); + return; + } + while(!feof(in)) + { + bufdata = fread(buf, 1, sizeof(buf), in); + BZ2_bzWrite(&err, out, buf, bufdata); + } + fclose(in); + BZ2_bzWriteClose(&err, out, 0, NULL, NULL); + fclose(real); +} + +static int shareupdate(unsigned long long uusharesize, void *data) +{ + updatehmlist(); + updatexmllist(); + updatexmlbz2list(); + return(0); +} + +static void dispatchcommand(struct qcommand *qcmd, struct command *cmdlist, struct socket *sk, void *data) +{ + char *p; + struct command *cmd; + + if((p = strchr(qcmd->string, ' ')) != NULL) + *(p++) = 0; + for(cmd = cmdlist; cmd->handler != NULL; cmd++) + { + if(!strcmp(cmd->name, qcmd->string)) + break; + } + if(cmd->handler != NULL) + cmd->handler(sk, data, qcmd->string, p); +/* + else + flog(LOG_DEBUG, "Unimplemented DC command: %s \"%s\"", qcmd->string, p?p:"noargs"); +*/ +} + +static int run(void) +{ + struct fnetnode *fn, *nextfn; + struct dchub *hub; + struct dcpeer *peer, *nextpeer; + struct qcommand *qcmd; + int ret; + + ret = 0; + for(fn = fnetnodes; fn != NULL; fn = nextfn) + { + nextfn = fn->next; + if(fn->fnet != &dcnet) + continue; + if(fn->data == NULL) + continue; + hub = (struct dchub *)fn->data; + if((qcmd = ulqcmd(&hub->queue)) != NULL) + { + if(*qcmd->string == '$') + { + if((fn->sk != NULL) && (fn->sk->state == SOCK_EST)) + dispatchcommand(qcmd, hubcmds, fn->sk, fn); + } else if(*qcmd->string != 0) { + hubrecvchat(fn->sk, fn, NULL, qcmd->string); + } + freeqcmd(qcmd); + ret = 1; + break; + } + } + for(peer = peers; peer != NULL; peer = nextpeer) + { + nextpeer = peer->next; + if((qcmd = ulqcmd(&peer->queue)) != NULL) + { + if(*qcmd->string == '$') + dispatchcommand(qcmd, peercmds, peer->sk, peer); + freeqcmd(qcmd); + ret = 1; + break; + } + } + return(ret); +} + +static void preinit(int hup) +{ + if(hup) + return; + regfnet(&dcnet); +} + +static int updateudpport(struct configvar *var, void *uudata) +{ + struct sockaddr_in addr; + struct socket *newsock; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(var->val.num); + if((newsock = netcsdgram((struct sockaddr *)&addr, sizeof(addr))) == NULL) + { + flog(LOG_WARNING, "could not create new DC UDP socket, reverting to old: %s", strerror(errno)); + return(0); + } + newsock->readcb = udpread; + if(udpsock != NULL) + putsock(udpsock); + udpsock = newsock; + return(0); +} + +static int updatetcpport(struct configvar *var, void *uudata) +{ + struct sockaddr_in addr; + struct socket *newsock; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(var->val.num); + if((newsock = netcslisten(SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr), peeraccept, NULL)) == NULL) + flog(LOG_INFO, "could not listen to a remote address, going into passive mode"); + if(tcpsock != NULL) + putsock(tcpsock); + tcpsock = newsock; + return(0); +} + +static int init(int hup) +{ + struct sockaddr_in addr; + + if(!hup) + { + GCBREG(sharechangecb, shareupdate, NULL); + if(udpsock != NULL) + putsock(udpsock); + if(tcpsock != NULL) + putsock(tcpsock); + addr.sin_family = AF_INET; + memset(&addr.sin_addr, 0, sizeof(addr.sin_addr)); + addr.sin_port = htons(confgetint("dc", "udpport")); + if((udpsock = netcsdgram((struct sockaddr *)&addr, sizeof(addr))) == NULL) + { + flog(LOG_CRIT, "could not create DC UDP socket: %s", strerror(errno)); + return(1); + } + udpsock->readcb = udpread; + addr.sin_port = htons(confgetint("dc", "tcpport")); + if((tcpsock = netcslisten(SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr), peeraccept, NULL)) == NULL) + flog(LOG_INFO, "could not listen to a remote address, going into passive mode"); + CBREG(confgetvar("dc", "udpport"), conf_update, updateudpport, NULL, NULL); + CBREG(confgetvar("dc", "tcpport"), conf_update, updatetcpport, NULL, NULL); + CBREG(confgetvar("net", "mode"), conf_update, updatetcpport, NULL, NULL); + } + return(0); +} + +static void terminate(void) +{ + if(hmlistname != NULL) + { + unlink(hmlistname); + free(hmlistname); + } + if(xmllistname != NULL) + { + unlink(xmllistname); + free(xmllistname); + } + if(xmlbz2listname != NULL) + { + unlink(xmlbz2listname); + free(xmlbz2listname); + } +} + +static struct configvar myvars[] = +{ + {CONF_VAR_STRING, "desc", {.str = L""}}, + {CONF_VAR_STRING, "speedstring", {.str = L"LAN(T1)"}}, + {CONF_VAR_STRING, "email", {.str = L"spam@spam.org"}}, + {CONF_VAR_INT, "udpport", {.num = 0}}, + {CONF_VAR_INT, "tcpport", {.num = 0}}, + {CONF_VAR_END} +}; + +static struct module me = +{ + .conf = + { + .vars = myvars + }, + .preinit = preinit, + .init = init, + .run = run, + .terminate = terminate, + .name = "dc" +}; + +MODULE(me) diff --git a/daemon/log.c b/daemon/log.c new file mode 100644 index 0000000..49a444c --- /dev/null +++ b/daemon/log.c @@ -0,0 +1,75 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "utils.h" + +int logtostderr, logtosyslog; +int syslogfac = LOG_DAEMON; +static int inited = 0; + +void flog(int priority, char *format, ...) +{ + va_list args; + char *b; + struct iovec iov[2]; + + va_start(args, format); + if((b = vsprintf2(format, args)) == NULL) + { + if(logtostderr) + fputs("No memory available for logging\n", stderr); + if(logtosyslog) + syslog(LOG_CRIT, "No memory available for logging"); + } + va_end(args); + if(logtostderr) + { + iov[0].iov_base = b; + iov[0].iov_len = strlen(b); + iov[1].iov_base = "\n"; + iov[1].iov_len = 1; + writev(2, iov, 2); + } + if(logtosyslog) + syslog(priority, "%s", b); + free(b); +} + +void initlog(void) +{ + if(inited) + { + closelog(); + openlog("doldacond", LOG_PID, syslogfac); + } else { + openlog("doldacond", LOG_PID, syslogfac); + logtostderr = 1; + logtosyslog = 0; + inited = 1; + } +} diff --git a/daemon/log.h b/daemon/log.h new file mode 100644 index 0000000..b411418 --- /dev/null +++ b/daemon/log.h @@ -0,0 +1,36 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _LOG_H +#define _LOG_H + +#include + +#define LOGOOM(size) flog(LOG_CRIT, "%s (%s:%i): out of memory (alloc %i)", __FUNCTION__, __FILE__, __LINE__, (size)) + +extern int logtostderr, logtosyslog; +extern int syslogfac; + +void flog(int priority, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif +; +void initlog(void); + +#endif diff --git a/daemon/main.c b/daemon/main.c new file mode 100644 index 0000000..299fd0d --- /dev/null +++ b/daemon/main.c @@ -0,0 +1,558 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "utils.h" +#include "log.h" +#include "conf.h" +#include "module.h" +#include "net.h" +#include "client.h" +#include "sysevents.h" +#include "auth.h" + +struct module *modchain = NULL; +static struct timer *timers = NULL; +static struct child *children = NULL; +volatile int running; +volatile int reinit; +static volatile int childrendone = 0; + +struct timer *timercallback(double at, void (*func)(int, void *), void *data) +{ + struct timer *new; + + new = smalloc(sizeof(*new)); + new->at = at; + new->func = func; + new->data = data; + new->next = timers; + new->prev = NULL; + if(timers != NULL) + timers->prev = new; + timers = new; + return(new); +} + +void canceltimer(struct timer *timer) +{ + if(timer->next != NULL) + timer->next->prev = timer->prev; + if(timer->prev != NULL) + timer->prev->next = timer->next; + if(timer == timers) + timers = timer->next; + timer->func(1, timer->data); + free(timer); +} + +void childcallback(pid_t pid, void (*func)(pid_t, int, void *), void *data) +{ + struct child *new; + + new = smalloc(sizeof(*new)); + new->pid = pid; + new->callback = func; + new->data = data; + new->finished = 0; + new->prev = NULL; + new->next = children; + if(children != NULL) + children->prev = new; + children = new; +} + +static void preinit(int hup) +{ + struct module *mod; + + for(mod = modchain; mod != NULL; mod = mod->next) + { + if(mod->preinit) + mod->preinit(hup); + if(!hup && ((mod->conf.vars != NULL) || (mod->conf.cmds != NULL))) + confregmod(&mod->conf); + } +} + +static void init(int hup) +{ + struct module *mod; + + for(mod = modchain; mod != NULL; mod = mod->next) + { + if(mod->init && mod->init(hup)) + { + flog(LOG_CRIT, "initialization of \"%s\" failed", mod->name); + exit(1); + } + } +} + +static void terminate(void) +{ + struct module *mod; + + for(mod = modchain; mod != NULL; mod = mod->next) + { + if(mod->terminate) + mod->terminate(); + } +} + +static void handler(int signum) +{ + pid_t pid; + int status; + struct child *child; + FILE *dumpfile; + extern int numfnetnodes, numtransfers, numdcpeers; + + switch(signum) + { + case SIGHUP: + reinit = 1; + break; + case SIGINT: + case SIGTERM: + running = 0; + break; + case SIGCHLD: + while((pid = waitpid(-1, &status, WNOHANG)) > 0) + { + for(child = children; child != NULL; child = child->next) + { + if(child->pid == pid) + { + child->finished = 1; + child->status = status; + } + } + childrendone = 1; + } + break; + case SIGUSR1: + flog(LOG_NOTICE, "forking and dumping core upon SIGUSR1"); + if(fork() == 0) + abort(); + break; + case SIGUSR2: + flog(LOG_NOTICE, "dumping memstats to /tmp/dc-mem upon SIGUSR2"); + if((dumpfile = fopen("/tmp/dc-mem", "w")) == NULL) { + flog(LOG_ERR, "could not dump stats: %s", strerror(errno)); + break; + } + fprintf(dumpfile, "%i %i %i\n", numfnetnodes, numtransfers, numdcpeers); + fclose(dumpfile); + break; + } +} + +pid_t forksess(uid_t user, struct authhandle *auth, void (*ccbfunc)(pid_t, int, void *), void *data, ...) +{ + int i, o; + int cpipe[2]; + struct + { + int tfd; + int fd; + } *files; + int maxfd, type, numfiles; + int acc; + int *ibuf; + struct passwd *pwent; + pid_t pid; + char *buf; + va_list args; + sigset_t sigset; + int ret, status; + + if((pwent = getpwuid(user)) == NULL) + { + flog(LOG_WARNING, "no passwd entry for uid %i, cannot fork session", user); + errno = EACCES; + return(-1); + } + if((geteuid() != 0) && (user != geteuid())) + { + flog(LOG_WARNING, "cannot fork non-owning session when not running as root (EUID is %i, target UID is %i)", geteuid(), user); + errno = EPERM; + return(-1); + } + va_start(args, data); + numfiles = 0; + files = NULL; + maxfd = 0; + while((type = va_arg(args, int)) != FD_END) + { + files = srealloc(files, sizeof(*files) * (numfiles + 1)); + files[numfiles].fd = va_arg(args, int); + if(files[numfiles].fd > maxfd) + maxfd = files[numfiles].fd; + acc = va_arg(args, int); + if(type == FD_PIPE) + { + if(pipe(cpipe) < 0) + { + flog(LOG_CRIT, "could not create pipe(!): %s", strerror(errno)); + for(i = 0; i < numfiles; i++) + close(files[i].tfd); + return(-1); + } + ibuf = va_arg(args, int *); + if(acc == O_WRONLY) + { + *ibuf = cpipe[1]; + files[numfiles].tfd = cpipe[0]; + } else { + *ibuf = cpipe[0]; + files[numfiles].tfd = cpipe[1]; + } + } else if(type == FD_FILE) { + buf = va_arg(args, char *); + if((files[numfiles].tfd = open(buf, acc)) < 0) + { + flog(LOG_CRIT, "could not open file \"%s\": %s", buf, strerror(errno)); + for(i = 0; i < numfiles; i++) + close(files[i].tfd); + return(-1); + } + } + if(files[numfiles].tfd > maxfd) + maxfd = files[numfiles].tfd; + numfiles++; + } + va_end(args); + sigemptyset(&sigset); + sigaddset(&sigset, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigset, NULL); + if((pid = fork()) < 0) + { + flog(LOG_WARNING, "could not fork(!) in forksess(): %s", strerror(errno)); + for(i = 0; i < numfiles; i++) + close(files[i].tfd); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + } + if(pid == 0) + { + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + signal(SIGPIPE, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGHUP, SIG_DFL); + for(i = 0; i < numfiles; i++) + { + if(dup2(files[i].tfd, maxfd + i + 1) < 0) + exit(127); + files[i].tfd = maxfd + i + 1; + } + for(i = 0; i < numfiles; i++) + { + if(dup2(files[i].tfd, files[i].fd) < 0) + exit(127); + } + initlog(); + for(i = 0; i < FD_SETSIZE; i++) + { + if(i <= maxfd) + { + for(o = 0; o < numfiles; o++) + { + if(i == files[o].fd) + break; + } + if(o == numfiles) + close(i); + } else { + close(i); + } + } + setpgrp(); + signal(SIGHUP, SIG_IGN); + errno = 0; + if((authopensess(auth)) != AUTH_SUCCESS) + { + flog(LOG_WARNING, "could not open session for user %s: %s", pwent->pw_name, (errno == 0)?"Unknown error - should be logged above":strerror(errno)); + exit(127); + } + if((pid = fork()) < 0) + { + authclosesess(auth); + exit(127); + } + if(pid == 0) + { + if(geteuid() == 0) + { + if(initgroups(pwent->pw_name, pwent->pw_gid)) + { + flog(LOG_WARNING, "could not initgroups: %s", strerror(errno)); + exit(127); + } + if(setgid(pwent->pw_gid)) + { + flog(LOG_WARNING, "could not setgid: %s", strerror(errno)); + exit(127); + } + if(setuid(pwent->pw_uid)) + { + flog(LOG_WARNING, "could not setuid: %s", strerror(errno)); + exit(127); + } + } + putenv(sprintf2("HOME=%s", pwent->pw_dir)); + putenv(sprintf2("SHELL=%s", pwent->pw_shell)); + putenv(sprintf2("USER=%s", pwent->pw_name)); + putenv(sprintf2("LOGNAME=%s", pwent->pw_name)); + putenv(sprintf2("PATH=%s/bin:/usr/local/bin:/bin:/usr/bin", pwent->pw_dir)); + chdir(pwent->pw_dir); + return(0); + } + for(i = 0; i < numfiles; i++) + close(files[i].fd); + while(((ret = waitpid(pid, &status, 0)) != pid) && (ret >= 0)); + authclosesess(auth); + if(ret < 0) + { + flog(LOG_WARNING, "waitpid(%i) said \"%s\"", pid, strerror(errno)); + exit(127); + } + if(!WIFEXITED(status)) + exit(127); + exit(WEXITSTATUS(status)); + } + for(i = 0; i < numfiles; i++) + close(files[i].tfd); + if(files != NULL) + free(files); + if(ccbfunc != NULL) + childcallback(pid, ccbfunc, data); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + return(pid); +} + +int main(int argc, char **argv) +{ + int c; + int nofork; + char *configfile; + char *pidfile; + FILE *pfstream, *confstream; + int delay; + struct module *mod; + struct timer *timer, *ntimer; + struct child *child; + double now; + + nofork = 0; + syslogfac = LOG_DAEMON; + configfile = NULL; + pidfile = NULL; + while((c = getopt(argc, argv, "p:C:f:hn")) != -1) + { + switch(c) + { + case 'p': + pidfile = optarg; + break; + case 'C': + configfile = optarg; + break; + case 'f': + if(!strcmp(optarg, "auth")) + syslogfac = LOG_AUTH; + else if(!strcmp(optarg, "authpriv")) + syslogfac = LOG_AUTHPRIV; + else if(!strcmp(optarg, "cron")) + syslogfac = LOG_CRON; + else if(!strcmp(optarg, "daemon")) + syslogfac = LOG_DAEMON; + else if(!strcmp(optarg, "ftp")) + syslogfac = LOG_FTP; + else if(!strcmp(optarg, "kern")) + syslogfac = LOG_KERN; + else if(!strcmp(optarg, "lpr")) + syslogfac = LOG_LPR; + else if(!strcmp(optarg, "mail")) + syslogfac = LOG_MAIL; + else if(!strcmp(optarg, "news")) + syslogfac = LOG_NEWS; + else if(!strcmp(optarg, "syslog")) + syslogfac = LOG_SYSLOG; + else if(!strcmp(optarg, "user")) + syslogfac = LOG_USER; + else if(!strcmp(optarg, "uucp")) + syslogfac = LOG_UUCP; + else if(!strncmp(optarg, "local", 5) && (strlen(optarg) == 6)) + syslogfac = LOG_LOCAL0 + (optarg[5] - '0'); + else + fprintf(stderr, "unknown syslog facility %s, using daemon\n", optarg); + break; + case 'n': + nofork = 1; + break; + case 'h': + case ':': + case '?': + default: + printf("usage: doldacond [-hn] [-C configfile] [-p pidfile] [-f facility]\n"); + exit(c != 'h'); + } + } + setlocale(LC_ALL, ""); + initlog(); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, handler); + signal(SIGINT, handler); + signal(SIGTERM, handler); + signal(SIGCHLD, handler); + signal(SIGUSR1, handler); + signal(SIGUSR2, handler); + preinit(0); + if(configfile == NULL) + { + if((configfile = findconfigfile()) == NULL) + { + flog(LOG_CRIT, "could not find a configuration file"); + exit(1); + } + } + pfstream = NULL; + if(pidfile != NULL) + { + if((pfstream = fopen(pidfile, "w")) == NULL) + { + flog(LOG_CRIT, "could not open specified PID file %s: %s", pidfile, strerror(errno)); + exit(1); + } + } + if((confstream = fopen(configfile, "r")) == NULL) + { + flog(LOG_CRIT, "could not open configuration file %s: %s", configfile, strerror(errno)); + exit(1); + } + readconfig(confstream); + fclose(confstream); + init(0); + if(!nofork) + { + logtosyslog = 1; + daemon(0, 0); + flog(LOG_INFO, "daemonized"); + logtostderr = 0; + } + if(pfstream != NULL) { + fprintf(pfstream, "%i\n", getpid()); + fclose(pfstream); + } + running = 1; + reinit = 0; + while(running) + { + if(reinit) + { + if((confstream = fopen(configfile, "r")) == NULL) + { + flog(LOG_ERR, "could not open configuration file %s: %s (ignoring HUP)", configfile, strerror(errno)); + } else { + preinit(1); + readconfig(confstream); + fclose(confstream); + init(1); + } + reinit = 0; + } + delay = 1000; /* -1; */ + for(mod = modchain; mod != NULL; mod = mod->next) + { + if(mod->run && mod->run()) + delay = 0; + } + if(!running) + delay = 0; + if(delay != 0) + { + now = ntime(); + for(timer = timers; timer != NULL; timer = timer->next) + { + if((delay == -1) || ((int)((timer->at - now) * 1000.0) < delay)) + delay = (int)((timer->at - now) * 1000.0); + } + } + if(childrendone) + { + delay = 0; + childrendone = 0; + } + pollsocks(delay); + now = ntime(); + for(timer = timers; timer != NULL; timer = ntimer) + { + ntimer = timer->next; + if(now < timer->at) + continue; + if(timer->prev != NULL) + timer->prev->next = timer->next; + if(timer->next != NULL) + timer->next->prev = timer->prev; + if(timer == timers) + timers = timer->next; + timer->func(0, timer->data); + free(timer); + } + do + { + for(child = children; child != NULL; child = child->next) + { + if(child->finished) + { + child->callback(child->pid, child->status, child->data); + if(child == children) + children = child->next; + if(child->prev != NULL) + child->prev->next = child->next; + if(child->next != NULL) + child->next->prev = child->prev; + free(child); + break; + } + } + } while(child != NULL); + } + flog(LOG_INFO, "terminating..."); + terminate(); + return(0); +} diff --git a/daemon/module.h b/daemon/module.h new file mode 100644 index 0000000..385f6c9 --- /dev/null +++ b/daemon/module.h @@ -0,0 +1,65 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _MODULE_H +#define _MODULE_H + +#include + +#include "conf.h" + +struct module +{ + struct module *next; + char *name; + /* Called before the configuration file is read. Must either + * succeed or call exit. Note that it is called both at startup + * and when SIGHUP has been received (then with hup = 1). */ + void (*preinit)(int hup); + /* Called when the configuration file has been read. Return zero + * on success and non-zero on failure. Note, as with preinit, that + * it can be called both at startup and after SIHUP. */ + int (*init)(int hup); + /* Called every "cycle". Its return value determines whether the + * module still has work to do, and thus determines whether the + * next pollsocks should block or not. Return non-zero whenever + * the module has more work to do. */ + int (*run)(void); + /* Called when the daemon is shutting down. */ + void (*terminate)(void); + struct configmod conf; +}; + +#define MODULE(mod) \ +static void __attribute__ ((constructor)) __regmod(void) \ +{ \ + extern struct module *modchain; \ + \ + if(mod.name == NULL) \ + { \ + fprintf(stderr, "module at %p has no name", &mod); \ + exit(1); \ + } \ + mod.conf.name = mod.name; \ + mod.next = modchain; \ + modchain = &mod; \ +} + +void regmod(struct module *); + +#endif diff --git a/daemon/net.c b/daemon/net.c new file mode 100644 index 0000000..f16441f --- /dev/null +++ b/daemon/net.c @@ -0,0 +1,1124 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +*/ +/* XXX: Implement SOCKS proxyability */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LINUX_SOCKIOS_H +#include +#endif +#include +#include + +#include "conf.h" +#include "net.h" +#include "module.h" +#include "log.h" +#include "utils.h" +#include "sysevents.h" + +static struct configvar myvars[] = +{ + /* 0 = Direct mode, 1 = Passive mode, 2 = SOCKS proxy */ + {CONF_VAR_INT, "mode", {.num = 0}}, + /* Only for direct mode */ + {CONF_VAR_IPV4, "visibleipv4", {.ipv4 = {0}}}, + {CONF_VAR_STRING, "publicif", {.str = L""}}, + {CONF_VAR_END} +}; + +static struct socket *sockets = NULL; +int numsocks = 0; + +/* XXX: Get autoconf for all this... */ +int getpublicaddr(int af, struct sockaddr **addr, socklen_t *lenbuf) +{ + struct sockaddr_in *ipv4; + struct configvar *var; + void *bufend; + int sock; + struct ifconf conf; + struct ifreq *ifr, req; + char *pif; + + if(af == AF_INET) + { + var = confgetvar("net", "visibleipv4"); + if(var->val.ipv4.s_addr != 0) + { + ipv4 = smalloc(sizeof(*ipv4)); + ipv4->sin_family = AF_INET; + ipv4->sin_addr.s_addr = var->val.ipv4.s_addr; + *addr = (struct sockaddr *)ipv4; + *lenbuf = sizeof(*ipv4); + return(0); + } + if((pif = icwcstombs(confgetstr("net", "publicif"), NULL)) == NULL) + { + flog(LOG_ERR, "could not convert net.publicif into local charset: %s", strerror(errno)); + return(-1); + } + if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return(-1); + conf.ifc_buf = smalloc(conf.ifc_len = 65536); + if(ioctl(sock, SIOCGIFCONF, &conf) < 0) + { + free(conf.ifc_buf); + close(sock); + return(-1); + } + bufend = ((char *)conf.ifc_buf) + conf.ifc_len; + ipv4 = NULL; + for(ifr = conf.ifc_ifcu.ifcu_req; (void *)ifr < bufend; ifr++) + { + memset(&req, 0, sizeof(req)); + memcpy(req.ifr_name, ifr->ifr_name, sizeof(ifr->ifr_name)); + if(ioctl(sock, SIOCGIFFLAGS, &req) < 0) + { + free(conf.ifc_buf); + close(sock); + return(-1); + } + if(!(req.ifr_flags & IFF_UP)) + continue; + if(ifr->ifr_addr.sa_family == AF_INET) + { + if(ntohl(((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr) == 0x7f000001) + continue; + if(ipv4 == NULL) + { + ipv4 = smalloc(sizeof(*ipv4)); + memcpy(ipv4, &ifr->ifr_addr, sizeof(ifr->ifr_addr)); + } else { + free(ipv4); + flog(LOG_WARNING, "could not locate an unambiguous interface for determining your public IP address - set net.publicif"); + errno = ENFILE; /* XXX: There's no appropriate one for this... */ + return(-1); + } + } + } + close(sock); + if(ipv4 != NULL) + { + *addr = (struct sockaddr *)ipv4; + *lenbuf = sizeof(*ipv4); + return(0); + } + errno = ENETDOWN; + return(-1); + } + errno = EPFNOSUPPORT; + return(-1); +} + +static struct socket *newsock(int type) +{ + struct socket *new; + + new = smalloc(sizeof(*new)); + new->refcount = 2; + new->fd = -1; + new->isrealsocket = 1; + new->family = -1; + new->tos = 0; + new->type = type; + new->state = -1; + new->ignread = 0; + new->close = 0; + new->remote = NULL; + new->remotelen = 0; + switch(type) + { + case SOCK_STREAM: + new->outbuf.s.buf = NULL; + new->outbuf.s.bufsize = 0; + new->outbuf.s.datasize = 0; + new->inbuf.s.buf = NULL; + new->inbuf.s.bufsize = 0; + new->inbuf.s.datasize = 0; + break; + case SOCK_DGRAM: + new->outbuf.d.f = new->outbuf.d.l = NULL; + new->inbuf.d.f = new->inbuf.d.l = NULL; + break; + } + new->conncb = NULL; + new->errcb = NULL; + new->readcb = NULL; + new->writecb = NULL; + new->acceptcb = NULL; + new->next = sockets; + new->prev = NULL; + if(sockets != NULL) + sockets->prev = new; + sockets = new; + numsocks++; + return(new); +} + +static struct socket *mksock(int domain, int type) +{ + int fd; + struct socket *new; + + if((fd = socket(domain, type, 0)) < 0) + { + flog(LOG_CRIT, "could not create socket: %s", strerror(errno)); + return(NULL); + } + new = newsock(type); + new->fd = fd; + new->family = domain; + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + return(new); +} + +struct socket *wrapsock(int fd) +{ + struct socket *new; + + new = newsock(SOCK_STREAM); + new->fd = fd; + new->state = SOCK_EST; + new->isrealsocket = 0; + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + return(new); +} + +static void unlinksock(struct socket *sk) +{ + if(sk->prev != NULL) + sk->prev->next = sk->next; + if(sk->next != NULL) + sk->next->prev = sk->prev; + if(sk == sockets) + sockets = sk->next; + putsock(sk); + numsocks--; +} + +void getsock(struct socket *sk) +{ + sk->refcount++; +} + +void putsock(struct socket *sk) +{ + struct dgrambuf *buf; + + if(--(sk->refcount) == 0) + { + switch(sk->type) + { + case SOCK_STREAM: + if(sk->outbuf.s.buf != NULL) + free(sk->outbuf.s.buf); + if(sk->inbuf.s.buf != NULL) + free(sk->inbuf.s.buf); + break; + case SOCK_DGRAM: + while((buf = sk->outbuf.d.f) != NULL) + { + sk->outbuf.d.f = buf->next; + free(buf->data); + free(buf); + } + while((buf = sk->inbuf.d.f) != NULL) + { + sk->inbuf.d.f = buf->next; + free(buf->data); + free(buf); + } + break; + } + if(sk->fd >= 0) + close(sk->fd); + if(sk->remote != NULL) + free(sk->remote); + free(sk); + } +} + +void *sockgetinbuf(struct socket *sk, size_t *size) +{ + void *buf; + struct dgrambuf *dbuf; + + switch(sk->type) + { + case SOCK_STREAM: + if((sk->inbuf.s.buf == NULL) || (sk->inbuf.s.datasize == 0)) + { + *size = 0; + return(NULL); + } + buf = sk->inbuf.s.buf; + *size = sk->inbuf.s.datasize; + sk->inbuf.s.buf = NULL; + sk->inbuf.s.bufsize = sk->inbuf.s.datasize = 0; + return(buf); + case SOCK_DGRAM: + if((dbuf = sk->inbuf.d.f) == NULL) + return(NULL); + sk->inbuf.d.f = dbuf->next; + if(dbuf->next == NULL) + sk->inbuf.d.l = NULL; + buf = dbuf->data; + *size = dbuf->size; + free(dbuf->addr); + free(dbuf); + return(buf); + } + return(NULL); +} + +static void sockrecv(struct socket *sk) +{ + int ret, inq; + struct dgrambuf *dbuf; + + switch(sk->type) + { + case SOCK_STREAM: +#if defined(HAVE_LINUX_SOCKIOS_H) && defined(SIOCINQ) + /* SIOCINQ is Linux-specific AFAIK, but I really have no idea + * how to read the inqueue size on other OSs */ + if(ioctl(sk->fd, SIOCINQ, &inq)) + { + /* I don't really know what could go wrong here, so let's + * assume it's transient. */ + flog(LOG_WARNING, "SIOCINQ return %s on socket %i, falling back to 2048 bytes", strerror(errno), sk->fd); + inq = 2048; + } +#else + inq = 2048; +#endif + if(inq > 65536) + inq = 65536; + sizebuf(&sk->inbuf.s.buf, &sk->inbuf.s.bufsize, sk->inbuf.s.datasize + inq, 1, 1); + ret = read(sk->fd, sk->inbuf.s.buf + sk->inbuf.s.datasize, inq); + if(ret < 0) + { + if((errno == EINTR) || (errno == EAGAIN)) + return; + if(sk->errcb != NULL) + sk->errcb(sk, errno, sk->data); + closesock(sk); + return; + } + if(ret == 0) + { + if(sk->errcb != NULL) + sk->errcb(sk, 0, sk->data); + closesock(sk); + return; + } + sk->inbuf.s.datasize += ret; + if(sk->readcb != NULL) + sk->readcb(sk, sk->data); + break; + case SOCK_DGRAM: + if(ioctl(sk->fd, SIOCINQ, &inq)) + { + /* I don't really know what could go wrong here, so let's + * assume it's transient. */ + flog(LOG_WARNING, "SIOCINQ return %s on socket %i", strerror(errno), sk->fd); + return; + } + dbuf = smalloc(sizeof(*dbuf)); + dbuf->data = smalloc(inq); + dbuf->addr = smalloc(dbuf->addrlen = sizeof(struct sockaddr_storage)); + ret = recvfrom(sk->fd, dbuf->data, inq, 0, dbuf->addr, &dbuf->addrlen); + if(ret < 0) + { + free(dbuf->addr); + free(dbuf->data); + free(dbuf); + if((errno == EINTR) || (errno == EAGAIN)) + return; + if(sk->errcb != NULL) + sk->errcb(sk, errno, sk->data); + closesock(sk); + return; + } + /* On UDP/IPv[46], ret == 0 doesn't mean EOF (since UDP can't + * have EOF), but rather an empty packet. I don't know if any + * other potential DGRAM protocols might have an EOF + * condition, so let's play safe. */ + if(ret == 0) + { + free(dbuf->addr); + free(dbuf->data); + free(dbuf); + if(!((sk->family == AF_INET) || (sk->family == AF_INET6))) + { + if(sk->errcb != NULL) + sk->errcb(sk, 0, sk->data); + closesock(sk); + } + return; + } + dbuf->addr = srealloc(dbuf->addr, dbuf->addrlen); + dbuf->data = srealloc(dbuf->data, dbuf->size = ret); + dbuf->next = NULL; + if(sk->inbuf.d.l != NULL) + sk->inbuf.d.l->next = dbuf; + else + sk->inbuf.d.f = dbuf; + sk->inbuf.d.l = dbuf; + if(sk->readcb != NULL) + sk->readcb(sk, sk->data); + break; + } +} + +static void sockflush(struct socket *sk) +{ + int ret; + struct dgrambuf *dbuf; + + switch(sk->type) + { + case SOCK_STREAM: + if(sk->isrealsocket) + ret = send(sk->fd, sk->outbuf.s.buf, sk->outbuf.s.datasize, MSG_DONTWAIT | MSG_NOSIGNAL); + else + ret = write(sk->fd, sk->outbuf.s.buf, sk->outbuf.s.datasize); + if(ret < 0) + { + /* For now, assume transient error, since + * the socket is polled for errors */ + break; + } + if(ret > 0) + { + memmove(sk->outbuf.s.buf, ((char *)sk->outbuf.s.buf) + ret, sk->outbuf.s.datasize -= ret); + if(sk->writecb != NULL) + sk->writecb(sk, sk->data); + } + break; + case SOCK_DGRAM: + dbuf = sk->outbuf.d.f; + if((sk->outbuf.d.f = dbuf->next) == NULL) + sk->outbuf.d.l = NULL; + sendto(sk->fd, dbuf->data, dbuf->size, MSG_DONTWAIT | MSG_NOSIGNAL, dbuf->addr, dbuf->addrlen); + free(dbuf->data); + free(dbuf->addr); + free(dbuf); + if(sk->writecb != NULL) + sk->writecb(sk, sk->data); + break; + } +} + +void closesock(struct socket *sk) +{ + sk->state = SOCK_STL; + close(sk->fd); + sk->fd = -1; + sk->close = 0; +} + +void sockqueue(struct socket *sk, void *data, size_t size) +{ + struct dgrambuf *new; + + if(sk->state == SOCK_STL) + return; + switch(sk->type) + { + case SOCK_STREAM: + sizebuf(&(sk->outbuf.s.buf), &(sk->outbuf.s.bufsize), sk->outbuf.s.datasize + size, 1, 1); + memcpy(sk->outbuf.s.buf + sk->outbuf.s.datasize, data, size); + sk->outbuf.s.datasize += size; + break; + case SOCK_DGRAM: + if(sk->remote == NULL) + return; + new = smalloc(sizeof(*new)); + new->next = NULL; + memcpy(new->data = smalloc(size), data, new->size = size); + memcpy(new->addr = smalloc(sk->remotelen), sk->remote, new->addrlen = sk->remotelen); + if(sk->outbuf.d.l == NULL) + { + sk->outbuf.d.l = sk->outbuf.d.f = new; + } else { + sk->outbuf.d.l->next = new; + sk->outbuf.d.l = new; + } + break; + } +} + +size_t sockgetdatalen(struct socket *sk) +{ + struct dgrambuf *b; + size_t ret; + + switch(sk->type) + { + case SOCK_STREAM: + ret = sk->inbuf.s.datasize; + break; + case SOCK_DGRAM: + ret = 0; + for(b = sk->inbuf.d.f; b != NULL; b = b->next) + ret += b->size; + break; + } + return(ret); +} + +size_t sockqueuesize(struct socket *sk) +{ + struct dgrambuf *b; + size_t ret; + + switch(sk->type) + { + case SOCK_STREAM: + ret = sk->outbuf.s.datasize; + break; + case SOCK_DGRAM: + ret = 0; + for(b = sk->outbuf.d.f; b != NULL; b = b->next) + ret += b->size; + break; + } + return(ret); +} + +struct socket *netcslisten(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data) +{ + struct socket *sk; + + if(confgetint("net", "mode") == 1) + { + errno = EOPNOTSUPP; + return(NULL); + } + /* I don't know if this is actually correct (it probably isn't), + * but since, at on least Linux systems, PF_* are specifically + * #define'd to their AF_* counterparts, it allows for a severely + * smoother implementation. If it breaks something on your + * platform, please tell me so. + */ + if(confgetint("net", "mode") == 0) + { + if((sk = mksock(name->sa_family, type)) == NULL) + return(NULL); + sk->state = SOCK_LST; + if(bind(sk->fd, name, namelen) < 0) + { + putsock(sk); + return(NULL); + } + if(listen(sk->fd, 16) < 0) + { + putsock(sk); + return(NULL); + } + sk->acceptcb = func; + sk->data = data; + return(sk); + } + errno = EOPNOTSUPP; + return(NULL); +} + +/* + * The difference between netcslisten() and netcslistenlocal() is that + * netcslistenlocal() always listens on the local host, instead of + * following proxy/passive mode directions. It is suitable for eg. the + * UI channel, while the file sharing networks should, naturally, use + * netcslisten() instead. +*/ + +struct socket *netcslistenlocal(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data) +{ + struct socket *sk; + + /* I don't know if this is actually correct (it probably isn't), + * but since, at on least Linux systems, PF_* are specifically + * #define'd to their AF_* counterparts, it allows for a severely + * smoother implementation. If it breaks something on your + * platform, please tell me so. + */ + if((sk = mksock(name->sa_family, type)) == NULL) + return(NULL); + sk->state = SOCK_LST; + if(bind(sk->fd, name, namelen) < 0) + { + putsock(sk); + return(NULL); + } + if(listen(sk->fd, 16) < 0) + { + putsock(sk); + return(NULL); + } + sk->acceptcb = func; + sk->data = data; + return(sk); +} + +struct socket *netcsdgram(struct sockaddr *name, socklen_t namelen) +{ + struct socket *sk; + int mode; + + mode = confgetint("net", "mode"); + if((mode == 0) || (mode == 1)) + { + if((sk = mksock(name->sa_family, SOCK_DGRAM)) == NULL) + return(NULL); + if(bind(sk->fd, name, namelen) < 0) + { + putsock(sk); + return(NULL); + } + sk->state = SOCK_EST; + return(sk); + } + errno = EOPNOTSUPP; + return(NULL); +} + +struct socket *netdupsock(struct socket *sk) +{ + struct socket *newsk; + + newsk = newsock(sk->type); + if((newsk->fd = dup(sk->fd)) < 0) + { + flog(LOG_WARNING, "could not dup() socket: %s", strerror(errno)); + putsock(newsk); + return(NULL); + } + newsk->state = sk->state; + newsk->ignread = sk->ignread; + if(sk->remote != NULL) + memcpy(newsk->remote = smalloc(sk->remotelen), sk->remote, newsk->remotelen = sk->remotelen); + return(newsk); +} + +void netdgramconn(struct socket *sk, struct sockaddr *addr, socklen_t addrlen) +{ + if(sk->remote != NULL) + free(sk->remote); + memcpy(sk->remote = smalloc(addrlen), addr, sk->remotelen = addrlen); + sk->ignread = 1; +} + +struct socket *netcsconn(struct sockaddr *addr, socklen_t addrlen, void (*func)(struct socket *, int, void *), void *data) +{ + struct socket *sk; + int mode; + + mode = confgetint("net", "mode"); + if((mode == 0) || (mode == 1)) + { + if((sk = mksock(addr->sa_family, SOCK_STREAM)) == NULL) + return(NULL); + memcpy(sk->remote = smalloc(addrlen), addr, sk->remotelen = addrlen); + if(!connect(sk->fd, addr, addrlen)) + { + sk->state = SOCK_EST; + func(sk, 0, data); + return(sk); + } + if(errno == EINPROGRESS) + { + sk->state = SOCK_SYN; + sk->conncb = func; + sk->data = data; + return(sk); + } + putsock(sk); + return(NULL); + } + errno = EOPNOTSUPP; + return(NULL); +} + +int pollsocks(int timeout) +{ + int i, num, ret, retlen; + int newfd; + struct pollfd *pfds; + struct socket *sk, *next, *newsk; + struct sockaddr_storage ss; + socklen_t sslen; + + pfds = smalloc(sizeof(*pfds) * (num = numsocks)); + for(i = 0, sk = sockets; i < num; sk = sk->next) + { + if(sk->state == SOCK_STL) + { + num--; + continue; + } + pfds[i].fd = sk->fd; + pfds[i].events = 0; + if(!sk->ignread) + pfds[i].events |= POLLIN; + if((sk->state == SOCK_SYN) || (sockqueuesize(sk) > 0)) + pfds[i].events |= POLLOUT; + pfds[i].revents = 0; + i++; + } + ret = poll(pfds, num, timeout); + if(ret < 0) + { + if(errno != EINTR) + { + flog(LOG_CRIT, "pollsocks: poll errored out: %s", strerror(errno)); + /* To avoid CPU hogging in case it's bad, which it + * probably is. */ + sleep(1); + } + free(pfds); + return(1); + } + for(sk = sockets; sk != NULL; sk = next) + { + next = sk->next; + for(i = 0; i < num; i++) + { + if(pfds[i].fd == sk->fd) + break; + } + if(i == num) + continue; + switch(sk->state) + { + case SOCK_LST: + if(pfds[i].revents & POLLIN) + { + sslen = sizeof(ss); + if((newfd = accept(sk->fd, (struct sockaddr *)&ss, &sslen)) < 0) + { + if(sk->errcb != NULL) + sk->errcb(sk, errno, sk->data); + } + newsk = newsock(sk->type); + newsk->fd = newfd; + newsk->family = sk->family; + newsk->state = SOCK_EST; + memcpy(newsk->remote = smalloc(sslen), &ss, sslen); + newsk->remotelen = sslen; + putsock(newsk); + if(sk->acceptcb != NULL) + sk->acceptcb(sk, newsk, sk->data); + } + if(pfds[i].revents & POLLERR) + { + retlen = sizeof(ret); + getsockopt(sk->fd, SOL_SOCKET, SO_ERROR, &ret, &retlen); + if(sk->errcb != NULL) + sk->errcb(sk, ret, sk->data); + continue; + } + break; + case SOCK_SYN: + if(pfds[i].revents & POLLERR) + { + retlen = sizeof(ret); + getsockopt(sk->fd, SOL_SOCKET, SO_ERROR, &ret, &retlen); + if(sk->conncb != NULL) + sk->conncb(sk, ret, sk->data); + closesock(sk); + continue; + } + if(pfds[i].revents & (POLLIN | POLLOUT)) + { + sk->state = SOCK_EST; + if(sk->conncb != NULL) + sk->conncb(sk, 0, sk->data); + } + break; + case SOCK_EST: + if(pfds[i].revents & POLLERR) + { + retlen = sizeof(ret); + getsockopt(sk->fd, SOL_SOCKET, SO_ERROR, &ret, &retlen); + if(sk->errcb != NULL) + sk->errcb(sk, ret, sk->data); + closesock(sk); + continue; + } + if(pfds[i].revents & POLLIN) + sockrecv(sk); + if(pfds[i].revents & POLLOUT) + { + if(sockqueuesize(sk) > 0) + sockflush(sk); + } + break; + } + if(pfds[i].revents & POLLNVAL) + { + flog(LOG_CRIT, "BUG: stale socket struct on fd %i", sk->fd); + sk->state = SOCK_STL; + unlinksock(sk); + continue; + } + if(pfds[i].revents & POLLHUP) + { + if(sk->errcb != NULL) + sk->errcb(sk, 0, sk->data); + closesock(sk); + unlinksock(sk); + continue; + } + } + free(pfds); + for(sk = sockets; sk != NULL; sk = next) + { + next = sk->next; + if(sk->refcount == 1 && (sockqueuesize(sk) == 0)) + { + unlinksock(sk); + continue; + } + if(sk->close && (sockqueuesize(sk) == 0)) + closesock(sk); + if(sk->state == SOCK_STL) + { + unlinksock(sk); + continue; + } + } + return(1); +} + +int socksettos(struct socket *sk, int tos) +{ + if(sk->family == AF_INET) + { + if(setsockopt(sk->fd, SOL_IP, IP_TOS, &tos, sizeof(tos)) < 0) + { + flog(LOG_WARNING, "could not set sock TOS to %i: %s", tos, strerror(errno)); + return(-1); + } + return(0); + } + /* XXX: How does the IPv6 traffic class work? */ + flog(LOG_WARNING, "could not set TOS on sock of family %i", sk->family); + return(1); +} + +struct resolvedata +{ + int fd; + void (*callback)(struct sockaddr *addr, int addrlen, void *data); + void *data; + struct sockaddr_storage addr; + int addrlen; +}; + +static void resolvecb(pid_t pid, int status, struct resolvedata *data) +{ + static char buf[80]; + int ret; + struct sockaddr_in *ipv4; + + if(!status) + { + if((ret = read(data->fd, buf, sizeof(buf))) != 4) + { + errno = ENONET; + data->callback(NULL, 0, data->data); + } else { + ipv4 = (struct sockaddr_in *)&data->addr; + memcpy(&ipv4->sin_addr, buf, 4); + data->callback((struct sockaddr *)ipv4, sizeof(*ipv4), data->data); + } + } else { + errno = ENONET; + data->callback(NULL, 0, data->data); + } + close(data->fd); + free(data); +} + +int netresolve(char *addr, void (*callback)(struct sockaddr *addr, int addrlen, void *data), void *data) +{ + int i; + char *p; + int port; + int pfd[2]; + pid_t child; + struct resolvedata *rdata; + struct sockaddr_in ipv4; + struct hostent *he; + sigset_t sigset; + + /* IPv4 */ + port = -1; + if((p = strchr(addr, ':')) != NULL) + { + *p = 0; + port = atoi(p + 1); + } + ipv4.sin_family = AF_INET; + ipv4.sin_port = htons(port); + if(inet_aton(addr, &ipv4.sin_addr)) + { + callback((struct sockaddr *)&ipv4, sizeof(ipv4), data); + } else { + sigemptyset(&sigset); + sigaddset(&sigset, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigset, NULL); + if((pipe(pfd) < 0) || ((child = fork()) < 0)) + { + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + return(-1); + } + if(child == 0) + { + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + for(i = 3; i < FD_SETSIZE; i++) + { + if(i != pfd[1]) + close(i); + } + signal(SIGALRM, SIG_DFL); + alarm(30); + if((he = gethostbyname(addr)) == NULL) + exit(1); + write(pfd[1], he->h_addr_list[0], 4); + exit(0); + } else { + close(pfd[1]); + fcntl(pfd[0], F_SETFL, fcntl(pfd[0], F_GETFL) | O_NONBLOCK); + rdata = smalloc(sizeof(*rdata)); + rdata->fd = pfd[0]; + rdata->callback = callback; + rdata->data = data; + memcpy(&rdata->addr, &ipv4, rdata->addrlen = sizeof(ipv4)); + childcallback(child, (void (*)(pid_t, int, void *))resolvecb, rdata); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + return(1); + } + } + return(0); +} + +int sockgetlocalname(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf) +{ + socklen_t len; + struct sockaddr_storage name; + + *namebuf = NULL; + if((sk->state == SOCK_STL) || (sk->fd < 0)) + return(-1); + len = sizeof(name); + if(getsockname(sk->fd, (struct sockaddr *)&name, &len) < 0) + { + flog(LOG_ERR, "BUG: alive socket with dead fd in sockgetlocalname"); + return(-1); + } + *namebuf = memcpy(smalloc(len), &name, len); + *lenbuf = len; + return(0); +} + +int sockgetremotename(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf) +{ + socklen_t len; + struct sockaddr_storage name; + struct sockaddr_in *ipv4; + struct sockaddr *pname; + socklen_t pnamelen; + + switch(confgetint("net", "mode")) + { + case 0: + *namebuf = NULL; + if((sk->state == SOCK_STL) || (sk->fd < 0)) + return(-1); + len = sizeof(name); + if(getsockname(sk->fd, (struct sockaddr *)&name, &len) < 0) + { + flog(LOG_ERR, "BUG: alive socket with dead fd in sockgetremotename"); + return(-1); + } + if(name.ss_family == AF_INET) + { + ipv4 = (struct sockaddr_in *)&name; + if(getpublicaddr(AF_INET, &pname, &pnamelen) < 0) + { + flog(LOG_WARNING, "could not determine public IP address - strange things may happen"); + return(-1); + } + ipv4->sin_addr.s_addr = ((struct sockaddr_in *)pname)->sin_addr.s_addr; + free(pname); + } + *namebuf = memcpy(smalloc(len), &name, len); + *lenbuf = len; + return(0); + case 1: + errno = EOPNOTSUPP; + return(-1); + default: + flog(LOG_CRIT, "unknown net mode %i active", confgetint("net", "mode")); + errno = EOPNOTSUPP; + return(-1); + } +} + +char *formataddress(struct sockaddr *arg, socklen_t arglen) +{ + struct sockaddr_un *UNIX; /* Some wise guy has #defined unix with + * lowercase letters to 1, so I do this + * instead. */ + struct sockaddr_in *ipv4; +#ifdef HAVE_IPV6 + struct sockaddr_in6 *ipv6; +#endif + static char *ret = NULL; + char buf[1024]; + + if(ret != NULL) + free(ret); + ret = NULL; + switch(arg->sa_family) + { + case AF_UNIX: + UNIX = (struct sockaddr_un *)arg; + ret = sprintf2("%s", UNIX->sun_path); + break; + case AF_INET: + ipv4 = (struct sockaddr_in *)arg; + if(inet_ntop(AF_INET, &ipv4->sin_addr, buf, sizeof(buf)) == NULL) + return(NULL); + ret = sprintf2("%s:%i", buf, (int)ntohs(ipv4->sin_port)); + break; +#ifdef HAVE_IPV6 + case AF_INET6: + ipv6 = (struct sockaddr_in6 *)arg; + if(inet_ntop(AF_INET6, &ipv6->sin6_addr, buf, sizeof(buf)) == NULL) + return(NULL); + ret = sprintf2("%s:%i", buf, (int)ntohs(ipv6->sin6_port)); + break; +#endif + default: + errno = EPFNOSUPPORT; + break; + } + return(ret); +} + +#if 0 + +/* + * It was very nice to use this, but it seems + * to mess things up, so I guess it has to go... :-( + */ + +static int formataddress(FILE *stream, const struct printf_info *info, const void *const *args) +{ + struct sockaddr *arg; + socklen_t arglen; + struct sockaddr_un *UNIX; /* Some wise guy has #defined unix with + * lowercase letters to 1, so I do this + * instead. */ + struct sockaddr_in *ipv4; + int ret; + + arg = *(struct sockaddr **)(args[0]); + arglen = *(socklen_t *)(args[1]); + switch(arg->sa_family) + { + case AF_UNIX: + UNIX = (struct sockaddr_un *)arg; + ret = fprintf(stream, "%s", UNIX->sun_path); + break; + case AF_INET: + ipv4 = (struct sockaddr_in *)arg; + ret = fprintf(stream, "%s:%i", inet_ntoa(ipv4->sin_addr), (int)ntohs(ipv4->sin_port)); + break; + default: + ret = -1; + errno = EPFNOSUPPORT; + break; + } + return(ret); +} + +static int formataddress_arginfo(const struct printf_info *info, size_t n, int *argtypes) +{ + if(n > 0) + argtypes[0] = PA_POINTER; + if(n > 1) + argtypes[1] = PA_INT; /* Sources tell me that socklen_t _must_ + * be an int, so I guess this should be + * safe. */ + return(2); +} +#endif + +static int init(int hup) +{ + if(!hup) + { + /* + if(register_printf_function('N', formataddress, formataddress_arginfo)) + { + flog(LOG_CRIT, "could not register printf handler %%N: %s", strerror(errno)); + return(1); + } + */ + } + return(0); +} + +static void terminate(void) +{ + while(sockets != NULL) + unlinksock(sockets); +} + +static struct module me = +{ + .name = "net", + .conf = + { + .vars = myvars + }, + .init = init, + .terminate = terminate +}; + +MODULE(me) diff --git a/daemon/net.h b/daemon/net.h new file mode 100644 index 0000000..451c398 --- /dev/null +++ b/daemon/net.h @@ -0,0 +1,113 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _NET_H +#define _NET_H + +#include + +#define SOCK_LST 0 /* Listening */ +#define SOCK_SYN 1 /* Connecting */ +#define SOCK_EST 2 /* Established */ +#define SOCK_STL 3 /* Stale, dead */ +#define SOCK_TOS_MINDELAY 0x10 +#define SOCK_TOS_MAXTP 0x08 +#define SOCK_TOS_MAXREL 0x04 +#define SOCK_TOS_MINCOST 0x02 + +struct dgrambuf +{ + struct dgrambuf *next; + struct sockaddr *addr; + socklen_t addrlen; + void *data; + size_t size; +}; + +struct socket +{ + struct socket *next, *prev; + int refcount; + int fd; + int isrealsocket; /* Bleh... */ + int family; + int tos; + int type; + int state; + int ignread; + int events; + int close; + struct sockaddr *remote; + socklen_t remotelen; + union + { + struct + { + struct dgrambuf *f, *l; + } d; + struct + { + void *buf; + size_t bufsize; + size_t datasize; + } s; + } outbuf; + union + { + struct + { + struct dgrambuf *f, *l; + } d; + struct + { + void *buf; + size_t bufsize; + size_t datasize; + } s; + } inbuf; + void (*conncb)(struct socket *sk, int err, void *data); + void (*errcb)(struct socket *sk, int err, void *data); + void (*readcb)(struct socket *sk, void *data); + void (*writecb)(struct socket *sk, void *data); + void (*acceptcb)(struct socket *sk, struct socket *newsk, void *data); + void *data; +}; + +void putsock(struct socket *sk); +void getsock(struct socket *sk); +struct socket *netcslisten(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data); +struct socket *netcslistenlocal(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data); +struct socket *netcsconn(struct sockaddr *addr, socklen_t addrlen, void (*func)(struct socket *, int, void *), void *data); +int pollsocks(int timeout); +void sockqueue(struct socket *sk, void *data, size_t size); +size_t sockqueuesize(struct socket *sk); +int netresolve(char *addr, void (*callback)(struct sockaddr *addr, int addrlen, void *data), void *data); +struct socket *netcsdgram(struct sockaddr *name, socklen_t namelen); +struct socket *netdupsock(struct socket *sk); +void netdgramconn(struct socket *sk, struct sockaddr *addr, socklen_t addrlen); +int sockgetlocalname(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf); +int sockgetremotename(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf); +void closesock(struct socket *sk); +void *sockgetinbuf(struct socket *sk, size_t *size); +struct socket *wrapsock(int fd); +size_t sockgetdatalen(struct socket *sk); +int getpublicaddr(int af, struct sockaddr **addr, socklen_t *lenbuf); +int socksettos(struct socket *sk, int tos); +char *formataddress(struct sockaddr *arg, socklen_t arglen); + +#endif diff --git a/daemon/search.c b/daemon/search.c new file mode 100644 index 0000000..040ec30 --- /dev/null +++ b/daemon/search.c @@ -0,0 +1,1183 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "utils.h" +#include "log.h" +#include "sysevents.h" +#include "filenet.h" +#include "search.h" + +#define TOK_STR 0 +#define TOK_SE 1 +#define TOK_OP 2 +#define TOK_CP 3 + +struct srchlist +{ + struct srchlist *next, *prev; + struct search *srch; +}; + +struct tok +{ + struct tok *next; + int type; + union + { + wchar_t *str; + struct sexpr *se; + } d; +}; + +struct reinfo +{ + wchar_t *begstr, *endstr, *onestr; + struct wcslist *strs; +}; + +struct bound +{ + int min, max; +}; + +static void trycommit(void); + +struct search *searches = NULL; +static struct srchlist *searchqueue = NULL; +static struct timer *committimer = NULL; +GCBCHAIN(newsrchcb, struct search *); + +wchar_t *regexunquotesimple(wchar_t *re) +{ + wchar_t *specials, *buf, *p; + + specials = L"\\^$.*+?[{()|"; + buf = smalloc((wcslen(re) + 1) * sizeof(wchar_t)); + p = buf; + for(; *re != L'\0'; re++) + { + if(*re == L'\\') + { + re++; + if(!*re) + { + *p = L'\0'; + return(buf); + } + *(p++) = *re; + } else { + if(wcschr(specials, *re) != NULL) + { + free(buf); + return(NULL); + } + *(p++) = *re; + } + } + *p = L'\0'; + return(buf); +} + +static void freesln(struct wcslist *ln, struct wcslist **list) +{ + if(ln->prev != NULL) + ln->prev->next = ln->next; + if(ln->next != NULL) + ln->next->prev = ln->prev; + if(ln == *list) + *list = ln->next; + free(ln->str); + free(ln); +} + +void freesl(struct wcslist **list) +{ + while(*list != NULL) + freesln(*list, list); +} + +static struct wcslist *newsl(struct wcslist **list, wchar_t *str) +{ + struct wcslist *ln; + + ln = smalloc(sizeof(*ln)); + memset(ln, 0, sizeof(*ln)); + ln->str = swcsdup(str); + ln->len = wcslen(str); + ln->next = *list; + ln->prev = NULL; + if(*list != NULL) + (*list)->prev = ln; + *list = ln; + return(ln); +} + +static void slmerge1(struct wcslist **list, wchar_t *str) +{ + size_t len; + struct wcslist *cur, *next; + + len = wcslen(str); + for(cur = *list; cur != NULL; cur = next) + { + next = cur->next; + if(len <= cur->len) + { + if(((len < cur->len) && wcsexists(cur->str, str)) || !wcscmp(str, cur->str)) + return; + } else if(len > cur->len) { + if(wcsexists(str, cur->str)) + freesln(cur, list); + } + } + newsl(list, str); +} + +void slmergemax(struct wcslist **dest, struct wcslist *src) +{ + for(; src != NULL; src = src->next) + slmerge1(dest, src->str); +} + +static struct wcslist *makeminlist1(wchar_t *s1, wchar_t *s2) +{ + int i; + wchar_t *p1, *p2, c; + struct wcslist *list; + + list = NULL; + for(p1 = s1; *p1 != L'\0'; p1++) + { + for(p2 = s2; *p2 != L'\0'; p2++) + { + for(i = 0; (p1[i] != L'\0') && (p2[i] != L'\0') && (towlower(p1[i]) == towlower(p2[i])); i++); + if(i > 0) + { + c = p2[i]; + p2[i] = L'\0'; + slmerge1(&list, p2); + p2[i] = c; + } + } + } + return(list); +} + +struct wcslist *slmergemin(struct wcslist *l1, struct wcslist *l2) +{ + struct wcslist *cur1, *cur2, *list, *rlist; + + list = NULL; + for(cur1 = l1; cur1 != NULL; cur1 = cur1->next) + { + for(cur2 = l2; cur2 != NULL; cur2 = cur2->next) + { + rlist = makeminlist1(cur1->str, cur2->str); + slmergemax(&list, rlist); + freesl(&rlist); + } + } + return(list); +} + +static struct bound readbound(wchar_t *p, wchar_t **endret) +{ + struct bound ret; + + switch(*p) + { + case L'?': + ret.min = 0; + ret.max = 1; + p++; + break; + case L'*': + ret.min = 0; + ret.max = -1; + p++; + break; + case L'+': + ret.min = 1; + ret.max = -1; + p++; + break; + case L'{': + p++; + ret.min = wcstol(p, &p, 10); + if(*p == L',') + { + p++; + if(*p == L'}') + ret.max = -1; + else + ret.max = wcstol(p, &p, 10); + } else { + ret.max = ret.min; + } + if(*p != L'}') + { + /* Cannot happen in a validated regex... */ + flog(LOG_WARNING, "BUG? \"Impossible\" case encountered in search.c:readbound() (No `}' after `{'-type bound!"); + } else { + p++; + } + break; + default: + ret.min = 1; + ret.max = 1; + break; + } + if(endret != NULL) + *endret = p; + return(ret); +} + +#define fnc(p) do {if((p) != NULL) free(p); (p) = NULL; p ## size = 0; p ## data = 0;} while(0) + +static struct reinfo analyzere(wchar_t *re, wchar_t **endret, wchar_t endc) +{ + int i, commit, parsealt; + struct reinfo ret, sinf; + struct bound b; + wchar_t *cs, *ns; + size_t cssize, csdata, nssize, nsdata, len1, len2, maxlen, minlen; + wchar_t beg, c; + struct wcslist *list; + + memset(&ret, 0, sizeof(ret)); + commit = parsealt = 0; + beg = 1; + cs = ns = NULL; + cssize = csdata = nssize = nsdata = 0; + while((*re != endc) && !parsealt) + { + switch(*re) + { + case L'$': + case L'^': + re++; + commit = 1; + break; + case L'.': + re++; + b = readbound(re, &re); + if(b.max != 0) + commit = 1; + break; + case L'(': + re++; + sinf = analyzere(re, &re, L')'); + re++; + b = readbound(re, &re); + if(sinf.onestr != NULL) + { + for(i = 0; i < b.min; i++) + { + bufcat(cs, sinf.onestr, wcslen(sinf.onestr)); + bufcat(ns, sinf.onestr, wcslen(sinf.onestr)); + } + if((b.max == -1) || (b.max > b.min)) + commit = 1; + } else { + commit = 1; + if(b.min > 0) + { + if(sinf.begstr != NULL) + bufcat(cs, sinf.begstr, wcslen(sinf.begstr)); + if(sinf.endstr != NULL) + bufcat(ns, sinf.endstr, wcslen(sinf.endstr)); + } + } + if(sinf.begstr != NULL) + free(sinf.begstr); + if(sinf.endstr != NULL) + free(sinf.endstr); + if(sinf.onestr != NULL) + free(sinf.onestr); + if(b.min > 0) + slmergemax(&ret.strs, sinf.strs); + freesl(&sinf.strs); + break; + case L'[': + c = L'\0'; + re += 2; + /* Must exist in a validated RE... */ + while(*(re++) != L']'); + readbound(re, &re); + commit = 1; + break; + case L'|': + re++; + parsealt = 1; + break; + case L'\\': + re++; + /* A validated RE cannot end in a backslash, so fall + * through */ + default: + c = *(re++); + b = readbound(re, &re); + for(i = 0; i < b.min; i++) + addtobuf(cs, c); + if((b.max == -1) || (b.max > b.min)) + commit = 1; + break; + } + if(commit) + { + if(cs != NULL) + addtobuf(cs, L'\0'); + if(beg) + { + if(cs != NULL) + ret.begstr = swcsdup(cs); + beg = 0; + } + if(cs != NULL) + slmerge1(&ret.strs, cs); + fnc(cs); + commit = 0; + if(ns != NULL) + { + cs = swcsdup(ns); + cssize = nssize; + csdata = nsdata; + fnc(ns); + } + } + } + if(cs != NULL) + { + addtobuf(cs, L'\0'); + if(beg) + ret.onestr = swcsdup(cs); + else + ret.endstr = swcsdup(cs); + slmerge1(&ret.strs, cs); + fnc(cs); + } + if(parsealt) + { + sinf = analyzere(re, &re, endc); + list = slmergemin(ret.strs, sinf.strs); + freesl(&ret.strs); + freesl(&sinf.strs); + ret.strs = list; + if(sinf.begstr != NULL) + { + if(ret.begstr != NULL) + { + for(i = 0; (sinf.begstr[i] != L'\0') && (ret.begstr != L'\0') && (ret.begstr[i] == sinf.begstr[i]); i++); + if(i == 0) + free(ret.begstr); + else + ret.begstr[i] = L'\0'; + } + free(sinf.begstr); + } else { + if(ret.begstr != NULL) + { + free(ret.begstr); + ret.begstr = NULL; + } + } + if(sinf.endstr != NULL) + { + if(ret.endstr != NULL) + { + len1 = wcslen(ret.endstr); + len2 = wcslen(sinf.endstr); + if(len1 < len2) + { + minlen = len1; + maxlen = len2; + } else { + minlen = len2; + maxlen = len1; + } + for(i = 1; (i <= minlen) && (ret.endstr[len1 - i] == sinf.endstr[len2 - i]); i++); + if(i == 1) + free(ret.endstr); + else if(i <= maxlen) + wmemmove(ret.endstr, ret.endstr + (len1 - i) + 1, i); + } + free(sinf.endstr); + } else { + if(ret.endstr != NULL) + { + free(ret.endstr); + ret.endstr = NULL; + } + } + if(sinf.onestr != NULL) + { + if(ret.onestr != NULL) + { + /* XXX: Comparing beginning and end of the onestrs and + * create begstr and endstr if there isn't an exact + * match.*/ + if(wcscmp(ret.onestr, sinf.onestr)) + { + free(ret.onestr); + ret.onestr = NULL; + } + } + free(sinf.onestr); + } else { + if(ret.onestr != NULL) + { + free(ret.onestr); + ret.onestr = NULL; + } + } + } + if(endret != NULL) + *endret = re; + return(ret); +} + +#undef fnc + +struct wcslist *regexfindstrings(wchar_t *re) +{ + struct reinfo i; + + i = analyzere(re, NULL, L'\0'); + if(i.begstr != NULL) + free(i.begstr); + if(i.endstr != NULL) + free(i.endstr); + if(i.onestr != NULL) + free(i.onestr); + return(i.strs); +} + +static struct sexpr *newsexpr(void) +{ + struct sexpr *sexpr; + + sexpr = smalloc(sizeof(*sexpr)); + memset(sexpr, 0, sizeof(*sexpr)); + sexpr->refcount = 1; + return(sexpr); +} + +void getsexpr(struct sexpr *sexpr) +{ + sexpr->refcount++; +} + +void putsexpr(struct sexpr *sexpr) +{ + if(--sexpr->refcount != 0) + return; + if(sexpr->l != NULL) + putsexpr(sexpr->l); + if(sexpr->r != NULL) + putsexpr(sexpr->r); + if((sexpr->op == SOP_NAMERE) || (sexpr->op == SOP_LINKRE)) + { + if(sexpr->d.re.sre != NULL) + free(sexpr->d.re.sre); + if(sexpr->d.re.inited) + regfree(&sexpr->d.re.cre); + } + if((sexpr->op == SOP_NAMESS) || (sexpr->op == SOP_LINKSS)) + { + if(sexpr->d.s != NULL) + free(sexpr->d.s); + } + free(sexpr); +} + +static struct tok *newtok(void) +{ + struct tok *tok; + + tok = smalloc(sizeof(*tok)); + memset(tok, 0, sizeof(*tok)); + tok->next = NULL; + return(tok); +} + +static void freetok(struct tok *tok) +{ + if((tok->type == TOK_STR) && (tok->d.str != NULL)) + free(tok->d.str); + if((tok->type == TOK_SE) && (tok->d.se != NULL)) + putsexpr(tok->d.se); + free(tok); +} + +static void pushtok(struct tok *tok, struct tok **st) +{ + tok->next = *st; + *st = tok; +} + +static struct tok *poptok(struct tok **st) +{ + struct tok *tok; + + tok = *st; + *st = (*st)->next; + return(tok); +} + +int calccost(struct sexpr *sexpr) +{ + sexpr->tcost = sexpr->cost; + if(sexpr->l != NULL) + sexpr->tcost += calccost(sexpr->l); + if(sexpr->r != NULL) + sexpr->tcost += calccost(sexpr->r); + return(sexpr->tcost); +} + +struct sexpr *parsesexpr(int argc, wchar_t **argv) +{ + int i, done, std; + struct tok *st, *tok, *tok2; + struct sexpr *sexpr; + char *buf; + wchar_t *wbuf; + + std = 0; + st = NULL; + for(i = 0; i < argc; i++) + { + pushtok(tok = newtok(), &st); + tok->type = TOK_STR; + tok->d.str = swcsdup(argv[i]); + std++; + do + { + done = 1; + if((st->type == TOK_STR) && !wcscmp(st->d.str, L"(")) + { + freetok(poptok(&st)); + pushtok(tok = newtok(), &st); + tok->type = TOK_OP; + done = 0; + } else if((st->type == TOK_STR) && !wcscmp(st->d.str, L")")) { + freetok(poptok(&st)); + pushtok(tok = newtok(), &st); + tok->type = TOK_CP; + done = 0; + } else if((st->type == TOK_STR) && (!wcsncmp(st->d.str, L"N~", 2) || !wcsncmp(st->d.str, L"L~", 2))) { + tok2 = poptok(&st); + pushtok(tok = newtok(), &st); + tok->type = TOK_SE; + sexpr = newsexpr(); + if((wbuf = regexunquotesimple(tok2->d.str + 2)) != NULL) + { + if(tok2->d.str[0] == L'N') + sexpr->op = SOP_NAMESS; + else + sexpr->op = SOP_LINKSS; + sexpr->d.s = wbuf; + sexpr->cost = 5; + } else { + if(tok2->d.str[0] == L'N') + sexpr->op = SOP_NAMERE; + else + sexpr->op = SOP_LINKRE; + sexpr->cost = 20; + if((buf = icwcstombs(tok2->d.str + 2, "UTF-8")) == NULL) + { + freetok(tok2); + putsexpr(sexpr); + goto out_err; + } + if(regcomp(&sexpr->d.re.cre, buf, REG_EXTENDED | REG_ICASE | REG_NOSUB)) + { + freetok(tok2); + free(buf); + putsexpr(sexpr); + goto out_err; + } + free(buf); + sexpr->d.re.inited = 1; + sexpr->d.re.sre = swcsdup(tok2->d.str + 2); + } + getsexpr(tok->d.se = sexpr); + freetok(tok2); + putsexpr(sexpr); + done = 0; + } else if((st->type == TOK_STR) && (!wcsncmp(st->d.str, L"S<", 2) || !wcsncmp(st->d.str, L"S=", 2) || !wcsncmp(st->d.str, L"S>", 2))) { + tok2 = poptok(&st); + pushtok(tok = newtok(), &st); + tok->type = TOK_SE; + sexpr = newsexpr(); + if(tok2->d.str[1] == L'<') + sexpr->op = SOP_SIZELT; + else if(tok2->d.str[1] == L'=') + sexpr->op = SOP_SIZEEQ; + else + sexpr->op = SOP_SIZEGT; + sexpr->d.n = wcstol(tok2->d.str + 2, NULL, 0); + sexpr->cost = 0; + getsexpr(tok->d.se = sexpr); + freetok(tok2); + putsexpr(sexpr); + done = 0; + } else if((std >= 3) && (st->type == TOK_CP) && (st->next->type == TOK_SE) && (st->next->next->type == TOK_OP)) { + freetok(poptok(&st)); + tok = poptok(&st); + freetok(poptok(&st)); + pushtok(tok, &st); + std -= 2; + done = 0; + } else if((std >= 2) && (st->type == TOK_SE) && (st->next->type == TOK_STR) && !wcscmp(st->next->d.str, L"!")) { + sexpr = newsexpr(); + sexpr->op = SOP_NOT; + sexpr->cost = 0; + getsexpr(sexpr->l = st->d.se); + freetok(poptok(&st)); + freetok(poptok(&st)); + pushtok(tok = newtok(), &st); + tok->type = TOK_SE; + getsexpr(tok->d.se = sexpr); + putsexpr(sexpr); + std -= 1; + done = 0; + } else if((std >= 3) && (st->type == TOK_SE) && (st->next->type == TOK_STR) && (!wcscmp(st->next->d.str, L"&") || !wcscmp(st->next->d.str, L"|")) && (st->next->next->type == TOK_SE)) { + sexpr = newsexpr(); + if(!wcscmp(st->next->d.str, L"&")) + sexpr->op = SOP_AND; + else + sexpr->op = SOP_OR; + sexpr->cost = 0; + getsexpr(sexpr->l = st->next->next->d.se); + getsexpr(sexpr->r = st->d.se); + freetok(poptok(&st)); + freetok(poptok(&st)); + freetok(poptok(&st)); + pushtok(tok = newtok(), &st); + tok->type = TOK_SE; + getsexpr(tok->d.se = sexpr); + putsexpr(sexpr); + std -= 2; + done = 0; + } + } while(!done); + } + if((st == NULL) || (st->next != NULL) || (st->type != TOK_SE)) + goto out_err; + getsexpr(sexpr = st->d.se); + freetok(st); + calccost(sexpr); + return(sexpr); + + out_err: + while(st != NULL) + freetok(poptok(&st)); + return(NULL); +} + +void optsexpr(struct sexpr *sexpr) +{ + struct sexpr *buf; + + if((sexpr->l != NULL) && (sexpr->r != NULL)) + { + if(sexpr->l->tcost > sexpr->r->tcost) + { + buf = sexpr->r; + sexpr->r = sexpr->l; + sexpr->l = buf; + } + } + if(sexpr->l != NULL) + optsexpr(sexpr->l); + if(sexpr->r != NULL) + optsexpr(sexpr->r); +} + +struct wcslist *findsexprstrs(struct sexpr *sexpr) +{ + struct wcslist *list, *l1, *l2; + + list = NULL; + switch(sexpr->op) + { + case SOP_AND: + list = findsexprstrs(sexpr->l); + l1 = findsexprstrs(sexpr->r); + slmergemax(&list, l1); + freesl(&l1); + break; + case SOP_OR: + l1 = findsexprstrs(sexpr->l); + l2 = findsexprstrs(sexpr->r); + list = slmergemin(l1, l2); + freesl(&l1); + freesl(&l2); + break; + case SOP_NOT: + break; + case SOP_NAMERE: + case SOP_LINKRE: + list = regexfindstrings(sexpr->d.re.sre); + break; + case SOP_NAMESS: + case SOP_LINKSS: + slmerge1(&list, sexpr->d.s); + break; + default: + break; + } + return(list); +} + +static void unlinksqueue(struct srchlist *ln) +{ + if(ln->prev != NULL) + ln->prev->next = ln->next; + if(ln->next != NULL) + ln->next->prev = ln->prev; + if(ln == searchqueue) + searchqueue = ln->next; + free(ln); +} + +static void ctexpire(int cancelled, void *data) +{ + committimer = NULL; + if(!cancelled) + trycommit(); +} + +static void estimatequeue(void) +{ + struct srchlist *cur; + struct srchfnnlist *ln; + time_t now, start; + + if(searchqueue == NULL) + return; + start = now = time(NULL); + for(ln = searchqueue->srch->fnl; ln != NULL; ln = ln->next) + { + if((ln->fn->lastsrch != 0) && (ln->fn->lastsrch + ln->fn->srchwait > start)) + start = ln->fn->lastsrch + ln->fn->srchwait; + } + if(start != searchqueue->srch->eta) + { + searchqueue->srch->eta = start; + CBCHAINDOCB(searchqueue->srch, search_eta, searchqueue->srch); + } + for(cur = searchqueue->next; cur != NULL; cur = cur->next) + { + now = start; + for(ln = cur->srch->fnl; ln != NULL; ln = ln->next) + { + if(now + ln->fn->srchwait > start) + start = now + ln->fn->srchwait; + } + if(start != cur->srch->eta) + { + cur->srch->eta = start; + CBCHAINDOCB(cur->srch, search_eta, cur->srch); + } + } + if((committimer == NULL) || ((time_t)committimer->at != searchqueue->srch->eta)) + { + if(committimer != NULL) + canceltimer(committimer); + committimer = timercallback(searchqueue->srch->eta, ctexpire, NULL); + } +} + +struct search *findsearch(int id) +{ + struct search *srch; + + for(srch = searches; srch != NULL; srch = srch->next) + { + if(srch->id == id) + break; + } + return(srch); +} + +struct search *newsearch(wchar_t *owner, struct sexpr *sexpr) +{ + struct search *srch; + static int id = 0; + + srch = smalloc(sizeof(*srch)); + memset(srch, 0, sizeof(*srch)); + srch->id = id++; + srch->owner = swcsdup(owner); + if((srch->sexpr = sexpr) != NULL) + getsexpr(srch->sexpr); + CBCHAININIT(srch, search_eta); + CBCHAININIT(srch, search_commit); + CBCHAININIT(srch, search_result); + CBCHAININIT(srch, search_destroy); + srch->next = searches; + srch->prev = NULL; + if(searches != NULL) + searches->prev = srch; + searches = srch; + return(srch); +} + +static void srchexpire(int cancelled, struct search *srch) +{ + srch->freetimer = NULL; + if(!cancelled) + freesearch(srch); +} + +static void trycommit(void) +{ + struct srchfnnlist *ln; + struct search *srch; + time_t now; + + if(searchqueue == NULL) + return; + srch = searchqueue->srch; + now = time(NULL); + for(ln = srch->fnl; ln != NULL; ln = ln->next) + { + if(now < ln->fn->lastsrch + ln->fn->srchwait) + break; + } + if(ln != NULL) + return; + unlinksqueue(searchqueue); + srch->state = SRCH_RUN; + srch->eta = time(NULL); + srch->committime = ntime(); + srch->freetimer = timercallback(ntime() + 300, (void (*)(int, void *))srchexpire, srch); + CBCHAINDOCB(srch, search_commit, srch); + for(ln = srch->fnl; ln != NULL; ln = ln->next) + fnetsearch(ln->fn, srch, ln); + estimatequeue(); +} + +void freesearch(struct search *srch) +{ + struct srchfnnlist *ln; + struct srchlist *sln; + + if(srch->prev != NULL) + srch->prev->next = srch->next; + if(srch->next != NULL) + srch->next->prev = srch->prev; + if(srch == searches) + searches = srch->next; + estimatequeue(); + if(srch->freetimer != NULL) + canceltimer(srch->freetimer); + CBCHAINDOCB(srch, search_destroy, srch); + CBCHAINFREE(srch, search_eta); + CBCHAINFREE(srch, search_commit); + CBCHAINFREE(srch, search_result); + CBCHAINFREE(srch, search_destroy); + while(srch->results != NULL) + freesrchres(srch->results); + for(sln = searchqueue; sln != NULL; sln = sln->next) + { + if(sln->srch == srch) + { + unlinksqueue(sln); + break; + } + } + while(srch->fnl != NULL) + { + ln = srch->fnl; + srch->fnl = ln->next; + CBCHAINDOCB(ln, searchfnl_destroy, ln); + CBCHAINFREE(ln, searchfnl_destroy); + putfnetnode(ln->fn); + free(ln); + } + if(srch->sexpr != NULL) + putsexpr(srch->sexpr); + if(srch->owner != NULL) + free(srch->owner); + free(srch); +} + +void searchaddfn(struct search *srch, struct fnetnode *fn) +{ + struct srchfnnlist *ln; + + for(ln = srch->fnl; ln != NULL; ln = ln->next) + { + if(ln->fn == fn) + return; + } + ln = smalloc(sizeof(*ln)); + memset(ln, 0, sizeof(*ln)); + getfnetnode(ln->fn = fn); + CBCHAININIT(ln, searchfnl_destroy); + ln->next = srch->fnl; + srch->fnl = ln; +} + +static void linksearch(struct search *srch, struct srchlist *prev) +{ + struct srchlist *new; + + new = smalloc(sizeof(*new)); + new->srch = srch; + if(prev == NULL) + { + new->prev = NULL; + new->next = searchqueue; + if(searchqueue != NULL) + searchqueue->prev = new; + searchqueue = new; + } else { + new->prev = prev; + if((new->next = prev->next) != NULL) + new->next->prev = new; + prev->next = new; + } + GCBCHAINDOCB(newsrchcb, srch); + estimatequeue(); +} + +/* + * queuesearch is also the "scheduler" function - it finds a suitable + * place in the queue for the new search. I'll make a weak attempt at + * describing the algorithm: + * First, we find the first search that doesn't have a lower priority + * than this one. If there is no such, we just link this one onto the + * end of the queue. + * Then, if we have a search of this priority in the queue with the + * same owner as the new search, we set lastmine to the search after + * that one, otherwise, lastmine is the first search of this + * priority. If lastmine is discovered either to not exist (that is, + * our last search is at the end of the queue), or to be of lower + * priority (higher number), we link it in at the appropriate end. + * Then, we find the next search of the same priority and owner as + * lastmine, and link this search in before it. That should yield a + * 'round-robin-like' scheduling within priority boundaries. I think. + */ +void queuesearch(struct search *srch) +{ + struct srchlist *cur, *lastmine, *prev; + wchar_t *nexto; + + for(prev = NULL, cur = searchqueue; cur != NULL; prev = cur, cur = cur->next) + { + if(cur->srch->prio >= srch->prio) + break; + } + if(cur == NULL) + { + linksearch(srch, prev); + return; + } + lastmine = cur; + for(; cur != NULL; prev = cur, cur = cur->next) + { + if(!wcscmp(cur->srch->owner, srch->owner)) + lastmine = cur->next; + if(cur->srch->prio > srch->prio) + break; + } + if((lastmine == NULL) || (lastmine->srch->prio > srch->prio)) + { + linksearch(srch, prev); + return; + } + nexto = lastmine->srch->owner; + for(cur = lastmine->next; cur != NULL; prev = cur, cur = cur->next) + { + if(!wcscmp(cur->srch->owner, nexto)) + break; + if(cur->srch->prio > srch->prio) + break; + } + if(cur == NULL) + { + linksearch(srch, prev); + return; + } + linksearch(srch, prev); +} + +static int srisvalid(struct srchres *sr, struct sexpr *sexpr) +{ + int ret; + char *buf; + wchar_t *p; + + switch(sexpr->op) + { + case SOP_FALSE: + return(0); + case SOP_TRUE: + return(1); + case SOP_AND: + if(!srisvalid(sr, sexpr->l)) + return(0); + return(srisvalid(sr, sexpr->r)); + case SOP_OR: + if(srisvalid(sr, sexpr->l)) + return(1); + return(srisvalid(sr, sexpr->r)); + case SOP_NOT: + return(!srisvalid(sr, sexpr->l)); + case SOP_NAMERE: + if((buf = icwcstombs(sr->filename, "UTF-8")) == NULL) + return(0); + ret = regexec(&sexpr->d.re.cre, buf, 0, NULL, 0); + free(buf); + return(!ret); + case SOP_LINKRE: + p = sr->filename; + if(sr->fnet->filebasename != NULL) + p = sr->fnet->filebasename(p); + if((buf = icwcstombs(p, "UTF-8")) == NULL) + return(0); + ret = regexec(&sexpr->d.re.cre, buf, 0, NULL, 0); + free(buf); + return(!ret); + case SOP_NAMESS: + return(wcsexists(sr->filename, sexpr->d.s)); + case SOP_LINKSS: + p = sr->filename; + if(sr->fnet->filebasename != NULL) + p = sr->fnet->filebasename(p); + return(wcsexists(p, sexpr->d.s)); + case SOP_SIZELT: + return(sr->size < sexpr->d.n); + case SOP_SIZEEQ: + return(sr->size == sexpr->d.n); + case SOP_SIZEGT: + return(sr->size > sexpr->d.n); + } + return(0); +} + +struct srchres *newsrchres(struct fnet *fnet, wchar_t *filename, wchar_t *peerid) +{ + struct srchres *sr; + + sr = smalloc(sizeof(*sr)); + memset(sr, 0, sizeof(*sr)); + sr->size = -1; + sr->slots = -1; + sr->fnet = fnet; + sr->filename = swcsdup(filename); + sr->peerid = swcsdup(peerid); + return(sr); +} + +void freesrchres(struct srchres *sr) +{ + if(sr->next != NULL) + sr->next->prev = sr->prev; + if(sr->prev != NULL) + sr->prev->next = sr->next; + if(sr->srch != NULL) + { + if(sr->srch->results == sr) + sr->srch->results = sr->next; + sr->srch->numres--; + } + if(sr->filename != NULL) + free(sr->filename); + if(sr->peerid != NULL) + free(sr->peerid); + if(sr->peernick != NULL) + free(sr->peernick); + if(sr->fn != NULL) + putfnetnode(sr->fn); + free(sr); +} + +struct srchres *dupsrchres(struct srchres *sr) +{ + struct srchres *new; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + new->size = sr->size; + new->slots = sr->slots; + new->fnet = sr->fnet; + if(sr->peerid != NULL) + new->peerid = swcsdup(sr->peerid); + if(sr->peernick != NULL) + new->peernick = swcsdup(sr->peernick); + if(sr->filename != NULL) + new->filename = swcsdup(sr->filename); + if(sr->fn != NULL) + getfnetnode(new->fn = sr->fn); + return(new); +} + +static void linksr(struct search *srch, struct srchres *sr) +{ + sr->prev = NULL; + sr->next = srch->results; + if(srch->results != NULL) + srch->results->prev = sr; + srch->results = sr; + sr->srch = srch; + sr->time = ntime() - srch->committime; + srch->numres++; +} + +void submitsrchres(struct srchres *sr) +{ + struct search *srch; + struct srchfnnlist *ln; + struct srchres *dsr; + + for(srch = searches; srch != NULL; srch = srch->next) + { + if(srch->state == SRCH_RUN) + { + if(!srisvalid(sr, srch->sexpr)) + continue; + for(ln = srch->fnl; ln != NULL; ln = ln->next) + { + if(((sr->fn != NULL) && (ln->fn == sr->fn)) || ((sr->fn == NULL) && (sr->fnet == ln->fn->fnet))) + break; + } + if(ln != NULL) + { + dsr = dupsrchres(sr); + linksr(srch, dsr); + CBCHAINDOCB(srch, search_result, srch, dsr); + } + } + } +} diff --git a/daemon/search.h b/daemon/search.h new file mode 100644 index 0000000..dff4c80 --- /dev/null +++ b/daemon/search.h @@ -0,0 +1,132 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _SEARCH_H +#define _SEARCH_H + +#include "filenet.h" +#include "sysevents.h" +#include +#include + +#define SOP_FALSE 0 +#define SOP_TRUE 1 +#define SOP_AND 2 +#define SOP_OR 3 +#define SOP_NOT 4 +#define SOP_NAMERE 5 +#define SOP_NAMESS 6 +#define SOP_LINKRE 7 +#define SOP_LINKSS 8 +#define SOP_SIZEGT 9 +#define SOP_SIZELT 10 +#define SOP_SIZEEQ 11 + +#define SRCH_WAIT 0 +#define SRCH_RUN 1 + +struct wcslist +{ + struct wcslist *next, *prev; + wchar_t *str; + size_t len; +}; + +struct sexpr +{ + int refcount; + int op; + struct sexpr *l, *r; + int cost, tcost; + union + { + struct + { + wchar_t *sre; + regex_t cre; + int inited; + } re; + wchar_t *s; + int n; + } d; +}; + +struct srchfnnlist +{ + struct srchfnnlist *next; + struct fnetnode *fn; + void *fnetdata; + CBCHAIN(searchfnl_destroy, struct srchfnnlist *ln); +}; + +struct search +{ + struct search *next, *prev; + int id; + int state; + wchar_t *owner; + int prio; + time_t eta; + double committime; + struct sexpr *sexpr; + struct srchfnnlist *fnl; + struct srchres *results; + int numres; + struct timer *freetimer; + CBCHAIN(search_eta, struct search *srch); + CBCHAIN(search_commit, struct search *srch); + CBCHAIN(search_result, struct search *srch, struct srchres *sr); + CBCHAIN(search_destroy, struct search *srch); +}; + +struct srchres +{ + struct srchres *next, *prev; + struct search *srch; + wchar_t *filename; + struct fnet *fnet; + wchar_t *peerid, *peernick; + size_t size; + int slots; + struct fnetnode *fn; + double time; +}; + +wchar_t *regexunquotesimple(wchar_t *re); +struct sexpr *parsesexpr(int argc, wchar_t **argv); +void optsexpr(struct sexpr *sexpr); +void getsexpr(struct sexpr *sexpr); +void putsexpr(struct sexpr *sexpr); +struct search *newsearch(wchar_t *owner, struct sexpr *sexpr); +void searchaddfn(struct search *srch, struct fnetnode *fn); +void queuesearch(struct search *srch); +void freesearch(struct search *srch); +struct wcslist *regexfindstrings(wchar_t *re); +void freesl(struct wcslist **list); +void slmergemax(struct wcslist **dest, struct wcslist *src); +struct wcslist *slmergemin(struct wcslist *l1, struct wcslist *l2); +struct wcslist *findsexprstrs(struct sexpr *sexpr); +struct srchres *newsrchres(struct fnet *fnet, wchar_t *filename, wchar_t *peerid); +void freesrchres(struct srchres *sr); +void submitsrchres(struct srchres *sr); +struct search *findsearch(int id); + +extern struct search *searches; +EGCBCHAIN(newsrchcb, struct search *); + +#endif diff --git a/daemon/sysevents.h b/daemon/sysevents.h new file mode 100644 index 0000000..47e574f --- /dev/null +++ b/daemon/sysevents.h @@ -0,0 +1,52 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _SYSEVENTS_H +#define _SYSEVENTS_H + +#include +#include "auth.h" + +#define FD_END -1 +#define FD_PIPE 0 +#define FD_FILE 1 + +struct timer +{ + struct timer *next, *prev; + double at; + void (*func)(int cancelled, void *data); + void *data; +}; + +struct child +{ + struct child *next, *prev; + pid_t pid; + void (*callback)(pid_t pid, int status, void *data); + void *data; + int status; + volatile int finished; +}; + +void childcallback(pid_t pid, void (*func)(pid_t, int, void *), void *data); +struct timer *timercallback(double at, void (*func)(int, void *), void *data); +void canceltimer(struct timer *timer); +pid_t forksess(uid_t user, struct authhandle *auth, void (*ccbfunc)(pid_t, int, void *), void *data, ...); + +#endif diff --git a/daemon/tiger.c b/daemon/tiger.c new file mode 100644 index 0000000..6465dbb --- /dev/null +++ b/daemon/tiger.c @@ -0,0 +1,748 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "tiger.h" +#include "utils.h" + +/* + * This won't be a fast implementation of Tiger. For now, I just want + * to be done with it and have it somewhat portable. + */ + +static unsigned long long table[]; + +void inittiger(struct tigerhash *th) +{ + th->a = 0x0123456789abcdefULL; + th->b = 0xfedcba9876543210ULL; + th->c = 0xf096a5b4c3b2e187ULL; + th->offset = 0; + th->len = 0; +} + +static void round(unsigned long long *a, unsigned long long *b, unsigned long long *c, unsigned long long x, int mul) +{ + *c ^= x; + *a -= table[((*c >> 0) & 0xff) + 0x0000] ^ table[((*c >> 16) & 0xff) + 0x0100] ^ table[((*c >> 32) & 0xff) + 0x0200] ^ table[((*c >> 48) & 0xff) + 0x0300]; + *b += table[((*c >> 8) & 0xff) + 0x0300] ^ table[((*c >> 24) & 0xff) + 0x0200] ^ table[((*c >> 40) & 0xff) + 0x0100] ^ table[((*c >> 56) & 0xff) + 0x0000]; + *b *= mul; +} + +static void pass(unsigned long long *a, unsigned long long *b, unsigned long long *c, unsigned long long *x, int mul) +{ + round(a, b, c, x[0], mul); + round(b, c, a, x[1], mul); + round(c, a, b, x[2], mul); + round(a, b, c, x[3], mul); + round(b, c, a, x[4], mul); + round(c, a, b, x[5], mul); + round(a, b, c, x[6], mul); + round(b, c, a, x[7], mul); +} + +static void key_schedule(unsigned long long *x) +{ + x[0] -= x[7] ^ 0xa5a5a5a5a5a5a5a5ULL; + x[1] ^= x[0]; + x[2] += x[1]; + x[3] -= x[2] ^ ((~x[1]) << 19); + x[4] ^= x[3]; + x[5] += x[4]; + x[6] -= x[5] ^ ((~x[4]) >> 23); + x[7] ^= x[6]; + x[0] += x[7]; + x[1] -= x[0] ^ ((~x[7]) << 19); + x[2] ^= x[1]; + x[3] += x[2]; + x[4] -= x[3] ^ ((~x[2]) >> 23); + x[5] ^= x[4]; + x[6] += x[5]; + x[7] -= x[6] ^ 0x0123456789abcdefULL; +} + +static void doblock(struct tigerhash *th) +{ + int i, o; + unsigned long long x[8], aa, bb, cc; + + for(i = 0; i < 8; i++) { + x[i] = 0; + for(o = 0; o < 8; o++) { + x[i] <<= 8; + x[i] |= th->block[(i * 8) + 7 - o]; + } + } + aa = th->a; + bb = th->b; + cc = th->c; + pass(&th->a, &th->b, &th->c, x, 5); + key_schedule(x); + pass(&th->c, &th->a, &th->b, x, 7); + key_schedule(x); + pass(&th->b, &th->c, &th->a, x, 9); + th->a ^= aa; + th->b -= bb; + th->c += cc; + th->offset = 0; +} + +void dotiger(struct tigerhash *th, char *buf, size_t buflen) +{ + int taken; + + th->len += buflen; + while(buflen > 0) { + taken = buflen; + if(taken > 64 - th->offset) + taken = 64 - th->offset; + memcpy(th->block + th->offset, buf, taken); + th->offset += taken; + buflen -= taken; + buf += taken; + if(th->offset == 64) + doblock(th); + } +} + +void synctiger(struct tigerhash *th) +{ + int i; + unsigned long long buf; + + th->block[th->offset++] = 1; + while(th->offset & 7) + th->block[th->offset++] = 0; + if(th->offset > 56) + doblock(th); + if(th->offset < 56) + memset(th->block + th->offset, 0, 56 - th->offset); + buf = th->len << 3; + for(i = 0; i < 8; i++) { + th->block[56 + i] = buf & 0xff; + buf >>= 8; + } + doblock(th); +} + +void restiger(struct tigerhash *th, char *rbuf) +{ + int i; + unsigned long long buf; + + buf = th->a; + for(i = 0; i < 8; i++) { + rbuf[i] = buf & 0xff; + buf >>= 8; + } + buf = th->b; + for(; i < 16; i++) { + rbuf[i] = buf & 0xff; + buf >>= 8; + } + buf = th->c; + for(; i < 24; i++) { + rbuf[i] = buf & 0xff; + buf >>= 8; + } +} + +void inittigertree(struct tigertreehash *tth) +{ + tth->blocks = 0; + tth->offset = 0; + tth->depth = 0; +} + +static void combine(struct tigertreehash *tth) +{ + struct tigerhash th; + + inittiger(&th); + dotiger(&th, "\001", 1); + tth->depth--; + dotiger(&th, tth->stack[tth->depth - 1], 24); + dotiger(&th, tth->stack[tth->depth], 24); + synctiger(&th); + restiger(&th, tth->stack[tth->depth - 1]); +} + +static void dotreeblock(struct tigertreehash *tth) +{ + struct tigerhash th; + int nb; + + inittiger(&th); + dotiger(&th, "\0", 1); + dotiger(&th, tth->block, tth->offset); + synctiger(&th); + restiger(&th, tth->stack[tth->depth++]); + tth->offset = 0; + for(nb = ++tth->blocks; !(nb & 1); nb >>= 1) + combine(tth); +} + +void dotigertree(struct tigertreehash *tth, char *buf, size_t buflen) +{ + int taken; + + while(buflen > 0) { + taken = buflen; + if(taken > 1024 - tth->offset) + taken = 1024 - tth->offset; + memcpy(tth->block + tth->offset, buf, taken); + tth->offset += taken; + buflen -= taken; + buf += taken; + if(tth->offset == 1024) + dotreeblock(tth); + } +} + +void synctigertree(struct tigertreehash *tth) +{ + if((tth->offset > 0) || (tth->blocks == 0)) + dotreeblock(tth); + while(tth->depth > 1) + combine(tth); +} + +void restigertree(struct tigertreehash *tth, char *rbuf) +{ + memcpy(rbuf, tth->stack[0], 24); +} + +static unsigned long long table[1024] = { + 0x02aab17cf7e90c5eULL, 0xac424b03e243a8ecULL, + 0x72cd5be30dd5fcd3ULL, 0x6d019b93f6f97f3aULL, + 0xcd9978ffd21f9193ULL, 0x7573a1c9708029e2ULL, + 0xb164326b922a83c3ULL, 0x46883eee04915870ULL, + 0xeaace3057103ece6ULL, 0xc54169b808a3535cULL, + 0x4ce754918ddec47cULL, 0x0aa2f4dfdc0df40cULL, + 0x10b76f18a74dbefaULL, 0xc6ccb6235ad1ab6aULL, + 0x13726121572fe2ffULL, 0x1a488c6f199d921eULL, + 0x4bc9f9f4da0007caULL, 0x26f5e6f6e85241c7ULL, + 0x859079dbea5947b6ULL, 0x4f1885c5c99e8c92ULL, + 0xd78e761ea96f864bULL, 0x8e36428c52b5c17dULL, + 0x69cf6827373063c1ULL, 0xb607c93d9bb4c56eULL, + 0x7d820e760e76b5eaULL, 0x645c9cc6f07fdc42ULL, + 0xbf38a078243342e0ULL, 0x5f6b343c9d2e7d04ULL, + 0xf2c28aeb600b0ec6ULL, 0x6c0ed85f7254bcacULL, + 0x71592281a4db4fe5ULL, 0x1967fa69ce0fed9fULL, + 0xfd5293f8b96545dbULL, 0xc879e9d7f2a7600bULL, + 0x860248920193194eULL, 0xa4f9533b2d9cc0b3ULL, + 0x9053836c15957613ULL, 0xdb6dcf8afc357bf1ULL, + 0x18beea7a7a370f57ULL, 0x037117ca50b99066ULL, + 0x6ab30a9774424a35ULL, 0xf4e92f02e325249bULL, + 0x7739db07061ccae1ULL, 0xd8f3b49ceca42a05ULL, + 0xbd56be3f51382f73ULL, 0x45faed5843b0bb28ULL, + 0x1c813d5c11bf1f83ULL, 0x8af0e4b6d75fa169ULL, + 0x33ee18a487ad9999ULL, 0x3c26e8eab1c94410ULL, + 0xb510102bc0a822f9ULL, 0x141eef310ce6123bULL, + 0xfc65b90059ddb154ULL, 0xe0158640c5e0e607ULL, + 0x884e079826c3a3cfULL, 0x930d0d9523c535fdULL, + 0x35638d754e9a2b00ULL, 0x4085fccf40469dd5ULL, + 0xc4b17ad28be23a4cULL, 0xcab2f0fc6a3e6a2eULL, + 0x2860971a6b943fcdULL, 0x3dde6ee212e30446ULL, + 0x6222f32ae01765aeULL, 0x5d550bb5478308feULL, + 0xa9efa98da0eda22aULL, 0xc351a71686c40da7ULL, + 0x1105586d9c867c84ULL, 0xdcffee85fda22853ULL, + 0xccfbd0262c5eef76ULL, 0xbaf294cb8990d201ULL, + 0xe69464f52afad975ULL, 0x94b013afdf133e14ULL, + 0x06a7d1a32823c958ULL, 0x6f95fe5130f61119ULL, + 0xd92ab34e462c06c0ULL, 0xed7bde33887c71d2ULL, + 0x79746d6e6518393eULL, 0x5ba419385d713329ULL, + 0x7c1ba6b948a97564ULL, 0x31987c197bfdac67ULL, + 0xde6c23c44b053d02ULL, 0x581c49fed002d64dULL, + 0xdd474d6338261571ULL, 0xaa4546c3e473d062ULL, + 0x928fce349455f860ULL, 0x48161bbacaab94d9ULL, + 0x63912430770e6f68ULL, 0x6ec8a5e602c6641cULL, + 0x87282515337ddd2bULL, 0x2cda6b42034b701bULL, + 0xb03d37c181cb096dULL, 0xe108438266c71c6fULL, + 0x2b3180c7eb51b255ULL, 0xdf92b82f96c08bbcULL, + 0x5c68c8c0a632f3baULL, 0x5504cc861c3d0556ULL, + 0xabbfa4e55fb26b8fULL, 0x41848b0ab3baceb4ULL, + 0xb334a273aa445d32ULL, 0xbca696f0a85ad881ULL, + 0x24f6ec65b528d56cULL, 0x0ce1512e90f4524aULL, + 0x4e9dd79d5506d35aULL, 0x258905fac6ce9779ULL, + 0x2019295b3e109b33ULL, 0xf8a9478b73a054ccULL, + 0x2924f2f934417eb0ULL, 0x3993357d536d1bc4ULL, + 0x38a81ac21db6ff8bULL, 0x47c4fbf17d6016bfULL, + 0x1e0faadd7667e3f5ULL, 0x7abcff62938beb96ULL, + 0xa78dad948fc179c9ULL, 0x8f1f98b72911e50dULL, + 0x61e48eae27121a91ULL, 0x4d62f7ad31859808ULL, + 0xeceba345ef5ceaebULL, 0xf5ceb25ebc9684ceULL, + 0xf633e20cb7f76221ULL, 0xa32cdf06ab8293e4ULL, + 0x985a202ca5ee2ca4ULL, 0xcf0b8447cc8a8fb1ULL, + 0x9f765244979859a3ULL, 0xa8d516b1a1240017ULL, + 0x0bd7ba3ebb5dc726ULL, 0xe54bca55b86adb39ULL, + 0x1d7a3afd6c478063ULL, 0x519ec608e7669eddULL, + 0x0e5715a2d149aa23ULL, 0x177d4571848ff194ULL, + 0xeeb55f3241014c22ULL, 0x0f5e5ca13a6e2ec2ULL, + 0x8029927b75f5c361ULL, 0xad139fabc3d6e436ULL, + 0x0d5df1a94ccf402fULL, 0x3e8bd948bea5dfc8ULL, + 0xa5a0d357bd3ff77eULL, 0xa2d12e251f74f645ULL, + 0x66fd9e525e81a082ULL, 0x2e0c90ce7f687a49ULL, + 0xc2e8bcbeba973bc5ULL, 0x000001bce509745fULL, + 0x423777bbe6dab3d6ULL, 0xd1661c7eaef06eb5ULL, + 0xa1781f354daacfd8ULL, 0x2d11284a2b16affcULL, + 0xf1fc4f67fa891d1fULL, 0x73ecc25dcb920adaULL, + 0xae610c22c2a12651ULL, 0x96e0a810d356b78aULL, + 0x5a9a381f2fe7870fULL, 0xd5ad62ede94e5530ULL, + 0xd225e5e8368d1427ULL, 0x65977b70c7af4631ULL, + 0x99f889b2de39d74fULL, 0x233f30bf54e1d143ULL, + 0x9a9675d3d9a63c97ULL, 0x5470554ff334f9a8ULL, + 0x166acb744a4f5688ULL, 0x70c74caab2e4aeadULL, + 0xf0d091646f294d12ULL, 0x57b82a89684031d1ULL, + 0xefd95a5a61be0b6bULL, 0x2fbd12e969f2f29aULL, + 0x9bd37013feff9fe8ULL, 0x3f9b0404d6085a06ULL, + 0x4940c1f3166cfe15ULL, 0x09542c4dcdf3defbULL, + 0xb4c5218385cd5ce3ULL, 0xc935b7dc4462a641ULL, + 0x3417f8a68ed3b63fULL, 0xb80959295b215b40ULL, + 0xf99cdaef3b8c8572ULL, 0x018c0614f8fcb95dULL, + 0x1b14accd1a3acdf3ULL, 0x84d471f200bb732dULL, + 0xc1a3110e95e8da16ULL, 0x430a7220bf1a82b8ULL, + 0xb77e090d39df210eULL, 0x5ef4bd9f3cd05e9dULL, + 0x9d4ff6da7e57a444ULL, 0xda1d60e183d4a5f8ULL, + 0xb287c38417998e47ULL, 0xfe3edc121bb31886ULL, + 0xc7fe3ccc980ccbefULL, 0xe46fb590189bfd03ULL, + 0x3732fd469a4c57dcULL, 0x7ef700a07cf1ad65ULL, + 0x59c64468a31d8859ULL, 0x762fb0b4d45b61f6ULL, + 0x155baed099047718ULL, 0x68755e4c3d50baa6ULL, + 0xe9214e7f22d8b4dfULL, 0x2addbf532eac95f4ULL, + 0x32ae3909b4bd0109ULL, 0x834df537b08e3450ULL, + 0xfa209da84220728dULL, 0x9e691d9b9efe23f7ULL, + 0x0446d288c4ae8d7fULL, 0x7b4cc524e169785bULL, + 0x21d87f0135ca1385ULL, 0xcebb400f137b8aa5ULL, + 0x272e2b66580796beULL, 0x3612264125c2b0deULL, + 0x057702bdad1efbb2ULL, 0xd4babb8eacf84be9ULL, + 0x91583139641bc67bULL, 0x8bdc2de08036e024ULL, + 0x603c8156f49f68edULL, 0xf7d236f7dbef5111ULL, + 0x9727c4598ad21e80ULL, 0xa08a0896670a5fd7ULL, + 0xcb4a8f4309eba9cbULL, 0x81af564b0f7036a1ULL, + 0xc0b99aa778199abdULL, 0x959f1ec83fc8e952ULL, + 0x8c505077794a81b9ULL, 0x3acaaf8f056338f0ULL, + 0x07b43f50627a6778ULL, 0x4a44ab49f5eccc77ULL, + 0x3bc3d6e4b679ee98ULL, 0x9cc0d4d1cf14108cULL, + 0x4406c00b206bc8a0ULL, 0x82a18854c8d72d89ULL, + 0x67e366b35c3c432cULL, 0xb923dd61102b37f2ULL, + 0x56ab2779d884271dULL, 0xbe83e1b0ff1525afULL, + 0xfb7c65d4217e49a9ULL, 0x6bdbe0e76d48e7d4ULL, + 0x08df828745d9179eULL, 0x22ea6a9add53bd34ULL, + 0xe36e141c5622200aULL, 0x7f805d1b8cb750eeULL, + 0xafe5c7a59f58e837ULL, 0xe27f996a4fb1c23cULL, + 0xd3867dfb0775f0d0ULL, 0xd0e673de6e88891aULL, + 0x123aeb9eafb86c25ULL, 0x30f1d5d5c145b895ULL, + 0xbb434a2dee7269e7ULL, 0x78cb67ecf931fa38ULL, + 0xf33b0372323bbf9cULL, 0x52d66336fb279c74ULL, + 0x505f33ac0afb4eaaULL, 0xe8a5cd99a2cce187ULL, + 0x534974801e2d30bbULL, 0x8d2d5711d5876d90ULL, + 0x1f1a412891bc038eULL, 0xd6e2e71d82e56648ULL, + 0x74036c3a497732b7ULL, 0x89b67ed96361f5abULL, + 0xffed95d8f1ea02a2ULL, 0xe72b3bd61464d43dULL, + 0xa6300f170bdc4820ULL, 0xebc18760ed78a77aULL, + 0xe6a6be5a05a12138ULL, 0xb5a122a5b4f87c98ULL, + 0x563c6089140b6990ULL, 0x4c46cb2e391f5dd5ULL, + 0xd932addbc9b79434ULL, 0x08ea70e42015aff5ULL, + 0xd765a6673e478cf1ULL, 0xc4fb757eab278d99ULL, + 0xdf11c6862d6e0692ULL, 0xddeb84f10d7f3b16ULL, + 0x6f2ef604a665ea04ULL, 0x4a8e0f0ff0e0dfb3ULL, + 0xa5edeef83dbcba51ULL, 0xfc4f0a2a0ea4371eULL, + 0xe83e1da85cb38429ULL, 0xdc8ff882ba1b1ce2ULL, + 0xcd45505e8353e80dULL, 0x18d19a00d4db0717ULL, + 0x34a0cfeda5f38101ULL, 0x0be77e518887caf2ULL, + 0x1e341438b3c45136ULL, 0xe05797f49089ccf9ULL, + 0xffd23f9df2591d14ULL, 0x543dda228595c5cdULL, + 0x661f81fd99052a33ULL, 0x8736e641db0f7b76ULL, + 0x15227725418e5307ULL, 0xe25f7f46162eb2faULL, + 0x48a8b2126c13d9feULL, 0xafdc541792e76eeaULL, + 0x03d912bfc6d1898fULL, 0x31b1aafa1b83f51bULL, + 0xf1ac2796e42ab7d9ULL, 0x40a3a7d7fcd2ebacULL, + 0x1056136d0afbbcc5ULL, 0x7889e1dd9a6d0c85ULL, + 0xd33525782a7974aaULL, 0xa7e25d09078ac09bULL, + 0xbd4138b3eac6edd0ULL, 0x920abfbe71eb9e70ULL, + 0xa2a5d0f54fc2625cULL, 0xc054e36b0b1290a3ULL, + 0xf6dd59ff62fe932bULL, 0x3537354511a8ac7dULL, + 0xca845e9172fadcd4ULL, 0x84f82b60329d20dcULL, + 0x79c62ce1cd672f18ULL, 0x8b09a2add124642cULL, + 0xd0c1e96a19d9e726ULL, 0x5a786a9b4ba9500cULL, + 0x0e020336634c43f3ULL, 0xc17b474aeb66d822ULL, + 0x6a731ae3ec9baac2ULL, 0x8226667ae0840258ULL, + 0x67d4567691caeca5ULL, 0x1d94155c4875adb5ULL, + 0x6d00fd985b813fdfULL, 0x51286efcb774cd06ULL, + 0x5e8834471fa744afULL, 0xf72ca0aee761ae2eULL, + 0xbe40e4cdaee8e09aULL, 0xe9970bbb5118f665ULL, + 0x726e4beb33df1964ULL, 0x703b000729199762ULL, + 0x4631d816f5ef30a7ULL, 0xb880b5b51504a6beULL, + 0x641793c37ed84b6cULL, 0x7b21ed77f6e97d96ULL, + 0x776306312ef96b73ULL, 0xae528948e86ff3f4ULL, + 0x53dbd7f286a3f8f8ULL, 0x16cadce74cfc1063ULL, + 0x005c19bdfa52c6ddULL, 0x68868f5d64d46ad3ULL, + 0x3a9d512ccf1e186aULL, 0x367e62c2385660aeULL, + 0xe359e7ea77dcb1d7ULL, 0x526c0773749abe6eULL, + 0x735ae5f9d09f734bULL, 0x493fc7cc8a558ba8ULL, + 0xb0b9c1533041ab45ULL, 0x321958ba470a59bdULL, + 0x852db00b5f46c393ULL, 0x91209b2bd336b0e5ULL, + 0x6e604f7d659ef19fULL, 0xb99a8ae2782ccb24ULL, + 0xccf52ab6c814c4c7ULL, 0x4727d9afbe11727bULL, + 0x7e950d0c0121b34dULL, 0x756f435670ad471fULL, + 0xf5add442615a6849ULL, 0x4e87e09980b9957aULL, + 0x2acfa1df50aee355ULL, 0xd898263afd2fd556ULL, + 0xc8f4924dd80c8fd6ULL, 0xcf99ca3d754a173aULL, + 0xfe477bacaf91bf3cULL, 0xed5371f6d690c12dULL, + 0x831a5c285e687094ULL, 0xc5d3c90a3708a0a4ULL, + 0x0f7f903717d06580ULL, 0x19f9bb13b8fdf27fULL, + 0xb1bd6f1b4d502843ULL, 0x1c761ba38fff4012ULL, + 0x0d1530c4e2e21f3bULL, 0x8943ce69a7372c8aULL, + 0xe5184e11feb5ce66ULL, 0x618bdb80bd736621ULL, + 0x7d29bad68b574d0bULL, 0x81bb613e25e6fe5bULL, + 0x071c9c10bc07913fULL, 0xc7beeb7909ac2d97ULL, + 0xc3e58d353bc5d757ULL, 0xeb017892f38f61e8ULL, + 0xd4effb9c9b1cc21aULL, 0x99727d26f494f7abULL, + 0xa3e063a2956b3e03ULL, 0x9d4a8b9a4aa09c30ULL, + 0x3f6ab7d500090fb4ULL, 0x9cc0f2a057268ac0ULL, + 0x3dee9d2dedbf42d1ULL, 0x330f49c87960a972ULL, + 0xc6b2720287421b41ULL, 0x0ac59ec07c00369cULL, + 0xef4eac49cb353425ULL, 0xf450244eef0129d8ULL, + 0x8acc46e5caf4deb6ULL, 0x2ffeab63989263f7ULL, + 0x8f7cb9fe5d7a4578ULL, 0x5bd8f7644e634635ULL, + 0x427a7315bf2dc900ULL, 0x17d0c4aa2125261cULL, + 0x3992486c93518e50ULL, 0xb4cbfee0a2d7d4c3ULL, + 0x7c75d6202c5ddd8dULL, 0xdbc295d8e35b6c61ULL, + 0x60b369d302032b19ULL, 0xce42685fdce44132ULL, + 0x06f3ddb9ddf65610ULL, 0x8ea4d21db5e148f0ULL, + 0x20b0fce62fcd496fULL, 0x2c1b912358b0ee31ULL, + 0xb28317b818f5a308ULL, 0xa89c1e189ca6d2cfULL, + 0x0c6b18576aaadbc8ULL, 0xb65deaa91299fae3ULL, + 0xfb2b794b7f1027e7ULL, 0x04e4317f443b5bebULL, + 0x4b852d325939d0a6ULL, 0xd5ae6beefb207ffcULL, + 0x309682b281c7d374ULL, 0xbae309a194c3b475ULL, + 0x8cc3f97b13b49f05ULL, 0x98a9422ff8293967ULL, + 0x244b16b01076ff7cULL, 0xf8bf571c663d67eeULL, + 0x1f0d6758eee30da1ULL, 0xc9b611d97adeb9b7ULL, + 0xb7afd5887b6c57a2ULL, 0x6290ae846b984fe1ULL, + 0x94df4cdeacc1a5fdULL, 0x058a5bd1c5483affULL, + 0x63166cc142ba3c37ULL, 0x8db8526eb2f76f40ULL, + 0xe10880036f0d6d4eULL, 0x9e0523c9971d311dULL, + 0x45ec2824cc7cd691ULL, 0x575b8359e62382c9ULL, + 0xfa9e400dc4889995ULL, 0xd1823ecb45721568ULL, + 0xdafd983b8206082fULL, 0xaa7d29082386a8cbULL, + 0x269fcd4403b87588ULL, 0x1b91f5f728bdd1e0ULL, + 0xe4669f39040201f6ULL, 0x7a1d7c218cf04adeULL, + 0x65623c29d79ce5ceULL, 0x2368449096c00bb1ULL, + 0xab9bf1879da503baULL, 0xbc23ecb1a458058eULL, + 0x9a58df01bb401eccULL, 0xa070e868a85f143dULL, + 0x4ff188307df2239eULL, 0x14d565b41a641183ULL, + 0xee13337452701602ULL, 0x950e3dcf3f285e09ULL, + 0x59930254b9c80953ULL, 0x3bf299408930da6dULL, + 0xa955943f53691387ULL, 0xa15edecaa9cb8784ULL, + 0x29142127352be9a0ULL, 0x76f0371fff4e7afbULL, + 0x0239f450274f2228ULL, 0xbb073af01d5e868bULL, + 0xbfc80571c10e96c1ULL, 0xd267088568222e23ULL, + 0x9671a3d48e80b5b0ULL, 0x55b5d38ae193bb81ULL, + 0x693ae2d0a18b04b8ULL, 0x5c48b4ecadd5335fULL, + 0xfd743b194916a1caULL, 0x2577018134be98c4ULL, + 0xe77987e83c54a4adULL, 0x28e11014da33e1b9ULL, + 0x270cc59e226aa213ULL, 0x71495f756d1a5f60ULL, + 0x9be853fb60afef77ULL, 0xadc786a7f7443dbfULL, + 0x0904456173b29a82ULL, 0x58bc7a66c232bd5eULL, + 0xf306558c673ac8b2ULL, 0x41f639c6b6c9772aULL, + 0x216defe99fda35daULL, 0x11640cc71c7be615ULL, + 0x93c43694565c5527ULL, 0xea038e6246777839ULL, + 0xf9abf3ce5a3e2469ULL, 0x741e768d0fd312d2ULL, + 0x0144b883ced652c6ULL, 0xc20b5a5ba33f8552ULL, + 0x1ae69633c3435a9dULL, 0x97a28ca4088cfdecULL, + 0x8824a43c1e96f420ULL, 0x37612fa66eeea746ULL, + 0x6b4cb165f9cf0e5aULL, 0x43aa1c06a0abfb4aULL, + 0x7f4dc26ff162796bULL, 0x6cbacc8e54ed9b0fULL, + 0xa6b7ffefd2bb253eULL, 0x2e25bc95b0a29d4fULL, + 0x86d6a58bdef1388cULL, 0xded74ac576b6f054ULL, + 0x8030bdbc2b45805dULL, 0x3c81af70e94d9289ULL, + 0x3eff6dda9e3100dbULL, 0xb38dc39fdfcc8847ULL, + 0x123885528d17b87eULL, 0xf2da0ed240b1b642ULL, + 0x44cefadcd54bf9a9ULL, 0x1312200e433c7ee6ULL, + 0x9ffcc84f3a78c748ULL, 0xf0cd1f72248576bbULL, + 0xec6974053638cfe4ULL, 0x2ba7b67c0cec4e4cULL, + 0xac2f4df3e5ce32edULL, 0xcb33d14326ea4c11ULL, + 0xa4e9044cc77e58bcULL, 0x5f513293d934fcefULL, + 0x5dc9645506e55444ULL, 0x50de418f317de40aULL, + 0x388cb31a69dde259ULL, 0x2db4a83455820a86ULL, + 0x9010a91e84711ae9ULL, 0x4df7f0b7b1498371ULL, + 0xd62a2eabc0977179ULL, 0x22fac097aa8d5c0eULL, + 0xf49fcc2ff1daf39bULL, 0x487fd5c66ff29281ULL, + 0xe8a30667fcdca83fULL, 0x2c9b4be3d2fcce63ULL, + 0xda3ff74b93fbbbc2ULL, 0x2fa165d2fe70ba66ULL, + 0xa103e279970e93d4ULL, 0xbecdec77b0e45e71ULL, + 0xcfb41e723985e497ULL, 0xb70aaa025ef75017ULL, + 0xd42309f03840b8e0ULL, 0x8efc1ad035898579ULL, + 0x96c6920be2b2abc5ULL, 0x66af4163375a9172ULL, + 0x2174abdcca7127fbULL, 0xb33ccea64a72ff41ULL, + 0xf04a4933083066a5ULL, 0x8d970acdd7289af5ULL, + 0x8f96e8e031c8c25eULL, 0xf3fec02276875d47ULL, + 0xec7bf310056190ddULL, 0xf5adb0aebb0f1491ULL, + 0x9b50f8850fd58892ULL, 0x4975488358b74de8ULL, + 0xa3354ff691531c61ULL, 0x0702bbe481d2c6eeULL, + 0x89fb24057deded98ULL, 0xac3075138596e902ULL, + 0x1d2d3580172772edULL, 0xeb738fc28e6bc30dULL, + 0x5854ef8f63044326ULL, 0x9e5c52325add3bbeULL, + 0x90aa53cf325c4623ULL, 0xc1d24d51349dd067ULL, + 0x2051cfeea69ea624ULL, 0x13220f0a862e7e4fULL, + 0xce39399404e04864ULL, 0xd9c42ca47086fcb7ULL, + 0x685ad2238a03e7ccULL, 0x066484b2ab2ff1dbULL, + 0xfe9d5d70efbf79ecULL, 0x5b13b9dd9c481854ULL, + 0x15f0d475ed1509adULL, 0x0bebcd060ec79851ULL, + 0xd58c6791183ab7f8ULL, 0xd1187c5052f3eee4ULL, + 0xc95d1192e54e82ffULL, 0x86eea14cb9ac6ca2ULL, + 0x3485beb153677d5dULL, 0xdd191d781f8c492aULL, + 0xf60866baa784ebf9ULL, 0x518f643ba2d08c74ULL, + 0x8852e956e1087c22ULL, 0xa768cb8dc410ae8dULL, + 0x38047726bfec8e1aULL, 0xa67738b4cd3b45aaULL, + 0xad16691cec0dde19ULL, 0xc6d4319380462e07ULL, + 0xc5a5876d0ba61938ULL, 0x16b9fa1fa58fd840ULL, + 0x188ab1173ca74f18ULL, 0xabda2f98c99c021fULL, + 0x3e0580ab134ae816ULL, 0x5f3b05b773645abbULL, + 0x2501a2be5575f2f6ULL, 0x1b2f74004e7e8ba9ULL, + 0x1cd7580371e8d953ULL, 0x7f6ed89562764e30ULL, + 0xb15926ff596f003dULL, 0x9f65293da8c5d6b9ULL, + 0x6ecef04dd690f84cULL, 0x4782275fff33af88ULL, + 0xe41433083f820801ULL, 0xfd0dfe409a1af9b5ULL, + 0x4325a3342cdb396bULL, 0x8ae77e62b301b252ULL, + 0xc36f9e9f6655615aULL, 0x85455a2d92d32c09ULL, + 0xf2c7dea949477485ULL, 0x63cfb4c133a39ebaULL, + 0x83b040cc6ebc5462ULL, 0x3b9454c8fdb326b0ULL, + 0x56f56a9e87ffd78cULL, 0x2dc2940d99f42bc6ULL, + 0x98f7df096b096e2dULL, 0x19a6e01e3ad852bfULL, + 0x42a99ccbdbd4b40bULL, 0xa59998af45e9c559ULL, + 0x366295e807d93186ULL, 0x6b48181bfaa1f773ULL, + 0x1fec57e2157a0a1dULL, 0x4667446af6201ad5ULL, + 0xe615ebcacfb0f075ULL, 0xb8f31f4f68290778ULL, + 0x22713ed6ce22d11eULL, 0x3057c1a72ec3c93bULL, + 0xcb46acc37c3f1f2fULL, 0xdbb893fd02aaf50eULL, + 0x331fd92e600b9fcfULL, 0xa498f96148ea3ad6ULL, + 0xa8d8426e8b6a83eaULL, 0xa089b274b7735cdcULL, + 0x87f6b3731e524a11ULL, 0x118808e5cbc96749ULL, + 0x9906e4c7b19bd394ULL, 0xafed7f7e9b24a20cULL, + 0x6509eadeeb3644a7ULL, 0x6c1ef1d3e8ef0edeULL, + 0xb9c97d43e9798fb4ULL, 0xa2f2d784740c28a3ULL, + 0x7b8496476197566fULL, 0x7a5be3e6b65f069dULL, + 0xf96330ed78be6f10ULL, 0xeee60de77a076a15ULL, + 0x2b4bee4aa08b9bd0ULL, 0x6a56a63ec7b8894eULL, + 0x02121359ba34fef4ULL, 0x4cbf99f8283703fcULL, + 0x398071350caf30c8ULL, 0xd0a77a89f017687aULL, + 0xf1c1a9eb9e423569ULL, 0x8c7976282dee8199ULL, + 0x5d1737a5dd1f7abdULL, 0x4f53433c09a9fa80ULL, + 0xfa8b0c53df7ca1d9ULL, 0x3fd9dcbc886ccb77ULL, + 0xc040917ca91b4720ULL, 0x7dd00142f9d1dcdfULL, + 0x8476fc1d4f387b58ULL, 0x23f8e7c5f3316503ULL, + 0x032a2244e7e37339ULL, 0x5c87a5d750f5a74bULL, + 0x082b4cc43698992eULL, 0xdf917becb858f63cULL, + 0x3270b8fc5bf86ddaULL, 0x10ae72bb29b5dd76ULL, + 0x576ac94e7700362bULL, 0x1ad112dac61efb8fULL, + 0x691bc30ec5faa427ULL, 0xff246311cc327143ULL, + 0x3142368e30e53206ULL, 0x71380e31e02ca396ULL, + 0x958d5c960aad76f1ULL, 0xf8d6f430c16da536ULL, + 0xc8ffd13f1be7e1d2ULL, 0x7578ae66004ddbe1ULL, + 0x05833f01067be646ULL, 0xbb34b5ad3bfe586dULL, + 0x095f34c9a12b97f0ULL, 0x247ab64525d60ca8ULL, + 0xdcdbc6f3017477d1ULL, 0x4a2e14d4decad24dULL, + 0xbdb5e6d9be0a1eebULL, 0x2a7e70f7794301abULL, + 0xdef42d8a270540fdULL, 0x01078ec0a34c22c1ULL, + 0xe5de511af4c16387ULL, 0x7ebb3a52bd9a330aULL, + 0x77697857aa7d6435ULL, 0x004e831603ae4c32ULL, + 0xe7a21020ad78e312ULL, 0x9d41a70c6ab420f2ULL, + 0x28e06c18ea1141e6ULL, 0xd2b28cbd984f6b28ULL, + 0x26b75f6c446e9d83ULL, 0xba47568c4d418d7fULL, + 0xd80badbfe6183d8eULL, 0x0e206d7f5f166044ULL, + 0xe258a43911cbca3eULL, 0x723a1746b21dc0bcULL, + 0xc7caa854f5d7cdd3ULL, 0x7cac32883d261d9cULL, + 0x7690c26423ba942cULL, 0x17e55524478042b8ULL, + 0xe0be477656a2389fULL, 0x4d289b5e67ab2da0ULL, + 0x44862b9c8fbbfd31ULL, 0xb47cc8049d141365ULL, + 0x822c1b362b91c793ULL, 0x4eb14655fb13dfd8ULL, + 0x1ecbba0714e2a97bULL, 0x6143459d5cde5f14ULL, + 0x53a8fbf1d5f0ac89ULL, 0x97ea04d81c5e5b00ULL, + 0x622181a8d4fdb3f3ULL, 0xe9bcd341572a1208ULL, + 0x1411258643cce58aULL, 0x9144c5fea4c6e0a4ULL, + 0x0d33d06565cf620fULL, 0x54a48d489f219ca1ULL, + 0xc43e5eac6d63c821ULL, 0xa9728b3a72770dafULL, + 0xd7934e7b20df87efULL, 0xe35503b61a3e86e5ULL, + 0xcae321fbc819d504ULL, 0x129a50b3ac60bfa6ULL, + 0xcd5e68ea7e9fb6c3ULL, 0xb01c90199483b1c7ULL, + 0x3de93cd5c295376cULL, 0xaed52edf2ab9ad13ULL, + 0x2e60f512c0a07884ULL, 0xbc3d86a3e36210c9ULL, + 0x35269d9b163951ceULL, 0x0c7d6e2ad0cdb5faULL, + 0x59e86297d87f5733ULL, 0x298ef221898db0e7ULL, + 0x55000029d1a5aa7eULL, 0x8bc08ae1b5061b45ULL, + 0xc2c31c2b6c92703aULL, 0x94cc596baf25ef42ULL, + 0x0a1d73db22540456ULL, 0x04b6a0f9d9c4179aULL, + 0xeffdafa2ae3d3c60ULL, 0xf7c8075bb49496c4ULL, + 0x9cc5c7141d1cd4e3ULL, 0x78bd1638218e5534ULL, + 0xb2f11568f850246aULL, 0xedfabcfa9502bc29ULL, + 0x796ce5f2da23051bULL, 0xaae128b0dc93537cULL, + 0x3a493da0ee4b29aeULL, 0xb5df6b2c416895d7ULL, + 0xfcabbd25122d7f37ULL, 0x70810b58105dc4b1ULL, + 0xe10fdd37f7882a90ULL, 0x524dcab5518a3f5cULL, + 0x3c9e85878451255bULL, 0x4029828119bd34e2ULL, + 0x74a05b6f5d3ceccbULL, 0xb610021542e13ecaULL, + 0x0ff979d12f59e2acULL, 0x6037da27e4f9cc50ULL, + 0x5e92975a0df1847dULL, 0xd66de190d3e623feULL, + 0x5032d6b87b568048ULL, 0x9a36b7ce8235216eULL, + 0x80272a7a24f64b4aULL, 0x93efed8b8c6916f7ULL, + 0x37ddbff44cce1555ULL, 0x4b95db5d4b99bd25ULL, + 0x92d3fda169812fc0ULL, 0xfb1a4a9a90660bb6ULL, + 0x730c196946a4b9b2ULL, 0x81e289aa7f49da68ULL, + 0x64669a0f83b1a05fULL, 0x27b3ff7d9644f48bULL, + 0xcc6b615c8db675b3ULL, 0x674f20b9bcebbe95ULL, + 0x6f31238275655982ULL, 0x5ae488713e45cf05ULL, + 0xbf619f9954c21157ULL, 0xeabac46040a8eae9ULL, + 0x454c6fe9f2c0c1cdULL, 0x419cf6496412691cULL, + 0xd3dc3bef265b0f70ULL, 0x6d0e60f5c3578a9eULL, + 0x5b0e608526323c55ULL, 0x1a46c1a9fa1b59f5ULL, + 0xa9e245a17c4c8ffaULL, 0x65ca5159db2955d7ULL, + 0x05db0a76ce35afc2ULL, 0x81eac77ea9113d45ULL, + 0x528ef88ab6ac0a0dULL, 0xa09ea253597be3ffULL, + 0x430ddfb3ac48cd56ULL, 0xc4b3a67af45ce46fULL, + 0x4ececfd8fbe2d05eULL, 0x3ef56f10b39935f0ULL, + 0x0b22d6829cd619c6ULL, 0x17fd460a74df2069ULL, + 0x6cf8cc8e8510ed40ULL, 0xd6c824bf3a6ecaa7ULL, + 0x61243d581a817049ULL, 0x048bacb6bbc163a2ULL, + 0xd9a38ac27d44cc32ULL, 0x7fddff5baaf410abULL, + 0xad6d495aa804824bULL, 0xe1a6a74f2d8c9f94ULL, + 0xd4f7851235dee8e3ULL, 0xfd4b7f886540d893ULL, + 0x247c20042aa4bfdaULL, 0x096ea1c517d1327cULL, + 0xd56966b4361a6685ULL, 0x277da5c31221057dULL, + 0x94d59893a43acff7ULL, 0x64f0c51ccdc02281ULL, + 0x3d33bcc4ff6189dbULL, 0xe005cb184ce66af1ULL, + 0xff5ccd1d1db99beaULL, 0xb0b854a7fe42980fULL, + 0x7bd46a6a718d4b9fULL, 0xd10fa8cc22a5fd8cULL, + 0xd31484952be4bd31ULL, 0xc7fa975fcb243847ULL, + 0x4886ed1e5846c407ULL, 0x28cddb791eb70b04ULL, + 0xc2b00be2f573417fULL, 0x5c9590452180f877ULL, + 0x7a6bddfff370eb00ULL, 0xce509e38d6d9d6a4ULL, + 0xebeb0f00647fa702ULL, 0x1dcc06cf76606f06ULL, + 0xe4d9f28ba286ff0aULL, 0xd85a305dc918c262ULL, + 0x475b1d8732225f54ULL, 0x2d4fb51668ccb5feULL, + 0xa679b9d9d72bba20ULL, 0x53841c0d912d43a5ULL, + 0x3b7eaa48bf12a4e8ULL, 0x781e0e47f22f1ddfULL, + 0xeff20ce60ab50973ULL, 0x20d261d19dffb742ULL, + 0x16a12b03062a2e39ULL, 0x1960eb2239650495ULL, + 0x251c16fed50eb8b8ULL, 0x9ac0c330f826016eULL, + 0xed152665953e7671ULL, 0x02d63194a6369570ULL, + 0x5074f08394b1c987ULL, 0x70ba598c90b25ce1ULL, + 0x794a15810b9742f6ULL, 0x0d5925e9fcaf8c6cULL, + 0x3067716cd868744eULL, 0x910ab077e8d7731bULL, + 0x6a61bbdb5ac42f61ULL, 0x93513efbf0851567ULL, + 0xf494724b9e83e9d5ULL, 0xe887e1985c09648dULL, + 0x34b1d3c675370cfdULL, 0xdc35e433bc0d255dULL, + 0xd0aab84234131be0ULL, 0x08042a50b48b7eafULL, + 0x9997c4ee44a3ab35ULL, 0x829a7b49201799d0ULL, + 0x263b8307b7c54441ULL, 0x752f95f4fd6a6ca6ULL, + 0x927217402c08c6e5ULL, 0x2a8ab754a795d9eeULL, + 0xa442f7552f72943dULL, 0x2c31334e19781208ULL, + 0x4fa98d7ceaee6291ULL, 0x55c3862f665db309ULL, + 0xbd0610175d53b1f3ULL, 0x46fe6cb840413f27ULL, + 0x3fe03792df0cfa59ULL, 0xcfe700372eb85e8fULL, + 0xa7be29e7adbce118ULL, 0xe544ee5cde8431ddULL, + 0x8a781b1b41f1873eULL, 0xa5c94c78a0d2f0e7ULL, + 0x39412e2877b60728ULL, 0xa1265ef3afc9a62cULL, + 0xbcc2770c6a2506c5ULL, 0x3ab66dd5dce1ce12ULL, + 0xe65499d04a675b37ULL, 0x7d8f523481bfd216ULL, + 0x0f6f64fcec15f389ULL, 0x74efbe618b5b13c8ULL, + 0xacdc82b714273e1dULL, 0xdd40bfe003199d17ULL, + 0x37e99257e7e061f8ULL, 0xfa52626904775aaaULL, + 0x8bbbf63a463d56f9ULL, 0xf0013f1543a26e64ULL, + 0xa8307e9f879ec898ULL, 0xcc4c27a4150177ccULL, + 0x1b432f2cca1d3348ULL, 0xde1d1f8f9f6fa013ULL, + 0x606602a047a7ddd6ULL, 0xd237ab64cc1cb2c7ULL, + 0x9b938e7225fcd1d3ULL, 0xec4e03708e0ff476ULL, + 0xfeb2fbda3d03c12dULL, 0xae0bced2ee43889aULL, + 0x22cb8923ebfb4f43ULL, 0x69360d013cf7396dULL, + 0x855e3602d2d4e022ULL, 0x073805bad01f784cULL, + 0x33e17a133852f546ULL, 0xdf4874058ac7b638ULL, + 0xba92b29c678aa14aULL, 0x0ce89fc76cfaadcdULL, + 0x5f9d4e0908339e34ULL, 0xf1afe9291f5923b9ULL, + 0x6e3480f60f4a265fULL, 0xeebf3a2ab29b841cULL, + 0xe21938a88f91b4adULL, 0x57dfeff845c6d3c3ULL, + 0x2f006b0bf62caaf2ULL, 0x62f479ef6f75ee78ULL, + 0x11a55ad41c8916a9ULL, 0xf229d29084fed453ULL, + 0x42f1c27b16b000e6ULL, 0x2b1f76749823c074ULL, + 0x4b76eca3c2745360ULL, 0x8c98f463b91691bdULL, + 0x14bcc93cf1ade66aULL, 0x8885213e6d458397ULL, + 0x8e177df0274d4711ULL, 0xb49b73b5503f2951ULL, + 0x10168168c3f96b6bULL, 0x0e3d963b63cab0aeULL, + 0x8dfc4b5655a1db14ULL, 0xf789f1356e14de5cULL, + 0x683e68af4e51dac1ULL, 0xc9a84f9d8d4b0fd9ULL, + 0x3691e03f52a0f9d1ULL, 0x5ed86e46e1878e80ULL, + 0x3c711a0e99d07150ULL, 0x5a0865b20c4e9310ULL, + 0x56fbfc1fe4f0682eULL, 0xea8d5de3105edf9bULL, + 0x71abfdb12379187aULL, 0x2eb99de1bee77b9cULL, + 0x21ecc0ea33cf4523ULL, 0x59a4d7521805c7a1ULL, + 0x3896f5eb56ae7c72ULL, 0xaa638f3db18f75dcULL, + 0x9f39358dabe9808eULL, 0xb7defa91c00b72acULL, + 0x6b5541fd62492d92ULL, 0x6dc6dee8f92e4d5bULL, + 0x353f57abc4beea7eULL, 0x735769d6da5690ceULL, + 0x0a234aa642391484ULL, 0xf6f9508028f80d9dULL, + 0xb8e319a27ab3f215ULL, 0x31ad9c1151341a4dULL, + 0x773c22a57bef5805ULL, 0x45c7561a07968633ULL, + 0xf913da9e249dbe36ULL, 0xda652d9b78a64c68ULL, + 0x4c27a97f3bc334efULL, 0x76621220e66b17f4ULL, + 0x967743899acd7d0bULL, 0xf3ee5bcae0ed6782ULL, + 0x409f753600c879fcULL, 0x06d09a39b5926db6ULL, + 0x6f83aeb0317ac588ULL, 0x01e6ca4a86381f21ULL, + 0x66ff3462d19f3025ULL, 0x72207c24ddfd3bfbULL, + 0x4af6b6d3e2ece2ebULL, 0x9c994dbec7ea08deULL, + 0x49ace597b09a8bc4ULL, 0xb38c4766cf0797baULL, + 0x131b9373c57c2a75ULL, 0xb1822cce61931e58ULL, + 0x9d7555b909ba1c0cULL, 0x127fafdd937d11d2ULL, + 0x29da3badc66d92e4ULL, 0xa2c1d57154c2ecbcULL, + 0x58c5134d82f6fe24ULL, 0x1c3ae3515b62274fULL, + 0xe907c82e01cb8126ULL, 0xf8ed091913e37fcbULL, + 0x3249d8f9c80046c9ULL, 0x80cf9bede388fb63ULL, + 0x1881539a116cf19eULL, 0x5103f3f76bd52457ULL, + 0x15b7e6f5ae47f7a8ULL, 0xdbd7c6ded47e9ccfULL, + 0x44e55c410228bb1aULL, 0xb647d4255edb4e99ULL, + 0x5d11882bb8aafc30ULL, 0xf5098bbb29d3212aULL, + 0x8fb5ea14e90296b3ULL, 0x677b942157dd025aULL, + 0xfb58e7c0a390acb5ULL, 0x89d3674c83bd4a01ULL, + 0x9e2da4df4bf3b93bULL, 0xfcc41e328cab4829ULL, + 0x03f38c96ba582c52ULL, 0xcad1bdbd7fd85db2ULL, + 0xbbb442c16082ae83ULL, 0xb95fe86ba5da9ab0ULL, + 0xb22e04673771a93fULL, 0x845358c9493152d8ULL, + 0xbe2a488697b4541eULL, 0x95a2dc2dd38e6966ULL, + 0xc02c11ac923c852bULL, 0x2388b1990df2a87bULL, + 0x7c8008fa1b4f37beULL, 0x1f70d0c84d54e503ULL, + 0x5490adec7ece57d4ULL, 0x002b3c27d9063a3aULL, + 0x7eaea3848030a2bfULL, 0xc602326ded2003c0ULL, + 0x83a7287d69a94086ULL, 0xc57a5fcb30f57a8aULL, + 0xb56844e479ebe779ULL, 0xa373b40f05dcbce9ULL, + 0xd71a786e88570ee2ULL, 0x879cbacdbde8f6a0ULL, + 0x976ad1bcc164a32fULL, 0xab21e25e9666d78bULL, + 0x901063aae5e5c33cULL, 0x9818b34448698d90ULL, + 0xe36487ae3e1e8abbULL, 0xafbdf931893bdcb4ULL, + 0x6345a0dc5fbbd519ULL, 0x8628fe269b9465caULL, + 0x1e5d01603f9c51ecULL, 0x4de44006a15049b7ULL, + 0xbf6c70e5f776cbb1ULL, 0x411218f2ef552bedULL, + 0xcb0c0708705a36a3ULL, 0xe74d14754f986044ULL, + 0xcd56d9430ea8280eULL, 0xc12591d7535f5065ULL, + 0xc83223f1720aef96ULL, 0xc3a0396f7363a51fULL +}; diff --git a/daemon/tiger.h b/daemon/tiger.h new file mode 100644 index 0000000..a6322ff --- /dev/null +++ b/daemon/tiger.h @@ -0,0 +1,28 @@ +#ifndef _TIGER_H +#define _TIGER_H + +struct tigerhash { + unsigned long long a, b, c; + unsigned char block[64]; + int offset; + size_t len; +}; + +struct tigertreehash { + int blocks; + char block[1024]; + int offset; + char stack[64][24]; + int depth; +}; + +void inittiger(struct tigerhash *th); +void dotiger(struct tigerhash *th, char *buf, size_t buflen); +void synctiger(struct tigerhash *th); +void restiger(struct tigerhash *th, char *rbuf); +void inittigertree(struct tigertreehash *tth); +void dotigertree(struct tigertreehash *tth, char *buf, size_t buflen); +void synctigertree(struct tigertreehash *tth); +void restigertree(struct tigertreehash *tth, char *rbuf); + +#endif diff --git a/daemon/transfer.c b/daemon/transfer.c new file mode 100644 index 0000000..15bd5d2 --- /dev/null +++ b/daemon/transfer.c @@ -0,0 +1,738 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "log.h" +#include "utils.h" +#include "sysevents.h" +#include "auth.h" +#include "transfer.h" +#include "module.h" + +static void killfilter(struct transfer *transfer); + +struct transfer *transfers = NULL; +int numtransfers = 0; +GCBCHAIN(newtransfercb, struct transfer *); + +void freetransfer(struct transfer *transfer) +{ + struct transarg *ta; + + if(transfer == transfers) + transfers = transfer->next; + if(transfer->next != NULL) + transfer->next->prev = transfer->prev; + if(transfer->prev != NULL) + transfer->prev->next = transfer->next; + CBCHAINDOCB(transfer, trans_destroy, transfer); + CBCHAINFREE(transfer, trans_ac); + CBCHAINFREE(transfer, trans_act); + CBCHAINFREE(transfer, trans_p); + CBCHAINFREE(transfer, trans_destroy); + CBCHAINFREE(transfer, trans_filterout); + while((ta = transfer->args) != NULL) + { + transfer->args = ta->next; + free(ta->rec); + free(ta->val); + free(ta); + } + if(transfer->filter != -1) + killfilter(transfer); + if(transfer->etimer != NULL) + canceltimer(transfer->etimer); + if(transfer->auth != NULL) + authputhandle(transfer->auth); + if(transfer->peerid != NULL) + free(transfer->peerid); + if(transfer->peernick != NULL) + free(transfer->peernick); + if(transfer->path != NULL) + free(transfer->path); + if(transfer->actdesc != NULL) + free(transfer->actdesc); + if(transfer->filterbuf != NULL) + free(transfer->filterbuf); + if(transfer->localend != NULL) + { + transfer->localend->readcb = NULL; + transfer->localend->writecb = NULL; + transfer->localend->errcb = NULL; + putsock(transfer->localend); + } + if(transfer->filterout != NULL) + { + transfer->filterout->readcb = NULL; + transfer->filterout->writecb = NULL; + transfer->filterout->errcb = NULL; + putsock(transfer->filterout); + } + if(transfer->fn != NULL) + putfnetnode(transfer->fn); + free(transfer); + numtransfers--; +} + +struct transfer *newtransfer(void) +{ + struct transfer *new; + static int curid = 0; + + new = smalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + new->id = curid++; + new->size = -1; + new->endpos = -1; + new->filter = -1; + CBCHAININIT(new, trans_ac); + CBCHAININIT(new, trans_act); + CBCHAININIT(new, trans_p); + CBCHAININIT(new, trans_destroy); + CBCHAININIT(new, trans_filterout); + new->next = NULL; + new->prev = NULL; + time(&new->activity); + numtransfers++; + return(new); +} + +void transferaddarg(struct transfer *transfer, wchar_t *rec, wchar_t *val) +{ + struct transarg *ta; + + ta = smalloc(sizeof(*ta)); + ta->rec = swcsdup(rec); + ta->val = swcsdup(val); + ta->next = transfer->args; + transfer->args = ta; +} + +void transferattach(struct transfer *transfer, struct transferiface *iface, void *data) +{ + if(transfer->iface != NULL) + transferdetach(transfer); + transfer->iface = iface; + transfer->ifacedata = data; +} + +void transferdetach(struct transfer *transfer) +{ + if(transfer->iface != NULL) + { + transfer->iface->detach(transfer, transfer->ifacedata); + transfer->iface = NULL; + transfer->ifacedata = NULL; + } +} + +struct transfer *newupload(struct fnetnode *fn, struct fnet *fnet, wchar_t *nickid, struct transferiface *iface, void *data) +{ + struct transfer *transfer; + + transfer = newtransfer(); + if(fnet != NULL) + transfer->fnet = fnet; + else + transfer->fnet = fn->fnet; + transfer->peerid = swcsdup(nickid); + transfer->state = TRNS_HS; + transfer->dir = TRNSD_UP; + if(fn != NULL) + getfnetnode(transfer->fn = fn); + transferattach(transfer, iface, data); + linktransfer(transfer); + bumptransfer(transfer); + return(transfer); +} + +void linktransfer(struct transfer *transfer) +{ + transfer->next = transfers; + transfer->prev = NULL; + if(transfers != NULL) + transfers->prev = transfer; + transfers = transfer; + GCBCHAINDOCB(newtransfercb, transfer); +} + +void resettransfer(struct transfer *transfer) +{ + if(transfer->dir == TRNSD_DOWN) + { + if(transfer->iface != NULL) + transferdetach(transfer); + killfilter(transfer); + transfersetstate(transfer, TRNS_WAITING); + transfersetactivity(transfer, L"reset"); + return; + } +} + +struct transfer *findtransfer(int id) +{ + struct transfer *transfer; + + for(transfer = transfers; transfer != NULL; transfer = transfer->next) + { + if(transfer->id == id) + break; + } + return(transfer); +} + +static void transexpire(int cancelled, struct transfer *transfer) +{ + transfer->etimer = NULL; + if(!cancelled) + bumptransfer(transfer); + else + transfer->timeout = 0; +} + +static void transferread(struct socket *sk, struct transfer *transfer) +{ + if(sockgetdatalen(sk) >= 65536) + sk->ignread = 1; + if((transfer->iface != NULL) && (transfer->iface->gotdata != NULL)) + transfer->iface->gotdata(transfer, transfer->ifacedata); +} + +static void transferwrite(struct socket *sk, struct transfer *transfer) +{ + if((transfer->iface != NULL) && (transfer->iface->wantdata != NULL)) + transfer->iface->wantdata(transfer, transfer->ifacedata); +} + +static void transfererr(struct socket *sk, int errno, struct transfer *transfer) +{ + if((transfer->iface != NULL) && (transfer->iface->endofdata != NULL)) + transfer->iface->endofdata(transfer, transfer->ifacedata); +} + +void transferputdata(struct transfer *transfer, void *buf, size_t size) +{ + time(&transfer->activity); + sockqueue(transfer->localend, buf, size); + transfer->curpos += size; + CBCHAINDOCB(transfer, trans_p, transfer); +} + +void transferendofdata(struct transfer *transfer) +{ + if(transfer->curpos >= transfer->size) + { + transfersetstate(transfer, TRNS_DONE); + transfer->localend->readcb = NULL; + transfer->localend->writecb = NULL; + transfer->localend->errcb = NULL; + putsock(transfer->localend); + transfer->localend = NULL; + } else { + resettransfer(transfer); + } +} + +size_t transferdatasize(struct transfer *transfer) +{ + return(sockqueuesize(transfer->localend)); +} + +void *transfergetdata(struct transfer *transfer, size_t *size) +{ + void *buf; + + if(transfer->localend == NULL) + return(NULL); + transfer->localend->ignread = 0; + time(&transfer->activity); + if((buf = sockgetinbuf(transfer->localend, size)) == NULL) + return(NULL); + if((transfer->endpos >= 0) && (transfer->curpos + *size >= transfer->endpos)) + { + *size = transfer->endpos - transfer->curpos; + buf = srealloc(buf, *size); + } + transfer->curpos += *size; + CBCHAINDOCB(transfer, trans_p, transfer); + return(buf); +} + +void transferprepul(struct transfer *transfer, size_t size, size_t start, size_t end, struct socket *lesk) +{ + transfersetsize(transfer, size); + transfer->curpos = start; + transfer->endpos = end; + lesk->ignread = 1; + transfersetlocalend(transfer, lesk); +} + +void transferstartul(struct transfer *transfer, struct socket *sk) +{ + transfersetstate(transfer, TRNS_MAIN); + socksettos(sk, confgetint("transfer", "ultos")); + if(transfer->localend != NULL) + transfer->localend->ignread = 0; +} + +void transfersetlocalend(struct transfer *transfer, struct socket *sk) +{ + if(transfer->localend != NULL) + putsock(transfer->localend); + getsock(transfer->localend = sk); + sk->data = transfer; + sk->readcb = (void (*)(struct socket *, void *))transferread; + sk->writecb = (void (*)(struct socket *, void *))transferwrite; + sk->errcb = (void (*)(struct socket *, int, void *))transfererr; +} + +void bumptransfer(struct transfer *transfer) +{ + struct fnetnode *fn; + struct fnetpeer *peer; + time_t now; + + if((now = time(NULL)) < transfer->timeout) + { + if(transfer->etimer == NULL) + transfer->etimer = timercallback(transfer->timeout, (void (*)(int, void *))transexpire, transfer); + return; + } + if(transfer->etimer != NULL) + canceltimer(transfer->etimer); + switch(transfer->state) + { + case TRNS_WAITING: + if(transfer->fn != NULL) + { + fn = transfer->fn; + if(fn->state != FNN_EST) + { + transfer->close = 1; + return; + } + peer = fnetfindpeer(fn, transfer->peerid); + } else { + peer = NULL; + for(fn = fnetnodes; fn != NULL; fn = fn->next) + { + if((fn->state == FNN_EST) && (fn->fnet == transfer->fnet) && ((peer = fnetfindpeer(fn, transfer->peerid)) != NULL)) + break; + } + } + transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 30), (void (*)(int, void *))transexpire, transfer); + if(now - transfer->lastreq > 30) + { + if(peer != NULL) + { + fn->fnet->reqconn(peer); + time(&transfer->lastreq); + } + } + break; + case TRNS_HS: + if(transfer->dir == TRNSD_UP) + { + if(now - transfer->activity < 60) + transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 60), (void (*)(int, void *))transexpire, transfer); + else + transfer->close = 1; + } else if(transfer->dir == TRNSD_DOWN) { + if(now - transfer->activity < 60) + transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 60), (void (*)(int, void *))transexpire, transfer); + else + resettransfer(transfer); + } + break; + case TRNS_MAIN: + if(transfer->dir == TRNSD_UP) + { + if(now - transfer->activity < 300) + transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 300), (void (*)(int, void *))transexpire, transfer); + else + transfer->close = 1; + } + break; + } +} + +void transfersetactivity(struct transfer *transfer, wchar_t *desc) +{ + time(&transfer->activity); + if(desc != NULL) + { + if(transfer->actdesc != NULL) + free(transfer->actdesc); + transfer->actdesc = swcsdup(desc); + } + bumptransfer(transfer); + CBCHAINDOCB(transfer, trans_act, transfer); +} + +void transfersetstate(struct transfer *transfer, int newstate) +{ + transfer->state = newstate; + if(transfer->etimer != NULL) + canceltimer(transfer->etimer); + transfersetactivity(transfer, NULL); + CBCHAINDOCB(transfer, trans_ac, transfer, L"state"); +} + +void transfersetnick(struct transfer *transfer, wchar_t *newnick) +{ + if(transfer->peernick != NULL) + free(transfer->peernick); + transfer->peernick = swcsdup(newnick); + CBCHAINDOCB(transfer, trans_ac, transfer, L"nick"); +} + +void transfersetsize(struct transfer *transfer, int newsize) +{ + transfer->size = newsize; + CBCHAINDOCB(transfer, trans_ac, transfer, L"size"); +} + +void transferseterror(struct transfer *transfer, int error) +{ + transfer->error = error; + CBCHAINDOCB(transfer, trans_ac, transfer, L"error"); +} + +void transfersetpath(struct transfer *transfer, wchar_t *path) +{ + if(transfer->path != NULL) + free(transfer->path); + transfer->path = swcsdup(path); + CBCHAINDOCB(transfer, trans_ac, transfer, L"path"); +} + +int slotsleft(void) +{ + struct transfer *transfer; + int slots; + + slots = confgetint("transfer", "slots"); + for(transfer = transfers; (transfer != NULL) && (slots > 0); transfer = transfer->next) + { + if((transfer->dir == TRNSD_UP) && (transfer->state == TRNS_MAIN) && !transfer->flags.b.minislot) + slots--; + } + return(slots); +} + +static void killfilter(struct transfer *transfer) +{ + if(transfer->filter != -1) + { + kill(-transfer->filter, SIGHUP); + transfer->filter = -1; + } + if(transfer->localend) + { + transfer->localend->readcb = NULL; + transfer->localend->writecb = NULL; + transfer->localend->errcb = NULL; + putsock(transfer->localend); + transfer->localend = NULL; + } + if(transfer->filterout) + { + transfer->filterout->readcb = NULL; + putsock(transfer->filterout); + transfer->filterout = NULL; + } + if(transfer->filterbuf) + { + free(transfer->filterbuf); + transfer->filterbuf = NULL; + } + transfer->filterbufsize = transfer->filterbufdata = 0; +} + +static char *findfilter(struct passwd *pwd) +{ + char *path, *filtername; + + if((path = sprintf2("%s/.dcdl-filter", pwd->pw_dir)) != NULL) + { + if(!access(path, X_OK)) + return(path); + free(path); + } + if((filtername = icwcstombs(confgetstr("transfer", "filter"), NULL)) == NULL) + { + flog(LOG_WARNING, "could not convert filter name into local charset: %s", strerror(errno)); + } else { + if(strchr(filtername, '/') == NULL) + { + if((path = sprintf2("/etc/%s", filtername)) != NULL) + { + if(!access(path, X_OK)) + { + free(filtername); + return(path); + } + free(path); + } + if((path = sprintf2("/usr/etc/%s", filtername)) != NULL) + { + if(!access(path, X_OK)) + { + free(filtername); + return(path); + } + free(path); + } + if((path = sprintf2("/usr/local/etc/%s", filtername)) != NULL) + { + if(!access(path, X_OK)) + { + free(filtername); + return(path); + } + free(path); + } + } else { + if(!access(filtername, X_OK)) + return(filtername); + } + free(filtername); + } + return(NULL); +} + +static void filterread(struct socket *sk, struct transfer *transfer) +{ + char *buf, *p, *p2; + size_t bufsize; + wchar_t *cmd, *arg; + + if((buf = sockgetinbuf(sk, &bufsize)) == NULL) + return; + bufcat(transfer->filterbuf, buf, bufsize); + free(buf); + if((p = memchr(transfer->filterbuf, '\n', transfer->filterbufdata)) != NULL) + { + *(p++) = 0; + if((p2 = strchr(transfer->filterbuf, ' ')) != NULL) + *(p2++) = 0; + if((cmd = icmbstowcs(transfer->filterbuf, NULL)) != NULL) + { + arg = NULL; + if(p2 != NULL) + { + if((arg = icmbstowcs(p2, NULL)) == NULL) + flog(LOG_WARNING, "filter sent a string which could not be converted into the local charset: %s: %s", transfer->filterbuf, strerror(errno)); + } + CBCHAINDOCB(transfer, trans_filterout, transfer, cmd, arg); + if(arg != NULL) + free(arg); + free(cmd); + } else { + flog(LOG_WARNING, "filter sent a string which could not be converted into the local charset: %s: %s", transfer->filterbuf, strerror(errno)); + } + memmove(transfer->filterbuf, p, transfer->filterbufdata -= (p - transfer->filterbuf)); + } +} + +static void filterexit(pid_t pid, int status, void *data) +{ + struct transfer *transfer; + + for(transfer = transfers; transfer != NULL; transfer = transfer->next) + { + if(transfer->filter == pid) + { + transfer->filter = -1; + killfilter(transfer); + if(WEXITSTATUS(status)) + { + resettransfer(transfer); + } else { + freetransfer(transfer); + } + break; + } + } +} + +int forkfilter(struct transfer *transfer) +{ + char *filtername, *filename, *peerid, *buf; + wchar_t *wfilename; + struct passwd *pwent; + pid_t pid; + int inpipe, outpipe; + char **argv; + size_t argvsize, argvdata; + struct socket *insock, *outsock; + struct transarg *ta; + char *rec, *val; + + wfilename = transfer->path; + if(transfer->fnet->filebasename != NULL) + wfilename = transfer->fnet->filebasename(wfilename); + if(transfer->auth == NULL) + { + flog(LOG_WARNING, "tried to fork filter for transfer with NULL authhandle (tranfer %i)", transfer->id); + errno = EACCES; + return(-1); + } + if((pwent = getpwuid(transfer->owner)) == NULL) + { + flog(LOG_WARNING, "no passwd entry for uid %i (found in transfer %i)", transfer->owner, transfer->id); + errno = EACCES; + return(-1); + } + if((filtername = findfilter(pwent)) == NULL) + { + flog(LOG_WARNING, "could not find filter for user %s", pwent->pw_name); + errno = ENOENT; + return(-1); + } + if((filename = icwcstombs(wfilename, NULL)) == NULL) + { + if((buf = icwcstombs(wfilename, "UTF-8")) == NULL) + { + flog(LOG_WARNING, "could convert transfer filename to neither local charset nor UTF-8: %s", strerror(errno)); + return(-1); + } + filename = sprintf2("utf8-%s", buf); + free(buf); + } + if((peerid = icwcstombs(transfer->peerid, NULL)) == NULL) + { + if((buf = icwcstombs(transfer->peerid, "UTF-8")) == NULL) + { + flog(LOG_WARNING, "could convert transfer peerid to neither local charset nor UTF-8: %s", strerror(errno)); + free(filename); + return(-1); + } + peerid = sprintf2("utf8-%s", buf); + free(buf); + } + if((pid = forksess(transfer->owner, transfer->auth, filterexit, NULL, FD_PIPE, 0, O_WRONLY, &inpipe, FD_PIPE, 1, O_RDONLY, &outpipe, FD_FILE, 2, O_RDWR, "/dev/null", FD_END)) < 0) + { + flog(LOG_WARNING, "could not fork session for filter for transfer %i: %s", transfer->id, strerror(errno)); + return(-1); + } + if(pid == 0) + { + argv = NULL; + argvsize = argvdata = 0; + buf = sprintf2("%i", transfer->size); + addtobuf(argv, filtername); + addtobuf(argv, filename); + addtobuf(argv, buf); + addtobuf(argv, peerid); + for(ta = transfer->args; ta != NULL; ta = ta->next) + { + if((rec = icwcstombs(ta->rec, NULL)) == NULL) + continue; + if((val = icwcstombs(ta->val, NULL)) == NULL) + continue; + addtobuf(argv, rec); + addtobuf(argv, val); + } + addtobuf(argv, NULL); + execv(filtername, argv); + flog(LOG_WARNING, "could not exec filter %s: %s", filtername, strerror(errno)); + exit(127); + } + insock = wrapsock(inpipe); + outsock = wrapsock(outpipe); + /* Really, really strange thing here - sometimes the kernel would + * return POLLIN on insock, even though it's a write-side + * pipe. The corresponding read on the pipe naturally returns + * EBADF, causing doldacond to think there's something wrong with + * the fd, and thus it closes it. Until I can find out whyever the + * kernel gives a POLLIN on the fd (if I can at all...), I'll just + * set ignread on insock for now. */ + insock->ignread = 1; + transfer->filter = pid; + transfersetlocalend(transfer, insock); + getsock(transfer->filterout = outsock); + outsock->data = transfer; + outsock->readcb = (void (*)(struct socket *, void *))filterread; + putsock(insock); + putsock(outsock); + free(filtername); + free(filename); + free(peerid); + return(0); +} + +static int run(void) +{ + struct transfer *transfer, *next; + + for(transfer = transfers; transfer != NULL; transfer = transfer->next) + { + if((transfer->endpos >= 0) && (transfer->state == TRNS_MAIN) && (transfer->localend != NULL) && (transfer->localend->state == SOCK_EST) && (transfer->curpos >= transfer->endpos)) + { + if((transfer->iface != NULL) && (transfer->iface->endofdata != NULL)) + transfer->iface->endofdata(transfer, transfer->ifacedata); + closesock(transfer->localend); + } + } + for(transfer = transfers; transfer != NULL; transfer = next) + { + next = transfer->next; + if(transfer->close) + { + transferdetach(transfer); + freetransfer(transfer); + continue; + } + } + return(0); +} + +static struct configvar myvars[] = +{ + {CONF_VAR_INT, "slots", {.num = 3}}, + {CONF_VAR_INT, "ultos", {.num = SOCK_TOS_MAXTP}}, + {CONF_VAR_INT, "dltos", {.num = SOCK_TOS_MAXTP}}, + {CONF_VAR_STRING, "filter", {.str = L"dc-filter"}}, + {CONF_VAR_END} +}; + +static struct module me = +{ + .conf = + { + .vars = myvars + }, + .name = "transfer", + .run = run +}; + +MODULE(me); diff --git a/daemon/transfer.h b/daemon/transfer.h new file mode 100644 index 0000000..af50310 --- /dev/null +++ b/daemon/transfer.h @@ -0,0 +1,129 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _TRANSFER_H +#define _TRANSFER_H + +#include +#include + +#include "net.h" +#include "filenet.h" +#include "utils.h" +#include "auth.h" + +#define TRNS_WAITING 0 +#define TRNS_HS 1 +#define TRNS_MAIN 2 +#define TRNS_DONE 3 + +#define TRNSD_UNKNOWN 0 +#define TRNSD_UP 1 +#define TRNSD_DOWN 2 + +#define TRNSE_NOERROR 0 +#define TRNSE_NOTFOUND 1 +#define TRNSE_NOSLOTS 2 + +struct transfer; + +struct transferiface +{ + void (*detach)(struct transfer *transfer, void *data); + void (*gotdata)(struct transfer *transfer, void *data); + void (*endofdata)(struct transfer *transfer, void *data); + void (*wantdata)(struct transfer *transfer, void *data); +}; + +struct transarg +{ + struct transarg *next; + wchar_t *rec; + wchar_t *val; +}; + +struct transfer +{ + struct transfer *next, *prev; + int id, close; + union + { + int w; + struct + { + int byop:1; + int sgranted:1; + int minislot:1; + } b; + } flags; + struct timer *etimer; + time_t timeout, activity, lastreq; + wchar_t *actdesc; + struct fnet *fnet; + struct transferiface *iface; + wchar_t *peerid, *peernick; + wchar_t *path; + uid_t owner; + int state, dir, error; + size_t size, curpos, endpos; + struct fnetnode *fn; + void *ifacedata; + struct socket *localend; + struct transarg *args; + pid_t filter; + struct authhandle *auth; + struct socket *filterout; + char *filterbuf; + size_t filterbufsize, filterbufdata; + CBCHAIN(trans_ac, struct transfer *transfer, wchar_t *attrib); + CBCHAIN(trans_p, struct transfer *transfer); + CBCHAIN(trans_act, struct transfer *transfer); + CBCHAIN(trans_destroy, struct transfer *transfer); + CBCHAIN(trans_filterout, struct transfer *transfer, wchar_t *cmd, wchar_t *arg); +}; + +void freetransfer(struct transfer *transfer); +struct transfer *newtransfer(void); +void linktransfer(struct transfer *transfer); +int slotsleft(void); +void bumptransfer(struct transfer *transfer); +struct transfer *findtransfer(int id); +struct transfer *newupload(struct fnetnode *fn, struct fnet *fnet, wchar_t *nickid, struct transferiface *iface, void *data); +void transfersetnick(struct transfer *transfer, wchar_t *newnick); +void transfersetpath(struct transfer *transfer, wchar_t *newpath); +void transfersetstate(struct transfer *transfer, int newstate); +void transfersetsize(struct transfer *transfer, int newsize); +void transferseterror(struct transfer *transfer, int error); +void transfersetactivity(struct transfer *transfer, wchar_t *desc); +void transferattach(struct transfer *transfer, struct transferiface *iface, void *data); +void transferdetach(struct transfer *transfer); +void resettransfer(struct transfer *transfer); +void transfersetlocalend(struct transfer *transfer, struct socket *sk); +void *transfergetdata(struct transfer *transfer, size_t *size); +void transferaddarg(struct transfer *transfer, wchar_t *rec, wchar_t *val); +int forkfilter(struct transfer *transfer); +void transferputdata(struct transfer *transfer, void *buf, size_t size); +size_t transferdatasize(struct transfer *transfer); +void transferendofdata(struct transfer *transfer); +void transferprepul(struct transfer *transfer, size_t size, size_t start, size_t end, struct socket *lesk); +void transferstartul(struct transfer *transfer, struct socket *sk); + +extern struct transfer *transfers; +EGCBCHAIN(newtransfercb, struct transfer *); + +#endif diff --git a/daemon/ui.c b/daemon/ui.c new file mode 100644 index 0000000..0302fa1 --- /dev/null +++ b/daemon/ui.c @@ -0,0 +1,2105 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +*/ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "conf.h" +#include "auth.h" +#include "utils.h" +#include "net.h" +#include "module.h" +#include "sysevents.h" +#include "filenet.h" +#include "transfer.h" +#include "search.h" +#include "client.h" + +#define PERM_DISALLOW 1 +#define PERM_ADMIN 2 +#define PERM_FNETCTL 4 +#define PERM_TRANS 8 +#define PERM_TRANSCU 16 +#define PERM_CHAT 32 +#define PERM_SRCH 64 + +#define NOTIF_END 0 +#define NOTIF_INT 1 +#define NOTIF_STR 2 +#define NOTIF_FLOAT 3 +#define NOTIF_ID 4 +#define NOTIF_PEND 0 +#define NOTIF_WAIT 1 + +struct uidata; + +struct command +{ + wchar_t *name; + void (*handler)(struct socket *sk, struct uidata *data, int argc, wchar_t **argv); +}; + +struct qcommand +{ + struct qcommand *next; + struct command *cmd; + int argc; + wchar_t **argv; +}; + +struct uiuser +{ + struct uiuser *next, *prev; + int used; + wchar_t *name; + unsigned long perms; + int delete; +}; + +struct notif +{ + struct notif *next, *prev; + struct uidata *ui; + int state; + int code; + double rlimit; + size_t argc; + struct timer *exptimer; + struct notifarg + { + int dt; + union + { + int n; + wchar_t *s; + double d; + } d; + } *argv; +}; + +struct uidata +{ + struct uidata *next, *prev; + struct socket *sk; + struct qcommand *queue, *queuelast; + struct authhandle *auth; + int close; + union + { + struct + { + int fnact:1; + int fnchat:1; + int tract:1; + int trprog:1; + int srch:1; + } b; + int w; + } notify; + wchar_t *username; + struct uiuser *userinfo; + uid_t uid; + struct notif *fnotif, *lnotif; + char *fcmdbuf; + size_t fcmdbufdata, fcmdbufsize; + pid_t fcmdpid; + struct socket *fcmdsk; + /* Read buffer */ + char *inbuf; + size_t inbufsize, indata; + /* Wordset storage */ + wchar_t **argv; + int argc, args; + /* WCS conversation stuff */ + wchar_t *cb; /* Conversation buffer */ + size_t cbsize, cbdata; + iconv_t ichandle; + /* Parser data */ + int ps; /* Parser state */ + wchar_t *pp; /* Current parse pointer */ + wchar_t *cw; /* Current word (building storage) */ + size_t cwsize, cwdata; +}; + +static int srcheta(struct search *srch, void *uudata); +static int srchcommit(struct search *srch, void *uudata); +static int srchres(struct search *srch, struct srchres *sr, void *uudata); + +struct uiuser *users = NULL; +struct uidata *actives = NULL; +struct socket *uisocket = NULL; + +static wchar_t *quoteword(wchar_t *word) +{ + wchar_t *wp, *buf, *bp; + int dq, numbs, numc; + + dq = 0; + numbs = 0; + numc = 0; + if(*word == L'\0') + { + dq = 1; + } else { + for(wp = word; *wp != L'\0'; wp++) + { + if(!dq && iswspace(*wp)) + dq = 1; + if((*wp == L'\\') || (*wp == L'\"')) + numbs++; + numc++; + } + } + if(!dq && !numbs) + return(NULL); + bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1)); + if(dq) + *(bp++) = L'\"'; + for(wp = word; *wp != L'\0'; wp++) + { + if((*wp == L'\\') || (*wp == L'\"')) + *(bp++) = L'\\'; + *(bp++) = *wp; + } + if(dq) + *(bp++) = L'\"'; + *(bp++) = L'\0'; + return(buf); +} + +static void sq(struct socket *sk, int cont, ...) +{ + int num, freepart; + va_list al; + char *final; + wchar_t *buf; + wchar_t *part, *tpart; + size_t bufsize, bufdata; + + buf = NULL; + bufsize = bufdata = 0; + num = 0; + va_start(al, cont); + while((part = va_arg(al, wchar_t *)) != NULL) + { + if(*part == L'%') + { + /* + * This kludge demands that all arguments that you call it + * with are the size of an int. That happens to be the + * case for most datatypes on most platforms and + * compilers, but I don't know exactly which ones, and + * also a long long is a notable candidate of an arg that + * is not the size of an int on 32-bit archs. If it breaks + * some existing call on your architecture, please tell + * me. + */ + part = vswprintf2(tpart = (part + 1), al); + for(; *tpart != L'\0'; tpart++) + { + if(*tpart == L'%') + { + if(tpart[1] == L'%') + tpart++; + else + va_arg(al, int); + } + } + freepart = 1; + } else { + freepart = 0; + } + if((tpart = quoteword(part)) != NULL) + { + if(freepart) + free(part); + part = tpart; + freepart = 1; + } + if((num > 1) || ((num == 1) && !(cont & 1))) + addtobuf(buf, L' '); + bufcat(buf, part, wcslen(part)); + if((num == 0) && (cont & 1)) + addtobuf(buf, L'-'); + num++; + if(freepart) + free(part); + } + if(cont & 2) + bufcat(buf, L" \0", 2); + else + bufcat(buf, L"\r\n\0", 3); + if((final = icwcstombs(buf, "utf-8")) == NULL) + { + flog(LOG_CRIT, "could not convert \"%ls\" into utf-8: %s", buf, strerror(errno)); + free(buf); + return; + } + va_end(al); + free(buf); + sockqueue(sk, final, strlen(final)); + free(final); +} + +struct uiuser *finduser(wchar_t *name) +{ + struct uiuser *user; + + for(user = users; user != NULL; user = user->next) + { + if(!wcscmp(user->name, name)) + break; + } + return(user); +} + +static void logout(struct uidata *data) +{ + data->userinfo = NULL; + if(data->username != NULL) + free(data->username); + data->username = NULL; + if(data->auth != NULL) + authputhandle(data->auth); + data->auth = NULL; +} + +static int haspriv(struct uidata *data, int perm) +{ + if(data->userinfo == NULL) + return(0); + if(data->userinfo->perms & perm) + return(1); + else + return(0); +} + +/* Useful macros for the command functions: */ +#define haveargs(n) do { if(argc < n) { sq(sk, 0, L"501", L"Wrong number of arguments", NULL); return; } } while(0) +#define havepriv(p) do { if((data->userinfo == NULL) || ((data->userinfo->perms & (p)) != (p))) { sq(sk, 0, L"502", L"%Unauthorized request - %x needed", (p), NULL); return; } } while(0) + +static void cmd_connect(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + int valid; + + if(confgetint("ui", "onlylocal")) + { + switch(sk->remote->sa_family) + { + case AF_INET: + valid = ((struct sockaddr_in *)sk->remote)->sin_addr.s_addr == INADDR_LOOPBACK; + break; + case AF_INET6: + valid = !memcmp(&((struct sockaddr_in6 *)sk->remote)->sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback)); + break; + default: + valid = 0; + break; + } + if(!valid) + { + sq(sk, 0, L"502", L"Only localhost connections allowed to this host", NULL); + sk->close = 1; + data->close = 1; + return; + } + } + sq(sk, 0, L"200", L"%Dolda Connect daemon v%s", VERSION, NULL); +} + +static void cmd_notfound(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + if((argv != NULL) && (argv[0] != NULL)) + sq(sk, 0, L"500", L"%Command not found: %ls", argv[0], NULL); + else + sq(sk, 0, L"500", L"No command", NULL); +} + +static void cmd_shutdown(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + extern volatile int running; + + havepriv(PERM_ADMIN); + flog(LOG_NOTICE, "UI shutdown request from %ls, shutting down", data->username); + running = 0; + sq(sk, 0, L"200", L"Daemon shutting down", NULL); +} + +static void cmd_quit(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + sq(sk, 0, L"200", L"Closing connection", NULL); + data->close = 1; +} + +static void cmd_lsauth(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct authmech *mech, *prev; + + prev = NULL; + for(mech = mechs; mech != NULL; mech = mech->next) + { + if(mech->enabled) + { + if(prev != NULL) + sq(sk, 1, L"200", prev->name, NULL); + prev = mech; + } + } + if(prev == NULL) + sq(sk, 0, L"201", L"No authentication methods supported", NULL); + else + sq(sk, 0, L"200", prev->name, NULL); +} + +static void cmd_login(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + char *buf; + int code; + struct passwd *pwd; + + haveargs(3); + if(data->username != NULL) + { + if(data->userinfo != NULL) + sq(sk, 0, L"503", L"Already logged in", NULL); + else + sq(sk, 0, L"503", L"Already logging in", NULL); + return; + } + if((buf = icwcstombs(argv[2], NULL)) == NULL) + { + sq(sk, 0, L"504", L"Could not convert username to locale charset", NULL); + return; + } + data->username = swcsdup(argv[2]); + if((pwd = getpwnam(buf)) == NULL) + data->uid = -1; + else + data->uid = pwd->pw_uid; + if((data->auth = initauth(argv[1], buf)) == NULL) + { + if(errno == ENOENT) + sq(sk, 0, L"508", L"No such authentication mechanism", NULL); + else + sq(sk, 0, L"505", L"Could not initialize authentication system", L"%%s", strerror(errno), NULL); + free(buf); + logout(data); + return; + } + free(buf); + switch(authenticate(data->auth, NULL)) + { + case AUTH_SUCCESS: + data->userinfo = finduser(data->username); + if(data->userinfo == NULL) + data->userinfo = finduser(L"default"); + if(data->uid == -1) + { + sq(sk, 0, L"506", L"Authentication error", NULL); + flog(LOG_INFO, "user %ls authenticated successfully, but no account existed", data->username); + logout(data); + } else if((data->userinfo == NULL) || (data->userinfo->perms & PERM_DISALLOW)) { + sq(sk, 0, L"506", L"Authentication error", NULL); + flog(LOG_INFO, "user %ls authenticated successfully, but was not authorized", data->username); + logout(data); + } else { + sq(sk, 0, L"200", L"Welcome", NULL); + flog(LOG_INFO, "%ls (UID %i) logged in", data->username, data->uid); + } + break; + case AUTH_DENIED: + sq(sk, 0, L"506", L"Authentication error", L"%%ls", (data->auth->text == NULL)?L"":(data->auth->text), NULL); + logout(data); + break; + case AUTH_PASS: + switch(data->auth->prompt) + { + case AUTH_PR_AUTO: + code = 300; + break; + case AUTH_PR_NOECHO: + code = 301; + break; + case AUTH_PR_ECHO: + code = 302; + break; + case AUTH_PR_INFO: + code = 303; + break; + case AUTH_PR_ERROR: + code = 304; + break; + } + sq(sk, 0, L"%%i", code, data->auth->text, NULL); + break; + case AUTH_ERR: + sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL); + logout(data); + break; + default: + flog(LOG_WARNING, "BUG? Non-caught return from authenticate in cmd_login"); + sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL); + logout(data); + break; + } +} + +static void cmd_pass(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + char *buf; + int code; + + haveargs(2); + if((buf = icwcstombs(argv[1], NULL)) == NULL) + { + sq(sk, 0, L"504", L"Could not convert data to locale charset", NULL); + return; + } + if((data->auth == NULL) || (data->userinfo != NULL)) + { + sq(sk, 0, L"507", L"Data not expected", NULL); + return; + } + switch(authenticate(data->auth, buf)) + { + case AUTH_SUCCESS: + data->userinfo = finduser(data->username); + if(data->userinfo == NULL) + data->userinfo = finduser(L"default"); + if(data->uid == -1) + { + sq(sk, 0, L"506", L"Authentication error", NULL); + flog(LOG_INFO, "user %ls authenticated successfully, but no account existed", data->username); + logout(data); + } else if((data->userinfo == NULL) || (data->userinfo->perms & PERM_DISALLOW)) { + sq(sk, 0, L"506", L"Authentication error", NULL); + flog(LOG_INFO, "user %ls authenticated successfully, but was not authorized", data->username); + logout(data); + } else { + sq(sk, 0, L"200", L"Welcome", NULL); + flog(LOG_INFO, "%ls (UID %i) logged in", data->username, data->uid); + } + break; + case AUTH_DENIED: + sq(sk, 0, L"506", L"Authentication error", L"%%ls", (data->auth->text == NULL)?L"":(data->auth->text), NULL); + logout(data); + break; + case AUTH_PASS: + switch(data->auth->prompt) + { + case AUTH_PR_AUTO: + code = 300; + break; + case AUTH_PR_NOECHO: + code = 301; + break; + case AUTH_PR_ECHO: + code = 302; + break; + case AUTH_PR_INFO: + code = 303; + break; + case AUTH_PR_ERROR: + code = 304; + break; + } + sq(sk, 0, L"%%i", code, data->auth->text, NULL); + break; + case AUTH_ERR: + sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL); + logout(data); + break; + default: + flog(LOG_WARNING, "BUG? Non-caught return from authenticate in cmd_pass"); + sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL); + logout(data); + break; + } + free(buf); +} + +static void cmd_fnetconnect(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + int i; + char *buf; + int err; + struct fnetnode *fn; + + haveargs(3); + havepriv(PERM_FNETCTL); + if((buf = icwcstombs(argv[2], NULL)) == NULL) + { + sq(sk, 0, L"504", L"Could not convert data to locale charset", NULL); + return; + } + fn = fnetinitconnect(argv[1], buf); + err = errno; + free(buf); + if(fn == NULL) + { + if(errno == EPROTONOSUPPORT) + sq(sk, 0, L"511", L"No such network name", NULL); + else + sq(sk, 0, L"509", L"Could not parse the address", L"%%s", strerror(err), NULL); + return; + } + for(i = 3; i < argc - 1; i += 2) + { + if(!wcscmp(argv[i], L"nick")) + fnetsetnick(fn, argv[i + 1]); + } + linkfnetnode(fn); + fnetsetname(fn, argv[2]); + putfnetnode(fn); + sq(sk, 0, L"200", L"Connection under way", NULL); +} + +static void cmd_lsnodes(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct fnetnode *fn; + + if(fnetnodes == NULL) + { + sq(sk, 0, L"201", L"No connected nodes", NULL); + return; + } + for(fn = fnetnodes; fn != NULL; fn = fn->next) + { + sq(sk, (fn->next != NULL)?1:0, L"200", L"%%i", fn->id, fn->fnet->name, (fn->name == NULL)?L"":fn->name, L"%%i", fn->numpeers, L"%%i", fn->state, NULL); + } +} + +static void cmd_disconnect(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct fnetnode *fn; + int i; + + haveargs(2); + havepriv(PERM_FNETCTL); + /* Note - Programmatical user interfaces must only give one + * argument per command, the multiple argument form is only for + * convenience when manually controlling the daemon via + * eg. telnet. The reason is that the return codes aren't clear + * enough for the multiple argument form. */ + for(i = 1; i < argc; i++) + { + if((fn = findfnetnode(wcstol(argv[i], NULL, 0))) == NULL) + { + sq(sk, 0, L"510", L"No such node", NULL); + return; + } + killfnetnode(fn); + unlinkfnetnode(fn); + } + sq(sk, 0, L"200", L"Node flagged for disconnection", NULL); +} + +static void cmd_lspa(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct fnetnode *fn; + struct fnetpeerdatum *datum; + + haveargs(2); + if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL) + { + sq(sk, 0, L"510", L"No such node", NULL); + return; + } + if(fn->peerdata == NULL) + { + sq(sk, 0, L"201", L"No data available", NULL); + } else { + for(datum = fn->peerdata; datum != NULL; datum = datum->next) + sq(sk, (datum->next != NULL)?1:0, L"200", datum->id, L"%%i", datum->datatype, NULL); + } +} + +static void cmd_lspeers(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + int i; + struct fnetnode *fn; + struct fnetpeer *peer; + wchar_t buf[40]; + + haveargs(2); + if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL) + { + sq(sk, 0, L"510", L"No such node", NULL); + return; + } + if(fn->peers == NULL) + { + sq(sk, 0, L"201", L"No peers avaiable", NULL); + } else { + for(peer = fn->peers; peer != NULL; peer = peer->next) + { + sq(sk, 2 | ((peer->next != NULL)?1:0), L"200", peer->id, peer->nick, NULL); + for(i = 0; i < peer->dinum; i++) + { + if(peer->peerdi[i].datum->datatype == FNPD_INT) + sq(sk, 2, peer->peerdi[i].datum->id, L"%%i", peer->peerdi[i].data.num, NULL); + /* Note: A long long is not the size of an int, so + * sq() can't handle the conversion itself. */ + if(peer->peerdi[i].datum->datatype == FNPD_LL) + { + swprintf(buf, 40, L"%lli", peer->peerdi[i].data.lnum); + sq(sk, 2, peer->peerdi[i].datum->id, buf, NULL); + } + if((peer->peerdi[i].datum->datatype == FNPD_STR) && (peer->peerdi[i].data.str != NULL)) + sq(sk, 2, peer->peerdi[i].datum->id, peer->peerdi[i].data.str, NULL); + } + sq(sk, 0, NULL); + } + } +} + +static void cmd_download(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + int i; + struct fnet *net; + struct fnetnode *fn; + struct transfer *transfer; + struct fnetpeer *peer; + + haveargs(4); + if((argc > 5) && ((argc % 2) == 0)) + { + sq(sk, 0, L"501", L"Must have an even number of arguments", NULL); + return; + } + havepriv(PERM_TRANS); + if((*(argv[1]) >= L'0') && (*(argv[1]) <= L'9')) + { + if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL) + { + sq(sk, 0, L"510", L"No such node", NULL); + return; + } + net = fn->fnet; + } else { + fn = NULL; + if((net = findfnet(argv[1])) == NULL) + { + sq(sk, 0, L"511", L"No such network name", NULL); + return; + } + } + transfer = newtransfer(); + authgethandle(transfer->auth = data->auth); + transfer->fnet = net; + transfer->peerid = swcsdup(argv[2]); + transfer->path = swcsdup(argv[3]); + transfer->dir = TRNSD_DOWN; + transfer->owner = data->uid; + if(fn != NULL) + { + transfer->fn = fn; + getfnetnode(fn); + linktransfer(transfer); + if(((peer = fnetfindpeer(fn, transfer->peerid)) != NULL) && (peer->nick != NULL)) + transfersetnick(transfer, peer->nick); + } else { + linktransfer(transfer); + } + if(argc > 4) + transfersetsize(transfer, wcstol(argv[4], NULL, 0)); + if(argc > 5) + { + for(i = 5; i < argc; i += 2) + transferaddarg(transfer, argv[i], argv[i + 1]); + } + sq(sk, 0, L"200", L"%%i", transfer->id, L"Download queued", NULL); + transfersetactivity(transfer, L"create"); +} + +static void cmd_lstrans(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct transfer *transfer, *pt; + + havepriv(PERM_TRANS); + pt = NULL; + for(transfer = transfers; transfer != NULL; transfer = transfer->next) + { + if((transfer->dir != TRNSD_DOWN) || (transfer->owner == data->uid)) + { + if(pt != NULL) + sq(sk, 1, L"200", L"%%i", pt->id, L"%%i", pt->dir, + L"%%i", pt->state, pt->peerid, + (pt->peernick == NULL)?L"":(pt->peernick), + (pt->path == NULL)?L"":(pt->path), + L"%%i", pt->size, L"%%i", pt->curpos, + NULL); + pt = transfer; + } + } + if(pt == NULL) + sq(sk, 0, L"201", L"No transfers", NULL); + else + sq(sk, 0, L"200", L"%%i", pt->id, L"%%i", pt->dir, + L"%%i", pt->state, pt->peerid, + (pt->peernick == NULL)?L"":(pt->peernick), + (pt->path == NULL)?L"":(pt->path), + L"%%i", pt->size, L"%%i", pt->curpos, + NULL); +} + +static void cmd_cancel(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct transfer *transfer; + + haveargs(2); + havepriv(PERM_TRANS); + if((transfer = findtransfer(wcstol(argv[1], NULL, 0))) == NULL) + { + sq(sk, 0, L"512", L"No such transfer", NULL); + return; + } + if((transfer->dir == TRNSD_UP) && !(data->userinfo->perms & PERM_TRANSCU)) + { + sq(sk, 0, L"502", L"You are not allowed to cancel uploads", NULL); + return; + } + if((transfer->dir == TRNSD_DOWN) && (transfer->owner != data->uid)) + { + sq(sk, 0, L"502", L"You do not own that transfer", NULL); + return; + } + transfer->close = 1; + sq(sk, 0, L"200", L"Transfer cancelled", NULL); +} + +static void cmd_notify(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + int i, val; + + if((argc % 2) != 1) + { + sq(sk, 0, L"501", L"Must have an even number of arguments", NULL); + return; + } + for(i = 1; i < argc; i += 2) + { + if(!wcscasecmp(argv[i + 1], L"on")) + val = 1; + else + val = 0; + if(!wcscasecmp(argv[i], L"all")) + { + if(val) + data->notify.w = ~0; + else + data->notify.w = 0; + } else if(!wcscasecmp(argv[i], L"fn:chat")) { + data->notify.b.fnchat = val; + } else if(!wcscasecmp(argv[i], L"fn:act")) { + data->notify.b.fnact = val; + } else if(!wcscasecmp(argv[i], L"trans:act")) { + data->notify.b.tract = val; + } else if(!wcscasecmp(argv[i], L"trans:prog")) { + data->notify.b.trprog = val; + } else if(!wcscasecmp(argv[i], L"srch:act")) { + data->notify.b.srch = val; + } + } + sq(sk, 0, L"200", L"Notification alteration succeeded", NULL); +} + +static void cmd_sendchat(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct fnetnode *fn; + int public; + + haveargs(5); + havepriv(PERM_CHAT); + if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL) + { + sq(sk, 0, L"510", L"No such node", NULL); + return; + } + public = wcstol(argv[2], NULL, 0); + if((public != 0) && (public != 1)) + { + sq(sk, 0, L"509", L"Second argument must be 0 or 1", NULL); + return; + } + if(fn->state != FNN_EST) + { + sq(sk, 0, L"513", L"Hub is in state FNN_EST", NULL); + return; + } + if(fnetsendchat(fn, public, argv[3], argv[4])) + { + if(errno == ENOTSUP) + sq(sk, 0, L"513", L"This network does not support chatting", NULL); + else if(errno == EPERM) + sq(sk, 0, L"502", L"This node does not allow you to chat", NULL); + else if(errno == EILSEQ) + sq(sk, 0, L"504", L"This network could not support all the characters in that message", NULL); + else + sq(sk, 0, L"505", L"Could not chat", NULL); + return; + } + sq(sk, 0, L"200", L"Chat string sent", NULL); +} + +static void cmd_search(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct search *srch; + struct fnetnode *fn; + struct sexpr *sexpr; + int i; + + haveargs(3); + havepriv(PERM_SRCH); + srch = newsearch(data->username, NULL); + for(i = 1; i < argc; i++) + { + if(!wcscmp(argv[i], L"all")) + { + for(fn = fnetnodes; fn != NULL; fn = fn->next) + { + if(fn->state == FNN_EST) + searchaddfn(srch, fn); + } + i++; + break; + } else if(!wcscmp(argv[i], L"prio")) { + if(++i == argc) + { + sq(sk, 0, L"501", L"No argument to prio", NULL); + freesearch(srch); + return; + } + srch->prio = wcstol(argv[i], NULL, 0); + } else if(iswdigit(*argv[i])) { + if((fn = findfnetnode(wcstol(argv[i], NULL, 0))) == NULL) + { + sq(sk, 0, L"510", L"No such node", NULL); + freesearch(srch); + return; + } + searchaddfn(srch, fn); + } else { + break; + } + } + if(srch->fnl == NULL) + { + sq(sk, 0, L"501", L"No fnetnodes to search found on line", NULL); + freesearch(srch); + return; + } + if(i == argc) + { + sq(sk, 0, L"501", L"No search expression found on line", NULL); + freesearch(srch); + return; + } + if((sexpr = parsesexpr(argc - i, argv + i)) == NULL) + { + sq(sk, 0, L"509", L"Could not parse search expression", NULL); + freesearch(srch); + return; + } + optsexpr(sexpr); + getsexpr(srch->sexpr = sexpr); + queuesearch(srch); + CBREG(srch, search_eta, srcheta, NULL, NULL); + CBREG(srch, search_commit, srchcommit, NULL, NULL); + CBREG(srch, search_result, srchres, NULL, NULL); + sq(sk, 0, L"200", L"%%i", srch->id, L"%%i", srch->eta - time(NULL), NULL); + putsexpr(sexpr); +} + +static void cmd_lssrch(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct search *srch, *pt; + time_t now; + + havepriv(PERM_SRCH); + pt = NULL; + now = time(NULL); + for(srch = searches; srch != NULL; srch = srch->next) + { + if(!wcscmp(srch->owner, data->username)) + { + if(pt != NULL) + sq(sk, 1, L"200", L"%%i", pt->id, L"%%i", pt->state, L"%%i", pt->eta - now, L"%%i", pt->numres, NULL); + pt = srch; + } + } + if(pt == NULL) + sq(sk, 0, L"201", L"No searches", NULL); + else + sq(sk, 0, L"200", L"%%i", pt->id, L"%%i", pt->state, L"%%i", pt->eta - now, L"%%i", pt->numres, NULL); +} + +static void cmd_lssr(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct search *srch; + struct srchres *sr; + wchar_t buf[64]; + + haveargs(2); + havepriv(PERM_SRCH); + if((srch = findsearch(wcstol(argv[1], NULL, 0))) == NULL) + { + sq(sk, 0, L"514", L"No such search", NULL); + return; + } + if(srch->results == NULL) + { + sq(sk, 0, L"201", L"No results", NULL); + } else { + for(sr = srch->results; sr != NULL; sr = sr->next) + { + swprintf(buf, 64, L"%f", sr->time); + sq(sk, (sr->next != NULL)?1:0, L"200", L"%%ls", sr->filename, sr->fnet->name, L"%%ls", sr->peerid, L"%%i", sr->size, L"%%i", sr->slots, L"%%i", (sr->fn == NULL)?-1:(sr->fn->id), buf, NULL); + } + } +} + +static void cmd_cansrch(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct search *srch; + int i; + + haveargs(2); + havepriv(PERM_SRCH); + /* Note - Programmatical user interfaces must only give one + * argument per command, the multiple argument form is only for + * convenience when manually controlling the daemon via + * eg. telnet. The reason is that the return codes aren't clear + * enough for the multiple argument form. */ + for(i = 1; i < argc; i++) + { + if((srch = findsearch(wcstol(argv[i], NULL, 0))) == NULL) + { + sq(sk, 0, L"514", L"No such search", NULL); + return; + } + freesearch(srch); + } + sq(sk, 0, L"200", L"Search cancelled", NULL); +} + +static void fcmdread(struct socket *sk, struct uidata *data) +{ + char *buf; + size_t bufsize; + + if((buf = sockgetinbuf(sk, &bufsize)) == NULL) + return; + bufcat(data->fcmdbuf, buf, bufsize); + free(buf); +} + +static void fcmderr(struct socket *sk, int err, struct uidata *data) +{ + wchar_t *wbuf, *p, *p2; + + if(err) + { + flog(LOG_WARNING, "error occurred on filtercmd pipe socket: %s", strerror(err)); + kill(-data->fcmdpid, SIGHUP); + putsock(data->fcmdsk); + data->fcmdsk = NULL; + if(data->fcmdbuf != NULL) + { + free(data->fcmdbuf); + data->fcmdbuf = NULL; + } + data->fcmdbufsize = data->fcmdbufdata = 0; + sq(data->sk, 0, L"505", L"An error occurred on the pipe to the filtercmd", L"%%s", strerror(err), NULL); + return; + } + putsock(data->fcmdsk); + data->fcmdsk = NULL; + data->fcmdpid = 0; + if(data->fcmdbuf == NULL) + { + wbuf = swcsdup(L""); + } else { + addtobuf(data->fcmdbuf, 0); + wbuf = icmbstowcs(data->fcmdbuf, NULL); + free(data->fcmdbuf); + } + data->fcmdbuf = NULL; + data->fcmdbufsize = data->fcmdbufdata = 0; + if(wbuf == NULL) + { + sq(data->sk, 0, L"504", L"Filtercmd sent data which could not be converted from the local charset", NULL); + return; + } + p = wbuf; + for(p2 = wcschr(p, L'\n'); p2 != NULL; p2 = wcschr(p, L'\n')) + { + *(p2++) = L'\0'; + sq(data->sk, (*p2 == L'\0')?0:1, L"200", L"%%ls", p, NULL); + p = p2; + } + if(*p == L'\0') + { + if(p == wbuf) + sq(data->sk, 0, L"201", L"No data returned", NULL); + } else { + sq(data->sk, 0, L"200", L"%%ls", p, NULL); + } + free(wbuf); +} + +static void cmd_filtercmd(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + int i; + pid_t pid; + int pipe; + char **cargv, **pp; + char *filtercmd, *argbuf; + size_t cargvsize, cargvdata; + struct passwd *pwent; + + haveargs(2); + havepriv(PERM_TRANS); + if((pwent = getpwuid(data->uid)) == NULL) + { + flog(LOG_WARNING, "no passwd entry for UI user %i", data->uid); + sq(sk, 0, L"505", L"System error - Could not fork session", "Internal error", NULL); + return; + } + if((filtercmd = findfile(icswcstombs(confgetstr("ui", "filtercmd"), NULL, NULL), "dcdl-filtercmd", pwent->pw_dir)) == NULL) + { + flog(LOG_WARNING, "could not find filtercmd executable for user %s", pwent->pw_name); + sq(sk, 0, L"505", L"System error - Could not fork session", L"Could not find filtercmd executable", NULL); + return; + } + cargv = NULL; + cargvsize = cargvdata = 0; + addtobuf(cargv, filtercmd); + for(i = 1; i < argc; i++) + { + if((argbuf = icwcstombs(argv[i], NULL)) == NULL) + { + for(i = 0; i < cargvdata; i++) + free(cargv[i]); + free(cargv); + sq(sk, 0, L"504", L"%Could not convert argument %i into local character set", i, L"%%s", strerror(errno), NULL); + return; + } + addtobuf(cargv, argbuf); + } + addtobuf(cargv, NULL); + if((pid = forksess(data->uid, data->auth, NULL, NULL, FD_FILE, 0, O_RDWR, "/dev/null", FD_PIPE, 1, O_RDONLY, &pipe, FD_FILE, 2, O_RDWR, "/dev/null", FD_END)) < 0) + { + flog(LOG_WARNING, "could not fork session in filtercmd: %s", strerror(errno)); + sq(sk, 0, L"505", L"System error - Could not fork session", L"%%s", strerror(errno), NULL); + return; + } + if(pid == 0) + { + execv(filtercmd, cargv); + flog(LOG_WARNING, "could not exec filtercmd %s: %s", filtercmd, strerror(errno)); + exit(127); + } + for(pp = cargv; *pp; pp++) + free(*pp); + free(cargv); + data->fcmdsk = wrapsock(pipe); + data->fcmdpid = pid; + if(data->fcmdbuf != NULL) + { + free(data->fcmdbuf); + data->fcmdbuf = NULL; + } + data->fcmdbufsize = data->fcmdbufdata = 0; + data->fcmdsk->data = data; + data->fcmdsk->readcb = (void (*)(struct socket *, void *))fcmdread; + data->fcmdsk->errcb = (void (*)(struct socket *, int, void *))fcmderr; +} + +static void cmd_lstrarg(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct transfer *transfer; + struct transarg *ta; + + haveargs(2); + havepriv(PERM_TRANS); + if((transfer = findtransfer(wcstol(argv[1], NULL, 0))) == NULL) + { + sq(sk, 0, L"512", L"No such transfer", NULL); + return; + } + if((transfer->dir == TRNSD_DOWN) && (transfer->owner != data->uid)) + { + sq(sk, 0, L"502", L"You do not own that transfer", NULL); + return; + } + if(transfer->args == NULL) + { + sq(sk, 0, L"201", L"Transfer has no arguments", NULL); + } else { + for(ta = transfer->args; ta != NULL; ta = ta->next) + sq(sk, ta->next != NULL, L"200", L"%%ls", ta->rec, L"%%ls", ta->val, NULL); + } +} + +static void cmd_hashstatus(struct socket *sk, struct uidata *data, int argc, wchar_t **argv) +{ + struct sharecache *node; + int total, hashed; + + total = hashed = 0; + for(node = shareroot->child; node != NULL; node = nextscnode(node)) + { + if(node->f.b.type == FILE_REG) + { + total++; + if(node->f.b.hastth) + hashed++; + } + } + sq(sk, 0, L"200", L"%%i", total, L"tth", L"%%i", hashed, NULL); +} + +#undef haveargs +#undef havepriv + +/* + * Reserved command numbers for nameless commands: + * 0: Issued when a client has connected + * 1: Issued when a named command couldn't be found + */ + +static struct command commands[] = +{ + {NULL, cmd_connect}, + {NULL, cmd_notfound}, + {L"shutdown", cmd_shutdown}, + {L"quit", cmd_quit}, + {L"lsauth", cmd_lsauth}, + {L"login", cmd_login}, + {L"pass", cmd_pass}, + {L"cnct", cmd_fnetconnect}, + {L"lsnodes", cmd_lsnodes}, + {L"dcnct", cmd_disconnect}, + {L"lspa", cmd_lspa}, + {L"lspeers", cmd_lspeers}, + {L"download", cmd_download}, + {L"lstrans", cmd_lstrans}, + {L"cancel", cmd_cancel}, + {L"notify", cmd_notify}, + {L"sendchat", cmd_sendchat}, + {L"search", cmd_search}, + {L"lssrch", cmd_lssrch}, + {L"lssr", cmd_lssr}, + {L"cansrch", cmd_cansrch}, + {L"filtercmd", cmd_filtercmd}, + {L"lstrarg", cmd_lstrarg}, + {L"hashstatus", cmd_hashstatus}, + {NULL, NULL} +}; + +static void freequeuecmd(struct qcommand *qcmd) +{ + int i; + + if(qcmd->argv != NULL) + { + for(i = 0; i < qcmd->argc; i++) + free(qcmd->argv[i]); + free(qcmd->argv); + } + free(qcmd); +} + +static struct qcommand *unlinkqcmd(struct uidata *data) +{ + struct qcommand *qcmd; + + qcmd = data->queue; + if(qcmd != NULL) + { + data->queue = qcmd->next; + if(qcmd == data->queuelast) + data->queuelast = qcmd->next; + } + return(qcmd); +} + +static struct notif *newnotif(struct uidata *data, int code, ...) +{ + struct notif *notif; + va_list args; + int dt, ca; + + notif = smalloc(sizeof(*notif)); + memset(notif, 0, sizeof(*notif)); + notif->rlimit = 0.0; + notif->ui = data; + notif->code = code; + va_start(args, code); + while((dt = va_arg(args, int)) != NOTIF_END) + { + ca = notif->argc; + notif->argv = realloc(notif->argv, sizeof(*notif->argv) * ++notif->argc); + notif->argv[ca].dt = dt; + switch(dt) + { + case NOTIF_INT: + case NOTIF_ID: + notif->argv[ca].d.n = va_arg(args, int); + break; + case NOTIF_STR: + notif->argv[ca].d.s = wcsdup(va_arg(args, wchar_t *)); + break; + case NOTIF_FLOAT: + notif->argv[ca].d.d = va_arg(args, double); + break; + } + } + va_end(args); + notif->next = NULL; + notif->prev = data->lnotif; + if(data->lnotif != NULL) + data->lnotif->next = notif; + else + data->fnotif = notif; + data->lnotif = notif; + return(notif); +} + +static void freenotif(struct notif *notif) +{ + int i; + + if(notif->next != NULL) + notif->next->prev = notif->prev; + if(notif->prev != NULL) + notif->prev->next = notif->next; + if(notif == notif->ui->fnotif) + notif->ui->fnotif = notif->next; + if(notif == notif->ui->lnotif) + notif->ui->lnotif = notif->prev; + if(notif->exptimer != NULL) + canceltimer(notif->exptimer); + for(i = 0; i < notif->argc; i++) + { + if(notif->argv[i].dt == NOTIF_STR) + free(notif->argv[i].d.s); + } + if(notif->argv != NULL) + free(notif->argv); + free(notif); +} + +static void notifexpire(int cancelled, struct notif *notif) +{ + notif->exptimer = NULL; + if(!cancelled) + freenotif(notif); +} + +static struct notif *findnotif(struct notif *notif, int dir, int state, int code, int id) +{ + int i, cont; + + for(; notif != NULL; notif = (dir?notif->next:notif->prev)) + { + if((notif->code == code) && ((state < 0) || (state == notif->state))) + { + cont = 0; + if(id >= 0) + { + for(i = 0; i < notif->argc; i++) + { + if((notif->argv[i].dt == NOTIF_ID) && (notif->argv[i].d.n != id)) + { + cont = 1; + break; + } + } + } + if(cont) + continue; + break; + } + } + return(notif); +} + +static void freeuidata(struct uidata *data) +{ + int i; + struct qcommand *qcmd; + + if(data->next != NULL) + data->next->prev = data->prev; + if(data->prev != NULL) + data->prev->next = data->next; + if(data == actives) + actives = data->next; + data->sk->readcb = NULL; + data->sk->errcb = NULL; + putsock(data->sk); + while((qcmd = unlinkqcmd(data)) != NULL) + freequeuecmd(qcmd); + iconv_close(data->ichandle); + if(data->cw != NULL) + free(data->cw); + if(data->cb != NULL) + free(data->cb); + if(data->argv != NULL) + { + for(i = 0; i < data->argc; i++) + free(data->argv[i]); + free(data->argv); + } + if(data->auth != NULL) + authputhandle(data->auth); + if(data->username != NULL) + { + if(data->userinfo != NULL) + flog(LOG_INFO, "%ls logged out", data->username); + free(data->username); + } + free(data->inbuf); + while(data->fnotif != NULL) + freenotif(data->fnotif); + if(data->fcmdbuf != NULL) + free(data->fcmdbuf); + if(data->fcmdpid != 0) + kill(-data->fcmdpid, SIGHUP); + if(data->fcmdsk != NULL) + putsock(data->fcmdsk); + free(data); +} + +static void queuecmd(struct uidata *data, struct command *cmd, int argc, wchar_t **argv) +{ + struct qcommand *new; + + new = smalloc(sizeof(*new)); + new->cmd = cmd; + new->argc = argc; + new->argv = argv; + new->next = NULL; + if(data->queuelast != NULL) + data->queuelast->next = new; + data->queuelast = new; + if(data->queue == NULL) + data->queue = new; +} + +static struct uidata *newuidata(struct socket *sk) +{ + struct uidata *data; + + data = smalloc(sizeof(*data)); + memset(data, 0, sizeof(*data)); + data->sk = sk; + getsock(sk); + data->inbuf = smalloc(1024); + data->uid = -1; + if((data->ichandle = iconv_open("wchar_t", "utf-8")) == (iconv_t)-1) + { + flog(LOG_CRIT, "iconv cannot handle UTF-8: %s", strerror(errno)); + return(NULL); + } + data->next = actives; + data->prev = NULL; + if(actives != NULL) + actives->prev = data; + actives = data; + return(data); +} + +static void uiread(struct socket *sk, struct uidata *data) +{ + int ret, done; + char *newbuf; + char *p1, *p2; + wchar_t *porig; + size_t datalen, len2; + struct command *cur; + + if(data->indata > 1024) + data->indata = 0; + if((newbuf = sockgetinbuf(sk, &datalen)) == NULL) + return; + sizebuf(&data->inbuf, &data->inbufsize, data->indata + datalen, 1, 1); + memcpy(data->inbuf + data->indata, newbuf, datalen); + free(newbuf); + data->indata += datalen; + if(data->cb == NULL) + { + data->cb = smalloc(sizeof(wchar_t) * (data->cbsize = 64)); + data->cbdata = 0; + data->pp = data->cb; + } + done = 0; + while(!done) + { + if(data->cbsize == data->cbdata) + { + len2 = data->pp - data->cb; + data->cb = srealloc(data->cb, sizeof(wchar_t) * (data->cbsize *= 2)); + data->pp = data->cb + len2; + } + p1 = data->inbuf; + p2 = (char *)(porig = (data->cb + data->cbdata)); + len2 = sizeof(wchar_t) * (data->cbsize - data->cbdata); + ret = iconv(data->ichandle, &p1, &data->indata, &p2, &len2); + memmove(data->inbuf, p1, data->indata); + /* Just a sanity check */ + if(((p2 - ((char *)data->cb)) % sizeof(wchar_t)) != 0) + { + flog(LOG_CRIT, "Aiya! iconv does strange things to our wchar_t's!"); + abort(); + } + data->cbdata += (((wchar_t *)p2) - porig); + if(ret < 0) + { + switch(errno) + { + case EILSEQ: + /* XXX: Should this really just ignore it? */ + data->indata = 0; + done = 1; + break; + case EINVAL: + done = 1; + break; + case E2BIG: + /* Just a sanity check */ + if(data->cbsize != data->cbdata) + { + flog(LOG_CRIT, "Aiya! iconv doesn't give us wchar_t's!"); + abort(); + } + break; + default: + flog(LOG_WARNING, "bug: strange error from iconv in uiread: %s", strerror(errno)); + break; + } + } else { + done = 1; + } + } + done = 0; + while(!done && (data->pp - data->cb < data->cbdata)) + { + switch(data->ps) + { + case 0: + if(iswspace(*data->pp)) + { + if(*data->pp == L'\r') + { + if(data->pp == data->cb + data->cbdata - 1) + { + done = 1; + break; + } + if(*(++data->pp) == L'\n') + { + if((data->argv != NULL) && (data->argv[0] != NULL)) + { + for(cur = commands; cur->handler != NULL; cur++) + { + if(cur->name == NULL) + continue; + if(!wcscasecmp(cur->name, data->argv[0])) + { + queuecmd(data, cur, data->argc, data->argv); + break; + } + } + if(cur->handler == NULL) + queuecmd(data, &commands[1], data->argc, data->argv); + } else { + queuecmd(data, &commands[1], data->argc, data->argv); + } + data->argv = NULL; + data->args = 0; + data->argc = 0; + wmemmove(data->cb, data->pp, data->cbdata -= (data->pp - data->cb)); + data->pp = data->cb; + } else { + data->pp++; + } + } else { + data->pp++; + } + } else { + data->ps = 1; + data->cwdata = 0; + } + break; + case 1: + if(iswspace(*data->pp)) + { + addtobuf(data->cw, L'\0'); + sizebuf(&data->argv, &data->args, data->argc + 1, sizeof(*data->argv), 1); + data->argv[data->argc++] = data->cw; + data->cw = NULL; + data->cwsize = 0; + data->cwdata = 0; + data->ps = 0; + } else if(*data->pp == L'\"') { + data->ps = 2; + data->pp++; + } else if(*data->pp == L'\\') { + if(data->pp == data->cb + data->cbdata - 1) + { + done = 1; + break; + } + addtobuf(data->cw, *(++data->pp)); + data->pp++; + } else { + addtobuf(data->cw, *(data->pp++)); + } + break; + case 2: + if(*data->pp == L'\"') + { + data->ps = 1; + } else if(*data->pp == L'\\') { + if(data->pp == data->cb + data->cbdata - 1) + { + done = 1; + break; + } + addtobuf(data->cw, *(++(data->pp))); + } else { + addtobuf(data->cw, *data->pp); + } + data->pp++; + break; + } + } +} + +static void uierror(struct socket *sk, int err, struct uidata *data) +{ + if(err) + flog(LOG_WARNING, "error occurred on UI socket: %s", strerror(err)); + freeuidata(data); +} + +static void uiaccept(struct socket *sk, struct socket *newsk, void *data) +{ + struct uidata *uidata; + + newsk->data = uidata = newuidata(newsk); + socksettos(newsk, confgetint("ui", "uitos")); + if(uidata == NULL) + return; + newsk->errcb = (void (*)(struct socket *, int, void *))uierror; + newsk->readcb = (void (*)(struct socket *, void *))uiread; + queuecmd(uidata, &commands[0], 0, NULL); +} + +static int srcheta(struct search *srch, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_SRCH) && data->notify.b.srch && !wcscmp(srch->owner, data->username)) + newnotif(data, 620, NOTIF_ID, srch->id, NOTIF_INT, srch->eta - time(NULL), NOTIF_END); + } + return(0); +} + +static int srchcommit(struct search *srch, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_SRCH) && data->notify.b.srch && !wcscmp(srch->owner, data->username)) + newnotif(data, 621, NOTIF_ID, srch->id, NOTIF_END); + } + return(0); +} + +static int srchres(struct search *srch, struct srchres *sr, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_SRCH) && data->notify.b.srch && !wcscmp(srch->owner, data->username)) + newnotif(data, 622, NOTIF_ID, srch->id, NOTIF_STR, sr->filename, NOTIF_STR, sr->fnet->name, NOTIF_STR, sr->peerid, NOTIF_INT, sr->size, NOTIF_INT, sr->slots, NOTIF_INT, (sr->fn == NULL)?-1:(sr->fn->id), NOTIF_FLOAT, sr->time, NOTIF_END); + } + return(0); +} + +static int recvchat(struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *string, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_CHAT) && data->notify.b.fnchat) + newnotif(data, 600, NOTIF_ID, fn->id, NOTIF_INT, public, NOTIF_STR, name, NOTIF_STR, peer, NOTIF_STR, string, NOTIF_END); + } + return(0); +} + +static int fnactive(struct fnetnode *fn, wchar_t *attrib, void *uudata) +{ + struct uidata *data; + struct notif *notif; + + if(!wcscmp(attrib, L"state")) + { + for(data = actives; data != NULL; data = data->next) + { + if(data->notify.b.fnact) + newnotif(data, 601, NOTIF_ID, fn->id, NOTIF_INT, fn->state, NOTIF_END); + } + } else if(!wcscmp(attrib, L"name")) { + for(data = actives; data != NULL; data = data->next) + { + if(data->notify.b.fnact) + newnotif(data, 602, NOTIF_ID, fn->id, NOTIF_STR, fn->name, NOTIF_END); + } + } else if(!wcscmp(attrib, L"numpeers")) { + for(data = actives; data != NULL; data = data->next) + { + if(data->notify.b.fnact) + { + if((notif = findnotif(data->fnotif, 1, NOTIF_PEND, 605, fn->id)) != NULL) + notif->argv[1].d.n = fn->numpeers; + else + newnotif(data, 605, NOTIF_ID, fn->id, NOTIF_INT, fn->numpeers, NOTIF_END); + } + } + } + return(0); +} + +static int fnunlink(struct fnetnode *fn, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(data->notify.b.fnact) + newnotif(data, 603, NOTIF_ID, fn->id, NOTIF_END); + } + return(0); +} + +static int newfnetnode(struct fnetnode *fn, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(data->notify.b.fnact) + newnotif(data, 604, NOTIF_ID, fn->id, NOTIF_STR, fn->fnet->name, NOTIF_END); + } + CBREG(fn, fnetnode_ac, fnactive, NULL, NULL); + CBREG(fn, fnetnode_chat, recvchat, NULL, NULL); + CBREG(fn, fnetnode_unlink, fnunlink, NULL, NULL); + return(0); +} + +static int transferchattr(struct transfer *transfer, wchar_t *attrib, void *uudata) +{ + struct uidata *data; + + if(!wcscmp(attrib, L"state")) + { + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid))) + newnotif(data, 611, NOTIF_ID, transfer->id, NOTIF_INT, transfer->state, NOTIF_END); + } + } else if(!wcscmp(attrib, L"nick")) { + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid))) + newnotif(data, 612, NOTIF_ID, transfer->id, NOTIF_STR, transfer->peernick, NOTIF_END); + } + } else if(!wcscmp(attrib, L"size")) { + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid))) + newnotif(data, 613, NOTIF_ID, transfer->id, NOTIF_INT, transfer->size, NOTIF_END); + } + } else if(!wcscmp(attrib, L"error")) { + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid))) + newnotif(data, 614, NOTIF_ID, transfer->id, NOTIF_INT, transfer->error, NOTIF_END); + } + } else if(!wcscmp(attrib, L"path")) { + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid))) + newnotif(data, 616, NOTIF_ID, transfer->id, NOTIF_STR, transfer->path, NOTIF_END); + } + } + return(0); +} + +static int transferprog(struct transfer *transfer, void *uudata) +{ + struct uidata *data; + struct notif *notif; + + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.trprog && ((transfer->owner == 0) || (transfer->owner == data->uid))) + { + if((notif = findnotif(data->fnotif, 1, NOTIF_PEND, 615, transfer->id)) != NULL) + notif->argv[1].d.n = transfer->curpos; + else + newnotif(data, 615, NOTIF_ID, transfer->id, NOTIF_INT, transfer->curpos, NOTIF_END)->rlimit = 0.5; + } + } + return(0); +} + +static int transferdestroyed(struct transfer *transfer, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid))) + newnotif(data, 617, NOTIF_ID, transfer->id, NOTIF_END); + } + return(0); +} + +static int newtransfernotify(struct transfer *transfer, void *uudata) +{ + struct uidata *data; + + for(data = actives; data != NULL; data = data->next) + { + if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid))) + newnotif(data, 610, NOTIF_ID, transfer->id, NOTIF_INT, transfer->dir, NOTIF_STR, transfer->peerid, NOTIF_STR, (transfer->path == NULL)?L"":transfer->path, NOTIF_END); + } + CBREG(transfer, trans_ac, transferchattr, NULL, NULL); + CBREG(transfer, trans_p, transferprog, NULL, NULL); + CBREG(transfer, trans_destroy, transferdestroyed, NULL, NULL); + return(0); +} + +static struct uiuser *newuser(wchar_t *name, unsigned long perms) +{ + struct uiuser *new; + + new = smalloc(sizeof(*new)); + new->used = 0; + new->name = swcsdup(name); + new->perms = perms; + new->delete = 0; + new->next = users; + new->prev = NULL; + if(users != NULL) + users->prev = new; + users = new; + return(new); +} + +static void freeuser(struct uiuser *user) +{ + if(user->next != NULL) + user->next->prev = user->prev; + if(user->prev != NULL) + user->prev->next = user->next; + if(user == users) + users = user->next; + free(user->name); + free(user); +} + +static int conf_user(int argc, wchar_t **argv) +{ + int i, perms, permmod; + struct uiuser *user; + wchar_t *p; + + if(argc < 3) + { + flog(LOG_WARNING, "not enough arguments given for user command"); + return(1); + } + perms = 0; + for(i = 2; i < argc; i++) + { + if(!iswalpha(argv[i][0])) + p = argv[i] + 1; + else + p = argv[i]; + if(!wcscmp(p, L"disallow")) + permmod = PERM_DISALLOW; + if(!wcscmp(p, L"admin")) + permmod = PERM_ADMIN; + if(!wcscmp(p, L"fnetctl")) + permmod = PERM_FNETCTL; + if(!wcscmp(p, L"trans")) + permmod = PERM_TRANS; + if(!wcscmp(p, L"transcu")) + permmod = PERM_TRANSCU; + if(!wcscmp(p, L"chat")) + permmod = PERM_CHAT; + if(!wcscmp(p, L"srch")) + permmod = PERM_SRCH; + if(!wcscmp(p, L"all")) + permmod = ~0; + if(argv[i][0] == L'-') + perms &= ~permmod; + else + perms |= permmod; + } + if((user = finduser(argv[1])) == NULL) + { + newuser(argv[1], perms); + } else { + user->delete = 0; + user->perms = perms; + } + return(0); +} + +static void preinit(int hup) +{ + struct uiuser *user; + + if(!hup) + { + newuser(L"default", 0); + } else { + for(user = users; user != NULL; user = user->next) + { + if(!wcscmp(user->name, L"default")) + user->delete = 1; + } + } +} + +#ifdef HAVE_IPV6 +static struct sockaddr *getnameforport(int port, socklen_t *len) +{ + static struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + addr.sin6_addr = in6addr_any; + if(len != NULL) + *len = sizeof(addr); + return((struct sockaddr *)&addr); +} +#else +static struct sockaddr *getnameforport(int port, socklen_t *len) +{ + static struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if(len != NULL) + *len = sizeof(addr); + return((struct sockaddr *)&addr); +} +#endif + +static int portupdate(struct configvar *var, void *uudata) +{ + struct sockaddr *addr; + socklen_t addrlen; + struct socket *newsock; + + addr = getnameforport(var->val.num, &addrlen); + if((uisocket = netcslistenlocal(SOCK_STREAM, addr, addrlen, uiaccept, NULL)) == NULL) + { + flog(LOG_WARNING, "could not create new UI socket, reverting to old: %s", strerror(errno)); + return(0); + } + if(uisocket != NULL) + putsock(uisocket); + uisocket = newsock; + return(0); +} + +static int init(int hup) +{ + struct sockaddr *addr; + socklen_t addrlen; + struct uiuser *user, *next; + + if(hup) + { + for(user = users; user != NULL; user = next) + { + next = user->next; + if(user->delete) + freeuser(user); + } + } + if(!hup) + { + if(uisocket != NULL) + putsock(uisocket); + addr = getnameforport(confgetint("ui", "port"), &addrlen); + if((uisocket = netcslistenlocal(SOCK_STREAM, addr, addrlen, uiaccept, NULL)) == NULL) + { + flog(LOG_CRIT, "could not create UI socket: %s", strerror(errno)); + return(1); + } + CBREG(confgetvar("ui", "port"), conf_update, portupdate, NULL, NULL); + GCBREG(newfncb, newfnetnode, NULL); + GCBREG(newtransfercb, newtransfernotify, NULL); + } + return(0); +} + +static int run(void) +{ + int i, id; + struct uidata *data, *next; + struct qcommand *qcmd; + struct notif *notif, *nnotif; + wchar_t buf[64]; + + for(data = actives; data != NULL; data = next) + { + next = data->next; + if(data->close) + freeuidata(data); + } + for(data = actives; data != NULL; data = data->next) + { + for(notif = data->fnotif; notif != NULL; notif = nnotif) + { + nnotif = notif->next; + if(notif->state == NOTIF_WAIT) + continue; + id = -1; + for(i = 0; i < notif->argc; i++) + { + if(notif->argv[i].dt == NOTIF_ID) + { + id = notif->argv[i].d.n; + break; + } + } + if(findnotif(notif->prev, 0, -1, notif->code, id) != NULL) + continue; + sq(data->sk, 2, L"%%i", notif->code, NULL); + for(i = 0; i < notif->argc; i++) + { + switch(notif->argv[i].dt) + { + case NOTIF_INT: + case NOTIF_ID: + sq(data->sk, 2, L"%%i", notif->argv[i].d.n, NULL); + break; + case NOTIF_STR: + if(notif->argv[i].d.s[0] == L'%') + sq(data->sk, 2, L"%%s", notif->argv[i].d.s, NULL); + else + sq(data->sk, 2, notif->argv[i].d.s, NULL); + break; + case NOTIF_FLOAT: + swprintf(buf, 64, L"%f", notif->argv[i].d.d); + sq(data->sk, 2, buf, NULL); + break; + } + } + sq(data->sk, 0, NULL); + if(notif->rlimit != 0) + { + notif->state = NOTIF_WAIT; + notif->exptimer = timercallback(ntime() + notif->rlimit, (void (*)(int, void *))notifexpire, notif); + } else { + freenotif(notif); + } + } + if((qcmd = unlinkqcmd(data)) != NULL) + { + qcmd->cmd->handler(data->sk, data, qcmd->argc, qcmd->argv); + freequeuecmd(qcmd); + return(1); + } + } + return(0); +} + +static void terminate(void) +{ + while(users != NULL) + freeuser(users); +} + +static struct configvar myvars[] = +{ + {CONF_VAR_BOOL, "onlylocal", {.num = 1}}, + {CONF_VAR_INT, "port", {.num = 1500}}, + {CONF_VAR_INT, "uitos", {.num = SOCK_TOS_MINDELAY}}, + {CONF_VAR_STRING, "filtercmd", {.str = L"dc-filtercmd"}}, + {CONF_VAR_END} +}; + +static struct configcmd mycmds[] = +{ + {"user", conf_user}, + {NULL} +}; + +static struct module me = +{ + .name = "ui", + .conf = + { + .vars = myvars, + .cmds = mycmds + }, + .preinit = preinit, + .init = init, + .run = run, + .terminate = terminate +}; + +MODULE(me) diff --git a/daemon/uiretref b/daemon/uiretref new file mode 100644 index 0000000..8d10b2c --- /dev/null +++ b/daemon/uiretref @@ -0,0 +1,32 @@ +500 - Command not found +501 - Wrong number of arguments +502 - Unauthorized request +503 - Already logged in +504 - Could not convert to character set +505 - Internal error +506 - Authentication error +507 - Command not expected +508 - No such authentication mechanism +509 - Malformed argument +510 - No such fnetnode +511 - No such fnet +512 - No such transfer +513 - Not supported for this fnet +514 - No such search + +600 - Chat receipt +601 - FN state change +602 - FN name change +603 - FN destroy +604 - FN create +605 - FN num peers change +610 - Transfer create +611 - Transfer state change +612 - Transfer nick change +613 - Transfer size change +614 - Transfer error update +615 - Transfer progress +616 - Transfer path change +620 - Search ETA change +621 - Search commit +622 - Search result diff --git a/daemon/utils.c b/daemon/utils.c new file mode 100644 index 0000000..216ae77 --- /dev/null +++ b/daemon/utils.c @@ -0,0 +1,737 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include "utils.h" +#include "log.h" + +static char *base64set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static int base64rev[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; +static char *base32set = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +static int base32rev[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +char *vsprintf2(char *format, va_list al) +{ + int ret; + char *buf; + + ret = vsnprintf(NULL, 0, format, al); + if((buf = malloc(ret + 1)) == NULL) + { + LOGOOM(ret + 1); + return(NULL); + } + vsnprintf(buf, ret + 1, format, al); + return(buf); +} + +char *sprintf2(char *format, ...) +{ + va_list args; + char *buf; + + va_start(args, format); + buf = vsprintf2(format, args); + va_end(args); + return(buf); +} + +wchar_t *vswprintf2(wchar_t *format, va_list al) +{ + int ret; + wchar_t *buf; + size_t bufsize; + + buf = smalloc(sizeof(wchar_t) * (bufsize = 1024)); + while((ret = vswprintf(buf, bufsize, format, al)) < 0) + buf = srealloc(buf, sizeof(wchar_t) * (bufsize *= 2)); + if(bufsize > ret + 1) + buf = srealloc(buf, sizeof(wchar_t) * (ret + 1)); + return(buf); +} + +wchar_t *swprintf2(wchar_t *format, ...) +{ + va_list args; + wchar_t *buf; + + va_start(args, format); + buf = vswprintf2(format, args); + va_end(args); + return(buf); +} + +wchar_t *icmbstowcs(char *mbs, char *charset) +{ + int ret; + char *buf; + char *p, *p2; + size_t len1, len2, bufsize, data; + iconv_t cd; + + len1 = strlen(mbs) + 1; + bufsize = len2 = len1 * sizeof(wchar_t); + if((buf = malloc(bufsize)) == NULL) + { + LOGOOM(bufsize); + return(NULL); + } + if(charset == NULL) + charset = nl_langinfo(CODESET); + if((cd = iconv_open("wchar_t", charset)) == (iconv_t)-1) + { + flog(LOG_ERR, "icmbstowcs: could not open iconv structure for %s: %s", charset, strerror(errno)); + free(buf); + return(NULL); + } + p = buf; + while(len1 > 0) + { + ret = iconv(cd, &mbs, &len1, &p, &len2); + if(ret < 0) + { + if(errno == E2BIG) + { + data = p - buf; + len2 += 128; + bufsize += 128; + if((p2 = realloc(buf, bufsize)) == NULL) + { + LOGOOM(bufsize); + free(buf); + iconv_close(cd); + return(NULL); + } + buf = p2; + p = buf + data; + } else { + free(buf); + iconv_close(cd); + return(NULL); + } + } + } + if(len2 > 0) + buf = realloc(buf, p - buf); + iconv_close(cd); + return((wchar_t *)buf); +} + +wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def) +{ + static wchar_t *buf = NULL; + + if(buf != NULL) + free(buf); + if((buf = icmbstowcs(mbs, charset)) == NULL) + { + if(*def == '~') + { + flog(LOG_WARNING, "icsmbstowcs: could not convert wcs string into charset %s: %s", charset, strerror(errno)); + def++; + } + return(def); + } + return(buf); +} + +char *icwcstombs(wchar_t *wcs, char *charset) +{ + int ret; + char *buf; + char *p, *p2; + size_t len1, len2, bufsize, data; + iconv_t cd; + + len1 = sizeof(wchar_t) * (wcslen(wcs) + 1); + bufsize = len2 = len1; + if((buf = malloc(bufsize)) == NULL) + { + LOGOOM(bufsize); + return(NULL); + } + if(charset == NULL) + charset = nl_langinfo(CODESET); + if((cd = iconv_open(charset, "wchar_t")) == (iconv_t)-1) + { + flog(LOG_ERR, "icwcstombs: could not open iconv structure for %s: %s", charset, strerror(errno)); + free(buf); + return(NULL); + } + p = buf; + while(len1 > 0) + { + ret = iconv(cd, (char **)&wcs, &len1, &p, &len2); + if(ret < 0) + { + if(errno == E2BIG) + { + data = p - buf; + len2 += 128; + bufsize += 128; + if((p2 = realloc(buf, bufsize)) == NULL) + { + LOGOOM(bufsize); + free(buf); + iconv_close(cd); + return(NULL); + } + buf = p2; + p = buf + data; + } else { + free(buf); + iconv_close(cd); + return(NULL); + } + } + } + if(len2 > 0) + buf = realloc(buf, p - buf); + iconv_close(cd); + return(buf); +} + +char *icswcstombs(wchar_t *wcs, char *charset, char *def) +{ + static char *buf = NULL; + + if(buf != NULL) + free(buf); + if((buf = icwcstombs(wcs, charset)) == NULL) + { + if(*def == '~') + { + flog(LOG_WARNING, "icswcstombs: could not convert mbs string from charset %s: %s", charset, strerror(errno)); + def++; + } + return(def); + } + return(buf); +} + +wchar_t *wcstolower(wchar_t *wcs) +{ + wchar_t *p; + + for(p = wcs; *p != L'\0'; p++) + *p = towlower(*p); + return(wcs); +} + +wchar_t ucptowc(int ucp) +{ + int ret; + unsigned long ucpbuf; + char *buf; + char *mbsp, *p, *p2; + wchar_t res; + size_t len1, len2, bufsize, data; + iconv_t cd; + + ucpbuf = htonl(ucp); + mbsp = (char *)&ucpbuf; + len1 = 4; + bufsize = len2 = len1 * sizeof(wchar_t); + if((buf = malloc(bufsize)) == NULL) + { + LOGOOM(bufsize); + return(L'\0'); + } + if((cd = iconv_open("wchar_t", "UCS-4BE")) == (iconv_t)-1) + { + flog(LOG_ERR, "ucptowc: could not open iconv structure for UCS-4BE: %s", strerror(errno)); + free(buf); + return(L'\0'); + } + p = buf; + while(len1 > 0) + { + ret = iconv(cd, &mbsp, &len1, &p, &len2); + if(ret < 0) + { + if(errno == E2BIG) + { + data = p - buf; + len2 += 128; + bufsize += 128; + if((p2 = realloc(buf, bufsize)) == NULL) + { + LOGOOM(bufsize); + free(buf); + iconv_close(cd); + return(L'\0'); + } + buf = p2; + p = buf + data; + } else { + free(buf); + iconv_close(cd); + return(L'\0'); + } + } + } + if(len2 > 0) + buf = realloc(buf, p - buf); + iconv_close(cd); + res = *(wchar_t *)buf; + free(buf); + return(res); +} + +void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo) +{ + if(*bufsize >= reqsize) + return; + switch(algo) + { + case 0: + *buf = srealloc(*buf, elsize * ((*bufsize) = reqsize)); + break; + case 1: + if(*bufsize == 0) + *bufsize = 1; + while(*bufsize < reqsize) + *bufsize <<= 1; + *buf = srealloc(*buf, elsize * (*bufsize)); + break; + } +} + +double ntime(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return((double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0)); +} + +int wcsexists(wchar_t *h, wchar_t *n) +{ + int i, o, nl, hl; + wchar_t *ln, *lh; + + ln = alloca(sizeof(*ln) * (nl = wcslen(n))); + for(i = 0; i < nl; i++) + ln[i] = towlower(n[i]); + lh = alloca(sizeof(*lh) * (hl = wcslen(h))); + if(nl > hl) + return(0); + for(i = 0; i < nl; i++) + lh[i] = towlower(h[i]); + i = 0; + while(1) + { + for(o = 0; o < nl; o++) + { + if(lh[i + o] != ln[o]) + break; + } + if(o == nl) + return(1); + if(i == hl - nl) + return(0); + lh[i + nl] = towlower(h[i + nl]); + i++; + } +} + +#ifndef HAVE_WCSCASECMP +int wcscasecmp(const wchar_t *s1, const wchar_t *s2) +{ + while(towlower(*s1) == towlower(*s2)) + { + if(*s1 == L'\0') + return(0); + } + return(towlower(*s1) - towlower(*s2)); +} +#endif + +char *hexencode(char *data, size_t datalen) +{ + char *buf, this; + size_t bufsize, bufdata; + int dig; + + buf = NULL; + bufsize = bufdata = 0; + for(; datalen > 0; datalen--, data++) + { + dig = (*data & 0xF0) >> 4; + if(dig > 9) + this = 'A' + dig - 10; + else + this = dig + '0'; + addtobuf(buf, this); + dig = *data & 0x0F; + if(dig > 9) + this = 'A' + dig - 10; + else + this = dig + '0'; + addtobuf(buf, this); + } + addtobuf(buf, 0); + return(buf); +} + +char *hexdecode(char *data, size_t *len) +{ + char *buf, this; + size_t bufsize, bufdata; + + buf = NULL; + bufsize = bufdata = 0; + for(; *data; data++) + { + if((*data >= 'A') && (*data <= 'F')) + { + this = (this & 0x0F) | ((*data - 'A' + 10) << 4); + } else if((*data >= '0') && (*data <= '9')) { + this = (this & 0x0F) | ((*data - '0') << 4); + } else { + if(buf != NULL) + free(buf); + return(NULL); + } + data++; + if(!*data) + { + if(buf != NULL) + free(buf); + return(NULL); + } + if((*data >= 'A') && (*data <= 'F')) + { + this = (this & 0xF0) | (*data - 'A' + 10); + } else if((*data >= '0') && (*data <= '9')) { + this = (this & 0xF0) | (*data - '0'); + } else { + if(buf != NULL) + free(buf); + return(NULL); + } + addtobuf(buf, this); + } + addtobuf(buf, 0); + if(len != NULL) + *len = bufdata - 1; + return(buf); +} + +char *base64encode(char *data, size_t datalen) +{ + char *buf; + size_t bufsize, bufdata; + + if(datalen == 0) + return(sstrdup("")); + buf = NULL; + bufsize = bufdata = 0; + while(datalen >= 3) + { + addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]); + addtobuf(buf, base64set[((data[0] & 0x03) << 4) | ((data[1] & 0xf0) >> 4)]); + addtobuf(buf, base64set[((data[1] & 0x0f) << 2) | ((data[2] & 0xc0) >> 6)]); + addtobuf(buf, base64set[data[2] & 0x3f]); + datalen -= 3; + data += 3; + } + if(datalen == 1) + { + addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]); + addtobuf(buf, base64set[(data[0] & 0x03) << 4]); + bufcat(buf, "==", 2); + } + if(datalen == 2) + { + addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]); + addtobuf(buf, base64set[((data[0] & 0x03) << 4) | ((data[1] & 0xf0) >> 4)]); + addtobuf(buf, base64set[(data[1] & 0x0f) << 2]); + addtobuf(buf, '='); + } + addtobuf(buf, 0); + return(buf); +} + +char *base64decode(char *data, size_t *datalen) +{ + int b, c; + char *buf, cur; + size_t bufsize, bufdata; + + buf = NULL; + bufsize = bufdata = 0; + cur = 0; + b = 8; + for(; *data > 0; data++) + { + c = (int)(unsigned char)*data; + if(c == '=') + break; + if(base64rev[c] == -1) + { + if(buf != NULL) + free(buf); + return(NULL); + } + b -= 6; + if(b <= 0) + { + cur |= base64rev[c] >> -b; + addtobuf(buf, cur); + b += 8; + cur = 0; + } + cur |= base64rev[c] << b; + } + if(datalen != NULL) + *datalen = bufdata; + addtobuf(buf, 0); + return(buf); +} + +char *base32encode(char *data, size_t datalen) +{ + char *buf; + size_t bufsize, bufdata; + + if(datalen == 0) + return(sstrdup("")); + buf = NULL; + bufsize = bufdata = 0; + while(datalen >= 5) + { + addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]); + addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]); + addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]); + addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]); + addtobuf(buf, base32set[((data[2] & 0x0f) << 1) | ((data[3] & 0x80) >> 7)]); + addtobuf(buf, base32set[((data[3] & 0x7c) >> 2)]); + addtobuf(buf, base32set[((data[3] & 0x03) << 3) | ((data[4] & 0xe0) >> 5)]); + addtobuf(buf, base32set[data[4] & 0x1f]); + datalen -= 5; + data += 5; + } + if(datalen == 1) + { + addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]); + addtobuf(buf, base32set[((data[0] & 0x07) << 2)]); + bufcat(buf, "======", 6); + } + if(datalen == 2) + { + addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]); + addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]); + addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]); + addtobuf(buf, base32set[((data[1] & 0x01) << 4)]); + bufcat(buf, "====", 4); + } + if(datalen == 3) + { + addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]); + addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]); + addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]); + addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]); + addtobuf(buf, base32set[((data[2] & 0x0f) << 1)]); + bufcat(buf, "===", 3); + } + if(datalen == 4) + { + addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]); + addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]); + addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]); + addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]); + addtobuf(buf, base32set[((data[2] & 0x0f) << 1) | ((data[3] & 0x80) >> 7)]); + addtobuf(buf, base32set[((data[3] & 0x7c) >> 2)]); + addtobuf(buf, base32set[((data[3] & 0x03) << 3)]); + bufcat(buf, "=", 1); + } + addtobuf(buf, 0); + return(buf); +} + +char *base32decode(char *data, size_t *datalen) +{ + int b, c; + char *buf, cur; + size_t bufsize, bufdata; + + buf = NULL; + bufsize = bufdata = 0; + cur = 0; + b = 8; + for(; *data > 0; data++) + { + c = (int)(unsigned char)*data; + if(c == '=') + break; + if(base32rev[c] == -1) + { + if(buf != NULL) + free(buf); + return(NULL); + } + b -= 5; + if(b <= 0) + { + cur |= base32rev[c] >> -b; + addtobuf(buf, cur); + b += 8; + cur = 0; + } + cur |= base32rev[c] << b; + } + if(datalen != NULL) + *datalen = bufdata; + addtobuf(buf, 0); + return(buf); +} + +void _freeparr(void **arr) +{ + void **buf; + + if(arr == NULL) + return; + for(buf = arr; *buf != NULL; buf++) + free(*buf); + free(arr); +} + +int _parrlen(void **arr) +{ + int i; + + if(arr == NULL) + return(0); + for(i = 0; *arr != NULL; arr++) + i++; + return(i); +} + +char *getetcpath(char *binpath) +{ + char *etcpath, *p; + size_t etcpathsize, etcpathdata; + + etcpath = NULL; + etcpathsize = etcpathdata = 0; + do + { + for(p = binpath; *p && (*p != ':'); p++); + for(; (p >= binpath) && (*p != '/'); p--); + if(p >= binpath) + { + if(etcpathdata > 0) + addtobuf(etcpath, ':'); + bufcat(etcpath, binpath, p - binpath + 1); + bufcat(etcpath, "etc", 3); + } + } while((binpath = strchr(binpath, ':')) != NULL); + addtobuf(etcpath, 0); + return(etcpath); +} + +char *findfile(char *gname, char *uname, char *homedir) +{ + char *path, *binpath, *etcpath, *p; + + if((homedir != NULL) && ((path = sprintf2("%s/.%s", homedir, uname)) != NULL)) + { + if(!access(path, F_OK)) + return(path); + free(path); + } + if(gname != NULL) + { + if(strchr(gname, '/') != NULL) + { + if(!access(gname, F_OK)) + return(sstrdup(gname)); + } else { + if((binpath = getenv("PATH")) == NULL) + etcpath = sstrdup("/usr/local/etc:/etc:/usr/etc"); + else + etcpath = getetcpath(binpath); + for(p = strtok(etcpath, ":"); p != NULL; p = strtok(NULL, ":")) + { + if((path = sprintf2("%s/%s", p, gname)) != NULL) + { + if(!access(path, F_OK)) + { + free(etcpath); + return(path); + } + free(path); + } + } + free(etcpath); + } + } + return(NULL); +} diff --git a/daemon/utils.h b/daemon/utils.h new file mode 100644 index 0000000..b8b6803 --- /dev/null +++ b/daemon/utils.h @@ -0,0 +1,193 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _UTILS_H +#define _UTILS_H + +#include +#include +#include +#include "log.h" + +/* "Safe" functions */ +#define smalloc(size) ({void *__result__; ((__result__ = malloc(size)) == NULL)?({LOGOOM(size); abort(); (void *)0;}):__result__;}) +#define srealloc(ptr, size) ({void *__result__; ((__result__ = realloc((ptr), (size))) == NULL)?({LOGOOM(size); abort(); (void *)0;}):__result__;}) +#define swcsdup(wcs) ((wchar_t *)wcscpy(smalloc(sizeof(wchar_t) * (wcslen(wcs) + 1)), (wcs))) +#define sstrdup(str) ((char *)strcpy(smalloc(strlen(str) + 1), (str))) + +#define CBCHAIN(name, args...) \ +struct cbchain_ ## name { \ + struct cbchain_ ## name *next, *prev; \ + int (*func)(args, void *data); \ + void (*destroy)(void *data); \ + void *data; \ +} * name + +#define GCBCHAIN(name, args...) \ +struct cbchain_ ## name * name = NULL + +#define EGCBCHAIN(name, args...) \ +extern struct cbchain_ ## name { \ + struct cbchain_ ## name *next, *prev; \ + int (*func)(args, void *data); \ + void *data; \ +} * name + +extern int vswprintf (wchar_t *__restrict __s, size_t __n, + __const wchar_t *__restrict __format, + __gnuc_va_list __arg); +extern int swprintf (wchar_t *__restrict __s, size_t __n, + __const wchar_t *__restrict __format, ...); + +char *vsprintf2(char *format, va_list al); +char *sprintf2(char *format, ...) +#if defined(__GNUC__) && 0 + __attribute__ ((format (printf, 1, 2))) +#endif + +; +wchar_t *vswprintf2(wchar_t *format, va_list al); +wchar_t *swprintf2(wchar_t *format, ...); +wchar_t *icmbstowcs(char *mbs, char *charset); +wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def); +char *icwcstombs(wchar_t *wcs, char *charset); +char *icswcstombs(wchar_t *wcs, char *charset, char *def); +wchar_t *wcstolower(wchar_t *wcs); +wchar_t ucptowc(int ucp); +void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo); +double ntime(void); +int wcsexists(wchar_t *h, wchar_t *n); +#ifndef HAVE_WCSCASECMP +int wcscasecmp(const wchar_t *s1, const wchar_t *s2); +#endif +char *hexencode(char *data, size_t datalen); +char *hexdecode(char *data, size_t *len); +char *base64encode(char *data, size_t datalen); +char *base64decode(char *data, size_t *datalen); +char *base32encode(char *data, size_t datalen); +char *base32decode(char *data, size_t *datalen); +void _freeparr(void **arr); +int _parrlen(void **arr); +char *findfile(char *gname, char *uname, char *homedir); + +#define sizebuf(b, bs, rs, es, a) _sizebuf((void **)(b), (bs), (rs), (es), (a)) +#define sizebuf2(b, rs, a) _sizebuf((void **)(&(b)), &(b ## size), (rs), sizeof(*(b)), (a)) +#define addtobuf(b, c) \ +do { \ + _sizebuf((void **)(&(b)), &(b ## size), (b ## data) + 1, sizeof(*(b)), 1); \ + (b)[(b ## data)++] = (c); \ +} while(0) +#define bufcat(d, s, n) \ +do { \ + size_t __bufcat_size__; \ + __bufcat_size__ = (n); \ + _sizebuf((void **)(&(d)), &(d ## size), (d ## data) + __bufcat_size__, sizeof(*(d)), 1); \ + memcpy((d) + (d ## data), (s), sizeof(*(d)) * __bufcat_size__); \ + (d ## data) += __bufcat_size__; \ +} while (0) + +#define freeparr(parr) _freeparr((void **)(parr)) +#define parrlen(parr) _parrlen((void **)(parr)) + +#define CBREG(obj, name, funca, destroya, dataa) \ +do { \ + struct cbchain_ ## name *__new_cb__; \ + __new_cb__ = smalloc(sizeof(*__new_cb__)); \ + __new_cb__->func = funca; \ + __new_cb__->destroy = destroya; \ + __new_cb__->data = dataa; \ + __new_cb__->prev = NULL; \ + __new_cb__->next = (obj)->name; \ + (obj)->name = __new_cb__; \ +} while(0) + +#define CBUNREG(obj, name, dataa) \ +do { \ + struct cbchain_ ## name *__cur__; \ + for(__cur__ = (obj)->name; __cur__ != NULL; __cur__ = __cur__->next) { \ + if(__cur__->data == (dataa)) { \ + if(__cur__->destroy != NULL) \ + __cur__->destroy(__cur__->data); \ + if(__cur__->prev != NULL) \ + __cur__->prev->next = __cur__->next; \ + if(__cur__->next != NULL) \ + __cur__->next->prev = __cur__->prev; \ + if(__cur__ == (obj)->name) \ + (obj)->name = __cur__->next; \ + free(__cur__); \ + break; \ + } \ + } \ +} while(0) + +#define GCBREG(name, funca, dataa) \ +do { \ + struct cbchain_ ## name *__new_cb__; \ + __new_cb__ = smalloc(sizeof(*__new_cb__)); \ + __new_cb__->func = funca; \ + __new_cb__->data = dataa; \ + __new_cb__->prev = NULL; \ + __new_cb__->next = name; \ + name = __new_cb__; \ +} while(0) + +#define CBCHAININIT(obj, name) (obj)->name = NULL + +#define CBCHAINFREE(obj, name) \ +do { \ + struct cbchain_ ## name *__cur__; \ + while((__cur__ = (obj)->name) != NULL) { \ + (obj)->name = __cur__->next; \ + if(__cur__->destroy != NULL) \ + __cur__->destroy(__cur__->data); \ + free(__cur__); \ + } \ +} while(0) + +#define CBCHAINDOCB(obj, name, args...) \ +do { \ + struct cbchain_ ## name *__cur__, *__next__; \ + for(__cur__ = (obj)->name; __cur__ != NULL; __cur__ = __next__) { \ + __next__ = __cur__->next; \ + if(__cur__->func(args, __cur__->data)) { \ + if(__cur__->next != NULL) \ + __cur__->next->prev = __cur__->prev; \ + if(__cur__->prev != NULL) \ + __cur__->prev->next = __cur__->next; \ + if(__cur__ == (obj)->name) \ + (obj)->name = __cur__->next; \ + free(__cur__); \ + } \ + } \ +} while(0) + +#define GCBCHAINDOCB(name, args...) \ +({ \ + struct cbchain_ ## name *__cur__; \ + int __ret__; \ + __ret__ = 0; \ + for(__cur__ = name; __cur__ != NULL; __cur__ = __cur__->next) { \ + if(__cur__->func(args, __cur__->data)) { \ + __ret__ = 1; \ + break; \ + } \ + } \ + __ret__; \ +}) + +#endif diff --git a/include/CVS/Entries b/include/CVS/Entries new file mode 100644 index 0000000..25536c8 --- /dev/null +++ b/include/CVS/Entries @@ -0,0 +1,2 @@ +/Makefile.am/1.3/Wed Aug 25 13:13:58 2004// +D/doldaconnect//// diff --git a/include/CVS/Repository b/include/CVS/Repository new file mode 100644 index 0000000..9845500 --- /dev/null +++ b/include/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/include diff --git a/include/CVS/Root b/include/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/include/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..4c6fb9a --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,2 @@ +pkginclude_HEADERS=doldaconnect/uilib.h doldaconnect/uimisc.h doldaconnect/utils.h +EXTRA_DIST=doldaconnect diff --git a/include/doldaconnect/CVS/Entries b/include/doldaconnect/CVS/Entries new file mode 100644 index 0000000..a6ad5de --- /dev/null +++ b/include/doldaconnect/CVS/Entries @@ -0,0 +1,4 @@ +/uilib.h/1.4/Fri Aug 6 19:11:23 2004// +/uimisc.h/1.5/Fri Aug 13 11:42:30 2004// +/utils.h/1.2/Wed Jul 28 00:00:38 2004// +D diff --git a/include/doldaconnect/CVS/Repository b/include/doldaconnect/CVS/Repository new file mode 100644 index 0000000..f2b63cf --- /dev/null +++ b/include/doldaconnect/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/include/doldaconnect diff --git a/include/doldaconnect/CVS/Root b/include/doldaconnect/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/include/doldaconnect/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/include/doldaconnect/uilib.h b/include/doldaconnect/uilib.h new file mode 100644 index 0000000..d67afb3 --- /dev/null +++ b/include/doldaconnect/uilib.h @@ -0,0 +1,56 @@ +#ifndef _UILIB_H +#define _UILIB_H + +#include + +struct dc_response +{ + struct dc_response *next, *prev; + int code, tag; + wchar_t *cmdname; + void *data; + void *internal; + struct + { + int argc; + wchar_t **argv; + } *rlines; + int linessize; + int numlines; + int curline; +}; + +struct dc_intresp +{ + int code; + int argc; + struct + { + int type; + union + { + int num; + wchar_t *str; + double flnum; + } val; + } *argv; +}; + +int dc_init(void); +void dc_cleanup(void); +void dc_disconnect(void); +void dc_freeresp(struct dc_response *resp); +struct dc_response *dc_getresp(void); +struct dc_response *dc_gettaggedresp(int tag); +struct dc_response *dc_gettaggedrespsync(int tag); +int dc_wantwrite(void); +int dc_getstate(void); +int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...); +int dc_handleread(void); +int dc_handlewrite(void); +int dc_connect(char *host, int port); +struct dc_intresp *dc_interpret(struct dc_response *resp); +void dc_freeires(struct dc_intresp *ires); +const char *dc_gethostname(void); + +#endif diff --git a/include/doldaconnect/uimisc.h b/include/doldaconnect/uimisc.h new file mode 100644 index 0000000..a7bc3e7 --- /dev/null +++ b/include/doldaconnect/uimisc.h @@ -0,0 +1,76 @@ +#ifndef _UIMISC_H +#define _UIMISC_H + +#define DC_LOGIN_ERR_SUCCESS 0 +#define DC_LOGIN_ERR_NOLOGIN 1 +#define DC_LOGIN_ERR_SERVER 2 +#define DC_LOGIN_ERR_USER 3 +#define DC_LOGIN_ERR_CONV 4 +#define DC_LOGIN_ERR_AUTHFAIL 5 + +#define DC_LOGIN_CONV_NOECHO 0 +#define DC_LOGIN_CONV_ECHO 1 +#define DC_LOGIN_CONV_INFO 2 +#define DC_LOGIN_CONV_ERROR 3 + +#define DC_FNN_STATE_SYN 0 +#define DC_FNN_STATE_HS 1 +#define DC_FNN_STATE_EST 2 +#define DC_FNN_STATE_DEAD 3 + +#define DC_TRNS_WAITING 0 +#define DC_TRNS_HS 1 +#define DC_TRNS_MAIN 2 +#define DC_TRNS_DONE 3 + +#define DC_TRNSD_UNKNOWN 0 +#define DC_TRNSD_UP 1 +#define DC_TRNSD_DOWN 2 + +#define DC_TRNSE_NOERROR 0 +#define DC_TRNSE_NOTFOUND 1 +#define DC_TRNSE_NOSLOTS 2 + +#include + +struct dc_fnetnode +{ + struct dc_fnetnode *next, *prev; + int id; + wchar_t *name; + wchar_t *fnet; + int state; + int numusers; + int found; + void (*destroycb)(struct dc_fnetnode *fn); + void *udata; +}; + +struct dc_transfer +{ + struct dc_transfer *next, *prev; + int id; + int dir, state; + wchar_t *peerid, *peernick; + wchar_t *path; + int size, curpos; + int found; + int error; + time_t errortime; + void (*destroycb)(struct dc_transfer *transfer); + void *udata; +}; + +void dc_loginasync(char *username, int useauthless, int (*conv)(int, wchar_t *, char **, void *), void (*callback)(int, wchar_t *, void *), void *udata); +struct dc_fnetnode *dc_findfnetnode(int id); +void dc_getfnlistasync(void (*callback)(int, void *), void *udata); +void dc_uimisc_handlenotify(struct dc_response *resp); +struct dc_transfer *dc_findtransfer(int id); +void dc_gettrlistasync(void (*callback)(int, void *), void *udata); +wchar_t **dc_lexsexpr(wchar_t *sexpr); +void dc_freewcsarr(wchar_t **arr); + +extern struct dc_fnetnode *dc_fnetnodes; +extern struct dc_transfer *dc_transfers; + +#endif diff --git a/include/doldaconnect/utils.h b/include/doldaconnect/utils.h new file mode 100644 index 0000000..60e06ad --- /dev/null +++ b/include/doldaconnect/utils.h @@ -0,0 +1,40 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include +#include + +/* "Safe" functions */ +#define smalloc(size) ({void *__result__; ((__result__ = malloc(size)) == NULL)?({exit(-1); (void *)0;}):__result__;}) +#define srealloc(ptr, size) ({void *__result__; ((__result__ = realloc((ptr), (size))) == NULL)?({exit(-1); (void *)0;}):__result__;}) +#define swcsdup(wcs) ((wchar_t *)wcscpy(smalloc(sizeof(wchar_t) * (wcslen(wcs) + 1)), (wcs))) +#define sstrdup(str) ((char *)strcpy(smalloc(strlen(str) + 1), (str))) + +char *vsprintf2(char *format, va_list al); +char *sprintf2(char *format, ...); +wchar_t *vswprintf2(wchar_t *format, va_list al); +wchar_t *swprintf2(wchar_t *format, ...); +wchar_t *icmbstowcs(char *mbs, char *charset); +wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def); +char *icwcstombs(wchar_t *wcs, char *charset); +char *icswcstombs(wchar_t *wcs, char *charset, char *def); +wchar_t *wcstolower(wchar_t *wcs); +void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo); + +#define sizebuf(b, bs, rs, es, a) _sizebuf((void **)(b), (bs), (rs), (es), (a)) +#define sizebuf2(b, rs, a) _sizebuf((void **)(&(b)), &(b ## size), (rs), sizeof(*(b)), (a)) +#define addtobuf(b, c) \ +do { \ + _sizebuf((void **)(&(b)), &(b ## size), (b ## data) + 1, sizeof(*(b)), 1); \ + (b)[(b ## data)++] = (c); \ +} while(0) +#define bufcat(d, s, n) \ +do { \ + size_t __bufcat_size__; \ + __bufcat_size__ = (n); \ + _sizebuf((void **)(&(d)), &(d ## size), (d ## data) + __bufcat_size__, sizeof(*(d)), 1); \ + memcpy((d) + (d ## data), (s), sizeof(*(d)) * __bufcat_size__); \ + (d ## data) += __bufcat_size__; \ +} while (0) + +#endif diff --git a/lib/CVS/Entries b/lib/CVS/Entries new file mode 100644 index 0000000..01eba9f --- /dev/null +++ b/lib/CVS/Entries @@ -0,0 +1,9 @@ +/Makefile.am/1.5/Mon Oct 4 02:05:35 2004// +/initcmds.h/1.8/Mon Jan 24 01:31:54 2005// +/makecmds/1.2/Wed Aug 4 12:46:13 2004// +/uicmds/1.9/Mon Jan 24 01:32:02 2005// +/uilib.c/1.13/Mon May 9 23:16:56 2005// +/uilib.h/1.1.1.1/Tue May 11 15:48:00 2004// +/uimisc.c/1.7/Mon May 9 23:20:49 2005// +/utils.c/1.2/Wed Jul 28 00:00:47 2004// +D/guile//// diff --git a/lib/CVS/Repository b/lib/CVS/Repository new file mode 100644 index 0000000..9ae7dde --- /dev/null +++ b/lib/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/lib diff --git a/lib/CVS/Root b/lib/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/lib/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..2449ba7 --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,16 @@ +EXTRA_DIST = makecmds uicmds + +SUBDIRS=. @extlibs@ +DIST_SUBDIRS=guile + +lib_LTLIBRARIES = libdcui.la + +libdcui_la_SOURCES = uilib.c uimisc.c utils.c +libdcui_la_LIBADD = @KRB5_LDADD@ + +BUILT_SOURCES = initcmds.h + +initcmds.h: uicmds makecmds + ./makecmds initcmds.h + +AM_CPPFLAGS=-I$(top_srcdir)/include diff --git a/lib/guile/CVS/Entries b/lib/guile/CVS/Entries new file mode 100644 index 0000000..6c1ad48 --- /dev/null +++ b/lib/guile/CVS/Entries @@ -0,0 +1,5 @@ +/Makefile.am/1.2/Sat May 14 14:23:25 2005// +/autodl/1.16/Sun Oct 9 02:53:45 2005// +/chatlog/1.2/Wed Jul 13 00:24:24 2005// +/dolcon-guile.c/1.3/Fri Sep 24 00:48:41 2004// +D/dolcon//// diff --git a/lib/guile/CVS/Repository b/lib/guile/CVS/Repository new file mode 100644 index 0000000..328ead7 --- /dev/null +++ b/lib/guile/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/lib/guile diff --git a/lib/guile/CVS/Root b/lib/guile/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/lib/guile/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/lib/guile/Makefile.am b/lib/guile/Makefile.am new file mode 100644 index 0000000..caf7c84 --- /dev/null +++ b/lib/guile/Makefile.am @@ -0,0 +1,9 @@ +SUBDIRS=dolcon + +EXTRA_DIST=autodl chatlog + +lib_LTLIBRARIES=libdolcon-guile.la + +libdolcon_guile_la_SOURCES=dolcon-guile.c +libdolcon_guile_la_LIBADD=@GUILE_LDFLAGS@ $(top_srcdir)/lib/libdcui.la +libdolcon_guile_la_CPPFLAGS=@GUILE_CFLAGS@ diff --git a/lib/guile/autodl b/lib/guile/autodl new file mode 100755 index 0000000..f57423f --- /dev/null +++ b/lib/guile/autodl @@ -0,0 +1,547 @@ +#!/usr/bin/guile -s +!# + +(use-modules (dolcon ui)) +(use-modules (ice-9 pretty-print)) + +(define sr '()) +(define lastsearch 0) +(define info-searcheta 0) +(define info-numavail 0) +(define info-numreal 0) +(define info-numtotal 0) +(define lastparse 0) +(define srchid -1) +(define session '()) +(define trans '()) +(define dpeers '()) +(define lastdl 0) + +(define (logf msg) + (write-line msg (current-output-port)) + (catch 'system-error (lambda () + (fsync (current-output-port))) + (lambda (key . err) #f)) + ) + +(define (make-getopt opts optdesc) + (let ((arg opts) (curpos 0) (rest '())) + (lambda () + (if (eq? arg '()) rest + (let ((ret #f)) + (while (not ret) + (if (= curpos 0) + (if (eq? (string-ref (car arg) 0) #\-) + (set! curpos 1) + (begin + (set! rest (append rest (list (car arg)))) + (set! arg (cdr arg)) + (if (eq? arg '()) + (set! ret #t))))) + (if (> curpos 0) + (if (< curpos (string-length (car arg))) + (begin (set! ret (string-ref (car arg) curpos)) (set! curpos (+ curpos 1))) + (begin (set! curpos 0) (set! arg (cdr arg)) (if (eq? arg '()) (set! ret #t)))))) + (if (eq? ret #t) rest + (let ((opt (string-index optdesc ret))) + (if (eq? opt #f) (throw 'illegal-option ret) + (if (and (< opt (- (string-length optdesc) 1)) (eq? (string-ref optdesc (+ opt 1)) #\:)) + (let ((ret + (cons ret (let ((optarg + (if (< curpos (string-length (car arg))) + (substring (car arg) curpos) + (begin (set! arg (cdr arg)) (if (eq? arg '()) (throw 'requires-argument ret)) (car arg))))) + (set! arg (cdr arg)) optarg)))) + (set! curpos 0) + ret) + (list ret)))))))))) + +(define (ftime) + (let ((ctime (gettimeofday))) + (+ (car ctime) (/ (cdr ctime) 1000000)))) + +(define (wanttosearch) + (> (- (current-time) lastsearch) + (if (> (length trans) 0) 300 60)) + ) + +(define defspeed '()) +(let ((matchlist (list + (cons (make-regexp "^[][{}() ]*BBB" regexp/icase) 100000)))) + (set! defspeed + (lambda (sr) + (catch 'ret + (lambda () + (for-each (lambda (o) + (if (regexp-exec (car o) (cadr (cdr (assoc 'peer sr)))) + (throw 'ret (cdr o)))) + matchlist) + 15000) + (lambda (sig ret) + ret)) + ))) + +(define (sr-less? sr1 sr2) + (let ((s1 (if (cdr (assoc 'speed sr1)) (cdr (assoc 'speed sr1)) (defspeed sr1))) + (s2 (if (cdr (assoc 'speed sr2)) (cdr (assoc 'speed sr2)) (defspeed sr2)))) + (if (= s1 s2) + (< (cdr (assoc 'resptime sr1)) (cdr (assoc 'resptime sr2))) + (> s1 s2))) + ) + +(define (srg-less? g1 g2) + (or (> (length (cdr g1)) (length (cdr g2))) + (and (= (length (cdr g1)) (length (cdr g2))) + (> (car g1) (car g2)))) + ) + +(define (gettrbysize size) + (catch 'ret + (lambda () + (for-each (lambda (o) + (if (= (cdr (assoc 'size (cdr o))) size) + (throw 'ret (cdr o)))) + trans) + #f) + (lambda (sig ret) + ret)) + ) + +(define (download sr) + (let ((resp #f)) + (let ((args (list "download" + (car (cdr (assoc 'peer sr))) + (cadr (cdr (assoc 'peer sr))) + (cdr (assoc 'filename sr)) + (cdr (assoc 'size sr))))) + (let ((tag (assoc 'tag session))) + (if tag (set! args (append args (list "tag" (cdr tag)))))) + (let ((uarg (assoc 'uarg session))) + (if uarg (set! args (append args (list "user" (cdr uarg)))))) + (set! resp (apply dc-ecmd-assert 200 args))) + (let ((id (car (dc-intresp resp)))) + (set! trans + (cons (cons id (list (assoc 'size sr) + (assoc 'peer sr) + (assoc 'filename sr) + (assoc 'resptime sr) + '(curpos . 0) + '(state . wait) + '(curspeed . #f) + '(lastpos . 0) + (cons 'id id) + (cons 'lasttime (current-time)) + (cons 'lastprog (current-time)))) + trans)) + (logf (string-append "downloading " + (cdr (assoc 'filename sr)) + " from " + (cadr (cdr (assoc 'peer sr))) + ", " + (number->string (cdr (assoc 'size sr))) + " bytes (id " + (number->string id) + ", " + (number->string (cdr (assoc 'slots sr))) + " slots), timing out in " + (number->string (max 10 (* (cdr (assoc 'resptime sr)) 2))) + " seconds")))) + (set! lastdl (current-time)) + ) + +(define (disablepeer peer) + (let ((newglist '()) (numrem 0)) + (for-each (lambda (g) + (let ((newlist '())) + (for-each (lambda (o) + (if (not (equal? (cdr (assoc 'peer o)) peer)) + (set! newlist (cons o newlist)) + (set! numrem (+ numrem 1)))) + (cdr g)) + (if (not (eq? newlist '())) + (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist))))) + sr) + (set! sr (sort newglist srg-less?)) + (logf (string-append "disabled " (cadr peer) " and removed " (number->string numrem) " search results"))) + (let* ((dpa (assoc peer dpeers)) (dp (and (pair? dpa) (cdr dpa)))) + (if dp + (set-cdr! (assoc 'time dp) (current-time)) + (set! dpeers (cons (cons peer (list (cons 'time (current-time)) + (cons 'peer peer))) + dpeers)))) + ) + +(define (checktrans) + (let ((time (current-time))) + (for-each (lambda (o) + (if (and (memq (cdr (assoc 'state (cdr o))) '(wait hs)) + (> (- time (cdr (assoc 'lastprog (cdr o)))) (max 10 (* (cdr (assoc 'resptime (cdr o))) 2)))) + (begin (logf (string-append "transfer " (number->string (car o)) " timing out")) + (dc-ecmd-assert 200 "cancel" (car o)) + (disablepeer (cdr (assoc 'peer (cdr o)))) + (set! trans (assq-remove! trans (car o))))) + (if (and (eq? (cdr (assoc 'state (cdr o))) 'main) + (> (- time (cdr (assoc 'lastprog (cdr o)))) 60)) + (begin (logf (string-append "transfer " (number->string (car o)) " seems to have stalled")) + (dc-ecmd-assert 200 "cancel" (car o)) + (set! trans (assq-remove! trans (car o))))) + (if (and (eq? (cdr (assoc 'state (cdr o))) 'main) + (> (- (cdr (assoc 'lastprog (cdr o))) (cdr (assoc 'lasttime (cdr o)))) 20)) + (begin (set-cdr! (assoc 'curspeed (cdr o)) + (/ (- (cdr (assoc 'curpos (cdr o))) (cdr (assoc 'lastpos (cdr o)))) + (- (cdr (assoc 'lastprog (cdr o))) (cdr (assoc 'lasttime (cdr o)))))) + (set-cdr! (assoc 'lastpos (cdr o)) (cdr (assoc 'curpos (cdr o)))) + (set-cdr! (assoc 'lasttime (cdr o)) (cdr (assoc 'lastprog (cdr o))))))) + trans)) + ) + +(define (write-info-file) + (if (assoc 'info-file session) + (let ((op (open-output-file (cdr (assoc 'info-file session))))) + (write (list (cons 'numdl (length trans)) + (cons 'lastdl lastdl) + (cons 'availsr info-numavail) + (cons 'realsr info-numreal) + (cons 'totalsr info-numtotal) + (cons 'lastsrch lastsearch) + (cons 'srcheta info-searcheta)) + op) + (newline op) + (close-port op)))) + +(define (parseresults) + (logf (string-append "entering parseresults with " + (number->string + (apply + (map (lambda (o) (length (cdr o))) sr))) + " results in " + (number->string (length sr)) + " sizes")) + (let ((retval #t) (numreal 0) (numtotal 0) (numavail 0)) + (catch 'ret + (lambda () + (and (eq? sr '()) (throw 'ret #f)) + (let ((numrem 0) (countrem 0) (newglist '())) + (for-each (lambda (g) + (let ((newlist '())) + (for-each (lambda (o) + (if (< (- (current-time) (cdr (assoc 'recvtime o))) 300) + (set! newlist (cons o newlist)) + (set! countrem (+ countrem 1)))) + (cdr g)) + (if (> (length newlist) 0) + (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist)) + (set! numrem (+ numrem 1))))) + sr) + (set! sr (sort newglist srg-less?)) + (if (> countrem 0) + (logf (string-append "removed " (number->string countrem) " time-outed results and " (number->string numrem) " entire sizes")))) + (let ((numrem 0) (newlist '())) + (for-each (lambda (o) + (if (> (- (current-time) (cdr (assoc 'time o))) 1800) + (set! numrem (+ numrem 1)) + (set! newlist (cons o newlist)))) + dpeers) + (set! dpeers newlist) + (logf (string-append "re-enabled " (number->string numrem) " disabled users"))) + (let ((numrem 0) (countrem 0) (newglist '())) + (for-each (lambda (g) + (let ((newlist '())) + (for-each (lambda (o) + (if (not (assoc (cdr (assoc 'peer o)) dpeers)) + (set! newlist (cons o newlist)) + (set! countrem (+ countrem 1)))) + (cdr g)) + (if (> (length newlist) 0) + (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist)) + (set! numrem (+ numrem 1))))) + sr) + (set! sr (sort newglist srg-less?)) + (if (> countrem 0) + (logf (string-append "removed " (number->string countrem) " results with disabled peers and " (number->string numrem) " entire sizes")))) + (and (eq? sr '()) (throw 'ret #f)) + (set! numtotal (apply + (map (lambda (o) (length (cdr o))) sr))) + (let* ((maxsize (apply max (map (lambda (o) (length (cdr o))) sr))) + (minsize (/ maxsize 3))) + (let ((numrem 0) (countrem 0)) + (for-each (lambda (o) (if (< (length (cdr o)) minsize) + (begin (set! countrem (+ countrem (length (cdr o)))) + (set! numrem (+ numrem 1))))) + sr) + (if (> countrem 0) + (logf (string-append "will disregard " (number->string countrem) " results from " (number->string numrem) " sizes due to popularity lack"))) + (set! numreal (- numtotal countrem))) + (let ((numrem 0) (numrrem 0)) + (for-each (lambda (g) + (for-each (lambda (o) + (if (< (cdr (assoc 'slots o)) 1) + (begin (set! numrem (+ numrem 1)) + (if (>= (length (cdr g)) minsize) + (set! numrrem (+ numrrem 1)))))) + (cdr g))) + sr) + (if (> numrem 0) + (logf (string-append (number->string numrem) " results had no slots"))) + (set! numavail (- numreal numrrem))) + (for-each (lambda (g) + (if (>= (length (cdr g)) minsize) + (catch 'found + (lambda () + (for-each (lambda (o) + (and (> (cdr (assoc 'slots o)) 0) + (throw 'found o))) + (cdr g))) + (lambda (sig sr) + (let ((tr (gettrbysize (cdr (assoc 'size sr))))) + (if (not tr) + (if (< (length trans) (cdr (assoc 'maxtrans session))) + (download sr)) + (if (and (cdr (assoc 'curspeed tr)) + (not (equal? (cdr (assoc 'peer sr)) (cdr (assoc 'peer tr)))) + (> (- (or (cdr (assoc 'speed sr)) (defspeed sr)) (cdr (assoc 'curspeed tr))) 10000)) + (begin (logf (string-append "abandoning transfer " + (number->string (cdr (assoc 'id tr))) + " for possible faster sender")) + (dc-ecmd-assert 200 "cancel" (cdr (assoc 'id tr))) + (set! trans (assq-remove! trans (cdr (assoc 'id tr)))) + (download sr))))))))) + sr) + ) + ) + (lambda (sig ret) + (set! retval ret) + )) + (set! info-numavail numavail) + (set! info-numreal numreal) + (set! info-numtotal numtotal) + retval) + ) + +(define (handlesr filename fnet peer size slots resptime) + (let ((cl (or (assoc size sr) + (let ((newp (cons size '()))) (set! sr (append sr (list newp))) newp))) + (newsr (list + (cons 'filename filename) + (cons 'peer (list fnet peer)) + (cons 'size size) + (cons 'slots slots) + (cons 'resptime resptime) + (cons 'speed (getspeed peer)) + (cons 'recvtime (current-time)) + (cons 'dis #f))) + (newlist '())) + (for-each (lambda (o) (if (not (and (equal? (cdr (assoc 'filename o)) filename) + (equal? (cdr (assoc 'peer o)) (list fnet peer)))) + (set! newlist (cons o newlist)))) + (cdr cl)) + (set-cdr! cl (sort (cons newsr newlist) sr-less?)) + ) + ) + +; XXX: Redefine to go through the server, once that is implemented +(define (getspeed username) + (catch 'system-error + (lambda () + (let* ((port (open-input-file (string-append (getenv "HOME") "/dc/users/" username))) (avg 0) (numdls (string->number (read-line port))) (max (string->number (read-line port))) (numents (string->number (read-line port)))) + (do ((i 0 (+ i 1))) ((= i numents) (close-port port) (/ avg numents)) (set! avg (+ avg (string->number (read-line port))))) + )) + (lambda args + #f + ) + ) + ) + +(define (validate-session session) + (catch 'wrong-type-arg + (lambda () + (and + (assoc 'sexpr session) + (assoc 'prio session) + (assoc 'maxtrans session) + #t + ) + ) + (lambda (key . args) + (display "Session data is not an a-list\n" (current-error-port)) + #f) + ) + ) + +(define (autodl-main args) + (let ((dc-server #f) (done #f) (retval 0)) + (let ((getopt (make-getopt (cdr args) "hs:S:e:p:t:a:I:")) (arg #f)) + (do ((arg (getopt) (getopt))) ((not (and (pair? arg) (char? (car arg)))) (set! args arg)) + (cond ((eq? (car arg) #\h) + (begin (display "usage: autodl [-s server] -S sessfile\n" (current-error-port)) + (display " autodl [-s server] -e search-expression [-p prio] [-t tag] [-a userarg]\n" (current-error-port)) + (display " autodl [-s server]\n" (current-error-port)) + (display " autodl -h\n" (current-error-port)) + (exit 0))) + ((eq? (car arg) #\s) + (set! dc-server (cdr arg))) + ((eq? (car arg) #\S) + (let ((port (open-file (cdr arg)))) (set! session (read port)) (close-port port))) + ((eq? (car arg) #\p) + (let ((c (assoc 'prio session))) + (if c (set-cdr! c (cdr arg)) + (set! session (cons (cons 'prio (cdr arg)) session))))) + ((eq? (car arg) #\t) + (let ((c (assoc 'tag session))) + (if c (set-cdr! c (cdr arg)) + (set! session (cons (cons 'tag (cdr arg)) session))))) + ((eq? (car arg) #\a) + (let ((c (assoc 'uarg session))) + (if c (set-cdr! c (cdr arg)) + (set! session (cons (cons 'uarg (cdr arg)) session))))) + ((eq? (car arg) #\I) + (let ((c (assoc 'info-file session))) + (if c (set-cdr! c (cdr arg)) + (set! session (cons (cons 'info-file (cdr arg)) session))))) + ((eq? (car arg) #\e) + (set! session (cons (cons 'sexpr (dc-lexsexpr (cdr arg))) session))) + ) + ) + ) + (if (eq? session '()) (begin (if (isatty? (current-input-port)) (display "Enter session data (s-expr):\n" (current-error-port))) (set! session (read)))) + (if (not (assoc 'prio session)) + (set! session (cons '(prio . 10) session))) + (if (not (assoc 'maxtrans session)) + (set! session (cons '(maxtrans . 1) session))) + (if (not (validate-session session)) (begin (display "Invalid session!\n" (current-error-port)) (exit 1))) + (if (not dc-server) (set! dc-server (getenv "DCSERVER"))) + (if (not dc-server) (set! dc-server "localhost")) + (catch 'system-error + (lambda () + (dc-c&l #t dc-server #t)) + (lambda (key . args) + (logf (string-append "could not connect to server: " (apply format #f (cadr args) (caddr args)))) + (exit 2))) + (dc-ecmd-assert 200 "notify" "all" "on") + (for-each (lambda (sig) (sigaction sig (lambda (sig) (throw 'sig sig)))) (list SIGINT SIGTERM SIGHUP)) + (catch 'sig + (lambda () + (while #t + (if (and (not (= lastsearch -1)) (wanttosearch)) + (begin + (if (not (= srchid -1)) + (dc-ecmd "cansrch" srchid)) + (let* ((resp (apply dc-ecmd-assert (append (list '(200 501 509) "search" "prio" (number->string (cdr (assoc 'prio session))) "all") (cdr (assoc 'sexpr session))))) + (ires (dc-intresp resp)) + (eres (dc-extract resp))) + (case (cdr (assoc 'code eres)) + ((200) + (begin (set! srchid (car ires)) + (logf (string-append "search scheduled in " (number->string (cadr ires)) " seconds (id " (number->string srchid) ")")) + (set! info-searcheta (+ (current-time) (cadr ires))) + (set! lastsearch -1))) + ((501) + (begin (set! srchid -1) + (logf (string-append "no fnetnodes available to search on")) + (set! lastsearch (current-time)))) + ((509) + (begin (logf "illegal search expression") + (set! done #t) + (set! retval 3) + (throw 'sig 0))))))) + (checktrans) + (if (> (- (current-time) lastparse) 20) + (begin (parseresults) + (set! lastparse (current-time)))) + (write-info-file) + (dc-select 10000) + (while (let ((resp (dc-getresp))) + (if resp + (begin + (let* ((er (dc-extract resp)) (code (cdr (assoc 'code er))) (cmd (cdr (assoc 'cmd er)))) + (cond + ((equal? cmd ".notify") + (case code + ((611) ; Transfer state change + (let ((ires (dc-intresp resp)) (tr #f)) + (if (and ires (assoc (car ires) trans)) + (begin (set! tr (cdr (assoc (car ires) trans))) + (set-cdr! (assoc 'state tr) + (cdr (assoc (cadr ires) '((0 . wait) (1 . hs) (2 . main) (3 . done))))) + (set-cdr! (assoc 'lastprog tr) (current-time)))))) + ((614) ; Transfer error + (let ((ires (dc-intresp resp))) + (if (and ires (assoc (car ires) trans)) + (begin (logf (string-append "transfer " (number->string (car ires)) " encountered error " (number->string (cadr ires)))) + (dc-ecmd-assert 200 "cancel" (car ires)) + (let ((tr (cdr (assoc (car ires) trans)))) + (disablepeer (cdr (assoc 'peer tr)))) + (set! trans (assq-remove! trans (car ires))))))) + ((615) ; Transfer progress + (let ((ires (dc-intresp resp)) (tr #f)) + (if (and ires (assoc (car ires) trans)) + (begin (set! tr (cdr (assoc (car ires) trans))) + (set-cdr! (assoc 'curpos tr) (cadr ires)) + (set-cdr! (assoc 'lastprog tr) (current-time)))))) + ((617) ; Transfer destroyed + (let* ((ires (dc-intresp resp)) (tr (and ires (assoc (car ires) trans)))) + (if tr + (begin (if (eq? (cdr (assoc 'state (cdr tr))) 'done) + (begin (logf (string-append "transfer " (number->string (car ires)) " done")) + (set! trans (assq-remove! trans (car ires))) + (set! done #t) + (throw 'sig 0)) + (begin (logf (string-append "transfer " (number->string (car ires)) " disappeared")) + (set! trans (assq-remove! trans (car ires))))))))) + ((620) ; Search rescheduled + (let ((ires (dc-intresp resp))) + (if (and ires (= (car ires) srchid)) + (begin (set! info-searcheta (+ (current-time) (cadr ires))) + (logf (string-append "search rescheduled to T+" (number->string (cadr ires)))))))) + ((621) ; Search committed + (let ((ires (dc-intresp resp))) + (if (and ires (= (car ires) srchid)) + (begin (logf "search committed") + (set! info-searcheta 0) + (set! lastsearch (current-time)))))) + ((622) ; Search result + (let ((ires (list->vector (dc-intresp resp)))) + (if (and ires (= (vector-ref ires 0) srchid)) (apply handlesr (map (lambda (n) (vector-ref ires n)) '(1 2 3 4 5 7)))))) + + ) + ) + + ) + ) + #t) + #f) + ) + #t + ) + ) + ) + (lambda (key sig) + (logf (string-append "interrupted by signal " (number->string sig))) + (if (not done) + (set! retval 1))) + ) + (logf "quitting...") + (catch 'sig + (lambda () + (if (dc-connected) + (begin (for-each (lambda (o) + (dc-qcmd (list "cancel" (car o)))) + trans) + (if (assoc 'info-file session) + (catch 'system-error + (lambda () + (delete-file (cdr (assoc 'info-file session)))) + (lambda (key . args) #t))) + (if (and done (assoc 'tag session)) + (dc-qcmd (list "filtercmd" "rmtag" (cdr (assoc 'tag session))))) + (if (not (= srchid -1)) + (dc-qcmd (list "cansrch" srchid))) + (dc-qcmd '("quit")) + (while (dc-connected) (dc-select)) + ))) + (lambda (key sig) + (logf "forcing quit"))) + (exit retval) + ) + ) + +(setlocale LC_ALL "") +(autodl-main (command-line)) diff --git a/lib/guile/chatlog b/lib/guile/chatlog new file mode 100755 index 0000000..0c5b097 --- /dev/null +++ b/lib/guile/chatlog @@ -0,0 +1,149 @@ +#!/usr/bin/guile -s +!# + +(use-modules (dolcon ui)) +(use-modules (ice-9 pretty-print)) + +(define fnetnodes '()) + +(define (make-getopt opts optdesc) + (let ((arg opts) (curpos 0) (rest '())) + (lambda () + (if (eq? arg '()) rest + (let ((ret #f)) + (while (not ret) + (if (= curpos 0) + (if (eq? (string-ref (car arg) 0) #\-) + (set! curpos 1) + (begin + (set! rest (append rest (list (car arg)))) + (set! arg (cdr arg)) + (if (eq? arg '()) + (set! ret #t))))) + (if (> curpos 0) + (if (< curpos (string-length (car arg))) + (begin (set! ret (string-ref (car arg) curpos)) (set! curpos (+ curpos 1))) + (begin (set! curpos 0) (set! arg (cdr arg)) (if (eq? arg '()) (set! ret #t)))))) + (if (eq? ret #t) rest + (let ((opt (string-index optdesc ret))) + (if (eq? opt #f) (throw 'illegal-option ret) + (if (and (< opt (- (string-length optdesc) 1)) (eq? (string-ref optdesc (+ opt 1)) #\:)) + (let ((ret + (cons ret (let ((optarg + (if (< curpos (string-length (car arg))) + (substring (car arg) curpos) + (begin (set! arg (cdr arg)) (if (eq? arg '()) (throw 'requires-argument ret)) (car arg))))) + (set! arg (cdr arg)) optarg)))) + (set! curpos 0) + ret) + (list ret)))))))))) + +(define (fn-getnames) + (let ((resp (dc-ecmd "lsnodes")) (er #f)) + (if (and resp (begin (set! er (dc-extract resp)) er) (= (cdr (assoc 'code er)) 200)) + (let ((ires #f)) + (while (begin (set! ires (dc-intresp resp)) ires) + (if (assoc (car ires) fnetnodes) + (set-cdr! (assoc (car ires) fnetnodes) (caddr ires)) + (set! fnetnodes (cons (cons (car ires) (caddr ires)) fnetnodes)))))))) + +(define (fn-getname id) + (if (not (assoc id fnetnodes)) + (fn-getnames)) + (if (assoc id fnetnodes) + (cdr (assoc id fnetnodes)) + (number->string id))) + +;(define (fn-getname id) +; (let ((resp (dc-ecmd "lsnodes")) (er #f)) +; (if (and resp (begin (set! er (dc-extract resp)) er) (= (cdr (assoc 'code er)) 200)) +; (begin +; (catch 'found +; (lambda () +; (let ((ires #f)) +; (while (begin (set! ires (dc-intresp resp)) ires) +; (if (= (car ires) id) +; (throw 'found (caddr ires))) +; )) +; (number->string id) +; ) +; (lambda (key ret) +; ret))) +; (number->string id))) +; ) + +(define (chatlog-main args) + (let ((dc-server #f) (log-dir #f) (last-fn #f)) + (let ((getopt (make-getopt (cdr args) "hs:S:e:")) (arg #f)) + (do ((arg (getopt) (getopt))) ((not (and (pair? arg) (char? (car arg)))) (set! args arg)) + (cond ((eq? (car arg) #\h) + (begin (display "usage: chatlog [-s server] [-d log-dir]\n" (current-error-port)) + (display " chatlog -h\n" (current-error-port)) + (exit 0))) + ((eq? (car arg) #\s) + (set! dc-server (cdr arg))) + ((eq? (car arg) #\d) + (set! log-dir (cdr arg))) + ) + ) + ) + (if (not dc-server) (set! dc-server (getenv "DCSERVER"))) + (if (not dc-server) (set! dc-server "localhost")) + (if (not log-dir) (set! log-dir (string-append (getenv "HOME") "/dc/chatlog"))) + + (dc-c&l #t dc-server #t) + (dc-ecmd-assert 200 "notify" "fn:chat" "on" "fn:act" "on") + + (while #t + (dc-select 10000) + (while (let ((resp (dc-getresp))) + (if resp + (begin + (let* ((er (dc-extract resp)) (code (cdr (assoc 'code er))) (cmd (cdr (assoc 'cmd er)))) + (cond + ((equal? cmd ".notify") + (case code + ((600) + (let ((ires (list->vector (dc-intresp resp)))) + (if ires + (let ((p (open-file + (string-append log-dir "/" + (let ((fixedname (list->string + (map (lambda (c) (if (eq? c #\/) #\_ c)) + (string->list (fn-getname (vector-ref ires 0))))))) + (if (= (string-length fixedname) 0) "noname" fixedname))) + "a"))) + (if (not (eq? (vector-ref ires 0) last-fn)) + (begin (write-line (string-append " -- " (fn-getname (vector-ref ires 0)) ":")) + (set! last-fn (vector-ref ires 0)))) + (for-each + (lambda (p) + (write-line (string-append (strftime "%H:%M:%S" (localtime (current-time))) ": <" (vector-ref ires 3) "> " (vector-ref ires 4)) p)) + (list p (current-output-port))) + (close-port p)) + )) + ) + ((602) + (let ((ires (dc-intresp resp))) + (if ires + (let ((ent (assoc (car ires) fnetnodes))) + (if ent + (set-cdr! ent (cadr ires)) + (set! fnetnodes (cons (cons (car ires) (cadr ires)) fnetnodes))))))) + + ) + ) + + ) + ) + #t) + #f) + ) + #t + ) + + ) + ) + ) + +(chatlog-main (command-line)) diff --git a/lib/guile/dolcon-guile.c b/lib/guile/dolcon-guile.c new file mode 100644 index 0000000..9e05753 --- /dev/null +++ b/lib/guile/dolcon-guile.c @@ -0,0 +1,339 @@ +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include + +struct respsmob +{ + struct dc_response *resp; +}; + +struct scmcb +{ + SCM subr; +}; + +static int fd = -1; +static scm_bits_t resptype; + +static SCM scm_dc_connect(SCM host, SCM port) +{ + int cport; + + SCM_ASSERT(SCM_STRINGP(host), host, SCM_ARG1, "dc-connect"); + if(port == SCM_UNDEFINED) + { + cport = -1; + } else { + SCM_ASSERT(SCM_INUMP(port), port, SCM_ARG2, "dc-connect"); + cport = SCM_INUM(port); + } + if(fd >= 0) + dc_disconnect(); + if((fd = dc_connect(SCM_STRING_CHARS(host), cport)) < 0) + scm_syserror("dc-connect"); + return(SCM_MAKINUM(fd)); +} + +static SCM scm_dc_disconnect(void) +{ + dc_disconnect(); + return(SCM_MAKINUM(0)); +} + +static SCM scm_dc_connected(void) +{ + return((fd == -1)?SCM_BOOL_F:SCM_BOOL_T); +} + +static SCM scm_dc_select(SCM timeout) +{ + struct pollfd pfd; + int cto, ret, enob; + + if(timeout == SCM_UNDEFINED) + { + cto = -1; + } else { + SCM_ASSERT(SCM_INUMP(timeout), timeout, SCM_ARG1, "dc-select"); + cto = SCM_INUM(timeout); + } + if(fd < 0) + scm_syserror_msg("dc-select", "Not connected", SCM_EOL, ENOTCONN); + pfd.fd = fd; + pfd.events = POLLIN; + if(dc_wantwrite()) + pfd.events |= POLLOUT; + if((ret = poll(&pfd, 1, cto)) < 0) + { + if(errno == EINTR) + return(SCM_BOOL_F); + enob = errno; + dc_disconnect(); + errno = enob; + scm_syserror("dc-select"); + } + if(((pfd.revents & POLLIN) && dc_handleread()) || ((pfd.revents & POLLOUT) && dc_handlewrite())) + { + if(errno == 0) + { + fd = -1; + return(SCM_BOOL_F); + } + scm_syserror("dc-select"); + } + return(ret?SCM_BOOL_T:SCM_BOOL_F); +} + +static SCM makerespsmob(struct dc_response *resp) +{ + struct respsmob *data; + + data = scm_must_malloc(sizeof(*data), "respsmob"); + data->resp = resp; + SCM_RETURN_NEWSMOB(resptype, data); +} + +static SCM scm_dc_getresp(SCM tag) +{ + struct dc_response *resp; + SCM ret; + + if(tag == SCM_UNDEFINED) + { + if((resp = dc_getresp()) == NULL) + return(SCM_BOOL_F); + } else { + SCM_ASSERT(SCM_INUMP(tag), tag, SCM_ARG1, "dc-getresp"); + if((resp = dc_gettaggedresp(SCM_INUM(tag))) == NULL) + return(SCM_BOOL_F); + } + ret = makerespsmob(resp); + return(ret); +} + +static SCM scm_dc_extract(SCM scm_resp) +{ + int i, o; + struct dc_response *resp; + SCM ret, l, w; + + SCM_ASSERT(SCM_SMOB_PREDICATE(resptype, scm_resp), scm_resp, SCM_ARG1, "dc-extract"); + resp = ((struct respsmob *)SCM_SMOB_DATA(scm_resp))->resp; + ret = SCM_EOL; + ret = scm_cons(scm_cons(scm_str2symbol("cmd"), scm_makfrom0str(icswcstombs(resp->cmdname, "UTF-8", NULL))), ret); + ret = scm_cons(scm_cons(scm_str2symbol("code"), SCM_MAKINUM(resp->code)), ret); + ret = scm_cons(scm_cons(scm_str2symbol("tag"), SCM_MAKINUM(resp->tag)), ret); + l = SCM_EOL; + for(i = resp->numlines - 1; i >= 0; i--) + { + w = SCM_EOL; + for(o = resp->rlines[i].argc - 1; o >= 0; o--) + w = scm_cons(scm_makfrom0str(icswcstombs(resp->rlines[i].argv[o], "UTF-8", NULL)), w); + l = scm_cons(w, l); + } + ret = scm_cons(scm_cons(scm_str2symbol("resp"), l), ret); + return(ret); +} + +static SCM scm_dc_intresp(SCM scm_resp) +{ + int i; + struct dc_response *resp; + struct dc_intresp *ires; + SCM ret; + + SCM_ASSERT(SCM_SMOB_PREDICATE(resptype, scm_resp), scm_resp, SCM_ARG1, "dc-intresp"); + resp = ((struct respsmob *)SCM_SMOB_DATA(scm_resp))->resp; + if((ires = dc_interpret(resp)) == NULL) + return(SCM_BOOL_F); + ret = SCM_EOL; + for(i = ires->argc - 1; i >= 0; i--) + { + switch(ires->argv[i].type) + { + case 1: + ret = scm_cons(scm_makfrom0str(icswcstombs(ires->argv[i].val.str, "UTF-8", NULL)), ret); + break; + case 2: + ret = scm_cons(scm_int2num(ires->argv[i].val.num), ret); + break; + case 3: + ret = scm_cons(scm_double2num(ires->argv[i].val.flnum), ret); + break; + } + } + dc_freeires(ires); + return(ret); +} + +static int qcmd_scmcb(struct dc_response *resp) +{ + struct scmcb *scmcb; + + scmcb = resp->data; + scm_apply(scmcb->subr, scm_cons(makerespsmob(resp), SCM_EOL), SCM_EOL); + scm_gc_unprotect_object(scmcb->subr); + free(scmcb); + return(2); +} + +static SCM scm_dc_qcmd(SCM argv, SCM callback) +{ + int tag, enob; + wchar_t **toks, *tok, *cmd; + size_t tokssize, toksdata; + SCM port; + struct scmcb *scmcb; + + SCM_ASSERT(SCM_CONSP(argv), argv, SCM_ARG1, "dc-qcmd"); + if(callback != SCM_UNDEFINED) + SCM_ASSERT(SCM_CLOSUREP(callback), callback, SCM_ARG2, "dc-qcmd"); + cmd = NULL; + toks = NULL; + tokssize = toksdata = 0; + for(; argv != SCM_EOL; argv = SCM_CDR(argv)) + { + port = scm_open_output_string(); + scm_display(SCM_CAR(argv), port); + if((tok = icmbstowcs(SCM_STRING_CHARS(scm_get_output_string(port)), "UTF-8")) == NULL) + { + enob = errno; + addtobuf(toks, NULL); + dc_freewcsarr(toks); + if(cmd != NULL) + free(cmd); + errno = enob; + scm_syserror("dc-qcmd"); + } + if(cmd == NULL) + cmd = tok; + else + addtobuf(toks, tok); + } + addtobuf(toks, NULL); + if(callback == SCM_UNDEFINED) + { + tag = dc_queuecmd(NULL, NULL, cmd, L"%%a", toks, NULL); + } else { + scmcb = scm_must_malloc(sizeof(*scmcb), "scmcb"); + scm_gc_protect_object(scmcb->subr = callback); + tag = dc_queuecmd(qcmd_scmcb, scmcb, cmd, L"%%a", toks, NULL); + } + dc_freewcsarr(toks); + if(cmd != NULL) + free(cmd); + return(SCM_MAKINUM(tag)); +} + +static void login_scmcb(int err, wchar_t *reason, struct scmcb *scmcb) +{ + SCM errsym; + + switch(err) + { + case DC_LOGIN_ERR_SUCCESS: + errsym = scm_str2symbol("success"); + break; + case DC_LOGIN_ERR_NOLOGIN: + errsym = scm_str2symbol("nologin"); + break; + case DC_LOGIN_ERR_SERVER: + errsym = scm_str2symbol("server"); + break; + case DC_LOGIN_ERR_USER: + errsym = scm_str2symbol("user"); + break; + case DC_LOGIN_ERR_CONV: + errsym = scm_str2symbol("conv"); + break; + case DC_LOGIN_ERR_AUTHFAIL: + errsym = scm_str2symbol("authfail"); + break; + } + scm_apply(scmcb->subr, scm_cons(errsym, scm_cons((reason == NULL)?SCM_BOOL_F:scm_makfrom0str(icswcstombs(reason, "UTF-8", NULL)), SCM_EOL)), SCM_EOL); + scm_gc_unprotect_object(scmcb->subr); + free(scmcb); +} + +static SCM scm_dc_loginasync(SCM callback, SCM useauthless, SCM username) +{ + struct scmcb *scmcb; + + SCM_ASSERT(SCM_CLOSUREP(callback), callback, SCM_ARG1, "dc-loginasync"); + scmcb = scm_must_malloc(sizeof(*scmcb), "scmcb"); + scm_gc_protect_object(scmcb->subr = callback); + dc_loginasync(SCM_STRINGP(username)?SCM_STRING_CHARS(username):NULL, SCM_NFALSEP(useauthless), NULL, (void (*)(int, wchar_t *, void *))login_scmcb, scmcb); + return(SCM_BOOL_T); +} + +static SCM scm_dc_lexsexpr(SCM sexpr) +{ + SCM ret; + wchar_t **arr, **ap, *buf; + + SCM_ASSERT(SCM_STRINGP(sexpr), sexpr, SCM_ARG1, "dc-lexsexpr"); + if((buf = icmbstowcs(SCM_STRING_CHARS(sexpr), NULL)) == NULL) + scm_syserror("dc-lexsexpr"); + arr = dc_lexsexpr(buf); + free(buf); + ret = SCM_EOL; + if(arr != NULL) + { + for(ap = arr; *ap != NULL; ap++) + ret = scm_cons(scm_makfrom0str(icswcstombs(*ap, "UTF-8", NULL)), ret); + dc_freewcsarr(arr); + } + return(scm_reverse(ret)); +} + +static size_t resp_free(SCM respsmob) +{ + struct respsmob *data; + + data = (struct respsmob *)SCM_SMOB_DATA(respsmob); + dc_freeresp(data->resp); + free(data); + return(sizeof(*data)); +} + +static int resp_print(SCM respsmob, SCM port, scm_print_state *pstate) +{ + struct respsmob *data; + + data = (struct respsmob *)SCM_SMOB_DATA(respsmob); + scm_puts("#resp->tag), port); + scm_puts(" ", port); + scm_puts(icswcstombs(data->resp->cmdname, "UTF-8", NULL), port); + scm_puts(" ", port); + scm_display(SCM_MAKINUM(data->resp->code), port); + scm_puts(">", port); + return(1); +} + +void init_guiledc(void) +{ + scm_c_define_gsubr("dc-connect", 1, 1, 0, scm_dc_connect); + scm_c_define_gsubr("dc-disconnect", 0, 0, 0, scm_dc_disconnect); + scm_c_define_gsubr("dc-connected", 0, 0, 0, scm_dc_connected); + scm_c_define_gsubr("dc-select", 0, 1, 0, scm_dc_select); + scm_c_define_gsubr("dc-getresp", 0, 1, 0, scm_dc_getresp); + scm_c_define_gsubr("dc-extract", 1, 0, 0, scm_dc_extract); + scm_c_define_gsubr("dc-intresp", 1, 0, 0, scm_dc_intresp); + scm_c_define_gsubr("dc-qcmd", 1, 1, 0, scm_dc_qcmd); + scm_c_define_gsubr("dc-loginasync", 2, 1, 0, scm_dc_loginasync); + scm_c_define_gsubr("dc-lexsexpr", 1, 0, 0, scm_dc_lexsexpr); + resptype = scm_make_smob_type("dc-resp", sizeof(struct respsmob)); + scm_set_smob_free(resptype, resp_free); + scm_set_smob_print(resptype, resp_print); + dc_init(); +} diff --git a/lib/guile/dolcon/CVS/Entries b/lib/guile/dolcon/CVS/Entries new file mode 100644 index 0000000..662f626 --- /dev/null +++ b/lib/guile/dolcon/CVS/Entries @@ -0,0 +1,4 @@ +/Makefile.am/1.1/Sun Sep 19 03:51:39 2004// +/ui.scm/1.8/Fri Jun 10 00:24:54 2005// +/util.scm/1.1/Thu Jun 2 18:05:45 2005// +D diff --git a/lib/guile/dolcon/CVS/Repository b/lib/guile/dolcon/CVS/Repository new file mode 100644 index 0000000..952d17b --- /dev/null +++ b/lib/guile/dolcon/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/lib/guile/dolcon diff --git a/lib/guile/dolcon/CVS/Root b/lib/guile/dolcon/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/lib/guile/dolcon/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/lib/guile/dolcon/Makefile.am b/lib/guile/dolcon/Makefile.am new file mode 100644 index 0000000..4996736 --- /dev/null +++ b/lib/guile/dolcon/Makefile.am @@ -0,0 +1,4 @@ +moduledir=$(datadir)/guile/site/dolcon +module_DATA=ui.scm + +EXTRA_DIST=ui.scm diff --git a/lib/guile/dolcon/ui.scm b/lib/guile/dolcon/ui.scm new file mode 100644 index 0000000..73828df --- /dev/null +++ b/lib/guile/dolcon/ui.scm @@ -0,0 +1,74 @@ +(define-module (dolcon ui)) + +(export dc-connect dc-disconnect dc-connected dc-select dc-getresp dc-extract dc-intresp dc-qcmd dc-loginasync dc-lexsexpr) + +(load-extension "libdolcon-guile" "init_guiledc") + +(define-public dc-login + (lambda (useauthless . username) + (let ((done #f) (errsym #f)) + (dc-loginasync + (lambda (err reason) + (set! errsym err) + (set! done #t)) + useauthless (if (pair? username) (car username) #f)) + (while (not done) (dc-select)) + errsym))) + +(define-public dc-must-connect + (lambda args + (let* ((fd (apply dc-connect args)) (resp (dc-extract (do ((resp (dc-getresp) (dc-getresp))) + ((and resp + (equal? (cdr (assoc 'cmd (dc-extract resp))) ".connect")) + resp) + (dc-select))))) + (if (= (cdr (assoc 'code resp)) 200) + fd + (throw 'bad-return (cdr (assoc 'code resp)) (cadr (assoc 'resp resp))) + ) + ) + ) + ) + +(define-public dc-c&l + (lambda (verbose host useauthless) + (let ((fd -1) (print (lambda (obj) (if verbose (display obj (if (port? verbose) verbose (current-error-port))))))) + (print "connecting...\n") + (set! fd (dc-must-connect host)) + (print "authenticating...\n") + (let ((ret (dc-login useauthless))) + (if (not (eq? ret 'success)) + (throw 'login-failure ret))) + (print "authentication success\n") + fd) + ) + ) + +(define-public dc-ecmd + (lambda args + (let ((tag (dc-qcmd args))) + (do ((resp (dc-getresp tag) (dc-getresp tag))) + (resp resp) + (dc-select)) + ) + ) + ) + +(define-public dc-ecmd-assert + (lambda (code . args) + (let* ((resp (apply dc-ecmd args)) (eresp (dc-extract resp))) + (if (not (if (list? code) + (memq (cdr (assoc 'code eresp)) code) + (= (cdr (assoc 'code eresp)) code))) + (throw 'bad-return (cdr (assoc 'code eresp)) (cadr (assoc 'resp eresp))) + ) + resp + ) + ) + ) + +(define-public dc-intall + (lambda (resp) + (let ((retlist '())) + (do ((ires (dc-intresp resp) (dc-intresp resp))) ((not ires) retlist) + (set! retlist (append retlist (list ires))))))) diff --git a/lib/guile/dolcon/util.scm b/lib/guile/dolcon/util.scm new file mode 100644 index 0000000..c772efc --- /dev/null +++ b/lib/guile/dolcon/util.scm @@ -0,0 +1,40 @@ +(define-module (dolcon util)) +(use-modules (dolcon ui)) + +(define fnetnodes '()) + +(define-public dc-fn-update + (lambda () + (set! fnetnodes + (let ((resp (dc-ecmd "lsnodes")) (er #f)) + (if (and resp (begin (set! er (dc-extract resp)) er) (= (cdr (assq 'code er)) 200)) + (map (lambda (o) + (apply (lambda (id net name users state) + (cons id + (list (cons 'id id) + (cons 'net net) + (cons 'name name) + (cons 'users users) + (cons 'state (list-ref '(syn hs est dead) state))))) + o)) + (dc-intall resp)) + '()))) + fnetnodes)) + +(define-public dc-fn-getattrib + (lambda (id attrib) + (if (not (assq id fnetnodes)) + (dc-fn-update)) + (let ((aform (assq id fnetnodes))) + (if aform + (cdr (assq attrib (cdr aform))) + #f)))) + +(define-public dc-fn-getname + (lambda (id) + (dc-fn-getattrib id 'name))) + +(define-public dc-getfnetnodes + (lambda () + (map (lambda (o) (car o)) + fnetnodes))) diff --git a/lib/initcmds.h b/lib/initcmds.h new file mode 100644 index 0000000..70475c0 --- /dev/null +++ b/lib/initcmds.h @@ -0,0 +1,138 @@ + +/* Do not modify this file - it is autogenerated by makecmds */ + +static void initcmds(void) +{ + struct command *cmd; + + cmd = makecmd(L"lssr"); + addresp(cmd, 200, RESP_STR, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_INT, RESP_FLOAT, RESP_END); + addresp(cmd, 201, RESP_END); + addresp(cmd, 514, RESP_END); + cmd = makecmd(L"lssrch"); + addresp(cmd, 200, RESP_INT, RESP_INT, RESP_INT, RESP_INT, RESP_END); + addresp(cmd, 201, RESP_END); + cmd = makecmd(L"shutdown"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 502, RESP_END); + cmd = makecmd(L"quit"); + addresp(cmd, 200, RESP_END); + cmd = makecmd(L"lsauth"); + addresp(cmd, 200, RESP_STR, RESP_END); + addresp(cmd, 201, RESP_END); + cmd = makecmd(L"login"); + addresp(cmd, 200, RESP_END); /* Success */ + addresp(cmd, 300, RESP_STR, RESP_END); /* Auto (think GSS-API) */ + addresp(cmd, 301, RESP_STR, RESP_END); /* No echo */ + addresp(cmd, 302, RESP_STR, RESP_END); /* Echo */ + addresp(cmd, 303, RESP_STR, RESP_END); /* Info message */ + addresp(cmd, 304, RESP_STR, RESP_END); /* Error message */ + addresp(cmd, 501, RESP_END); + addresp(cmd, 503, RESP_END); /* Already logging in */ + addresp(cmd, 504, RESP_END); /* Charset error */ + addresp(cmd, 505, RESP_DSC, RESP_STR, RESP_END); /* Back-end error */ + addresp(cmd, 506, RESP_END); /* Authentication error */ + addresp(cmd, 508, RESP_END); /* No such authentication mechanism */ + cmd = makecmd(L"pass"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 300, RESP_STR, RESP_END); + addresp(cmd, 301, RESP_STR, RESP_END); + addresp(cmd, 302, RESP_STR, RESP_END); + addresp(cmd, 303, RESP_STR, RESP_END); + addresp(cmd, 304, RESP_STR, RESP_END); + addresp(cmd, 504, RESP_END); + addresp(cmd, 505, RESP_DSC, RESP_STR, RESP_END); + addresp(cmd, 506, RESP_END); + addresp(cmd, 507, RESP_END); /* Data not expected */ + cmd = makecmd(L"cnct"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 504, RESP_END); + addresp(cmd, 509, RESP_END); + addresp(cmd, 511, RESP_END); + cmd = makecmd(L"lsnodes"); + addresp(cmd, 200, RESP_INT, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_END); + addresp(cmd, 201, RESP_END); + addresp(cmd, 502, RESP_END); + cmd = makecmd(L"dcnct"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 510, RESP_END); + cmd = makecmd(L"lspeers"); + addresp(cmd, 200, RESP_STR, RESP_STR, RESP_END); /* Peer ID and nick are standardized, so they can be here -- the rest have to be extracted manually */ + addresp(cmd, 201, RESP_END); + addresp(cmd, 510, RESP_END); + cmd = makecmd(L"lspa"); + addresp(cmd, 200, RESP_STR, RESP_INT, RESP_END); + addresp(cmd, 201, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 510, RESP_END); + cmd = makecmd(L"download"); + addresp(cmd, 200, RESP_INT, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 510, RESP_END); + addresp(cmd, 511, RESP_END); + cmd = makecmd(L"lstrans"); + addresp(cmd, 200, RESP_INT, RESP_INT, RESP_INT, RESP_STR, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_END); + addresp(cmd, 201, RESP_END); + addresp(cmd, 502, RESP_END); + cmd = makecmd(L"cancel"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 512, RESP_END); + cmd = makecmd(L"notify"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 501, RESP_END); + cmd = makecmd(L"sendchat"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 501, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 504, RESP_END); + addresp(cmd, 505, RESP_END); + addresp(cmd, 509, RESP_END); + addresp(cmd, 510, RESP_END); + addresp(cmd, 513, RESP_END); + cmd = makecmd(L"search"); + addresp(cmd, 200, RESP_INT, RESP_INT, RESP_END); + addresp(cmd, 501, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 509, RESP_END); + addresp(cmd, 510, RESP_END); + cmd = makecmd(L"cansrch"); + addresp(cmd, 200, RESP_END); + addresp(cmd, 514, RESP_END); + cmd = makecmd(L"filtercmd"); + addresp(cmd, 200, RESP_STR, RESP_END); + addresp(cmd, 201, RESP_END); + addresp(cmd, 501, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 504, RESP_END); + addresp(cmd, 505, RESP_END); + cmd = makecmd(L"lstrarg"); + addresp(cmd, 200, RESP_STR, RESP_STR, RESP_END); + addresp(cmd, 201, RESP_END); + addresp(cmd, 501, RESP_END); + addresp(cmd, 502, RESP_END); + addresp(cmd, 512, RESP_END); + cmd = makecmd(NULL); /* Nameless notification */ + addresp(cmd, 600, RESP_INT, RESP_INT, RESP_STR, RESP_STR, RESP_STR, RESP_END); /* FN chat */ + addresp(cmd, 601, RESP_INT, RESP_INT, RESP_END); /* FN state change */ + addresp(cmd, 602, RESP_INT, RESP_STR, RESP_END); /* FN name change */ + addresp(cmd, 603, RESP_INT, RESP_END); /* FN destroy */ + addresp(cmd, 604, RESP_INT, RESP_STR, RESP_END); /* FN create */ + addresp(cmd, 605, RESP_INT, RESP_INT, RESP_END); /* FN num peers change */ + addresp(cmd, 610, RESP_INT, RESP_INT, RESP_STR, RESP_STR, RESP_END); /* Transfer create */ + addresp(cmd, 611, RESP_INT, RESP_INT, RESP_END); /* Transfer state change */ + addresp(cmd, 612, RESP_INT, RESP_STR, RESP_END); /* Transfer nick change */ + addresp(cmd, 613, RESP_INT, RESP_INT, RESP_END); /* Transfer size change */ + addresp(cmd, 614, RESP_INT, RESP_INT, RESP_END); /* Transfer error update */ + addresp(cmd, 615, RESP_INT, RESP_INT, RESP_END); /* Transfer progress */ + addresp(cmd, 616, RESP_INT, RESP_STR, RESP_END); /* Transfer path change */ + addresp(cmd, 617, RESP_INT, RESP_END); /* Transfer destroy */ + addresp(cmd, 620, RESP_INT, RESP_INT, RESP_END); + addresp(cmd, 621, RESP_INT, RESP_END); + addresp(cmd, 622, RESP_INT, RESP_STR, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_INT, RESP_FLOAT, RESP_END); + cmd = makecmd(NULL); /* Nameless connection */ + addresp(cmd, 200, RESP_STR, RESP_END); + addresp(cmd, 502, RESP_END); +} diff --git a/lib/makecmds b/lib/makecmds new file mode 100755 index 0000000..750b43d --- /dev/null +++ b/lib/makecmds @@ -0,0 +1,49 @@ +#!/usr/bin/perl + +%keys = + ( + "s" => "RESP_STR", + "d" => "RESP_DSC", + "i" => "RESP_INT", + "f" => "RESP_FLOAT" + ); + +print(" +/* Do not modify this file - it is autogenerated by makecmds */ + +static void initcmds(void) +{ + struct command *cmd; + +"); + +while(<>) +{ + ($comment) = /\s*;\s*(.*)$/; + s/\s*;.*$//; + if(($name) = /^:(\w*)/) + { + if($name) + { + print(" cmd = makecmd(L\"$name\");"); + } else { + print(" cmd = makecmd(NULL);"); + } + if($comment) + { + print("\t/* $comment */"); + } + print("\n"); + } elsif(/^([0-9]{3})\s+/g) { + print(" addresp(cmd, $1"); + print(", " . $keys{$1}) while /\G(\S+)\s+/g; + print(", RESP_END);"); + if($comment) + { + print("\t/* $comment */"); + } + print("\n"); + } +} + +print("}\n"); diff --git a/lib/uicmds b/lib/uicmds new file mode 100644 index 0000000..d59d03d --- /dev/null +++ b/lib/uicmds @@ -0,0 +1,130 @@ +:lssr +200 s s s i i i f +201 +514 +:lssrch +200 i i i i +201 +:shutdown +200 +502 +:quit +200 +:lsauth +200 s +201 +:login +200 ; Success +300 s ; Auto (think GSS-API) +301 s ; No echo +302 s ; Echo +303 s ; Info message +304 s ; Error message +501 +503 ; Already logging in +504 ; Charset error +505 d s ; Back-end error +506 ; Authentication error +508 ; No such authentication mechanism +:pass +200 +300 s +301 s +302 s +303 s +304 s +504 +505 d s +506 +507 ; Data not expected +:cnct +200 +502 +504 +509 +511 +:lsnodes +200 i s s i i +201 +502 +:dcnct +200 +502 +510 +:lspeers +200 s s ; Peer ID and nick are standardized, so they can be here -- the rest have to be extracted manually +201 +510 +:lspa +200 s i +201 +502 +510 +:download +200 i +502 +510 +511 +:lstrans +200 i i i s s s i i +201 +502 +:cancel +200 +502 +512 +:notify +200 +501 +:sendchat +200 +501 +502 +504 +505 +509 +510 +513 +:search +200 i i +501 +502 +509 +510 +:cansrch +200 +514 +:filtercmd +200 s +201 +501 +502 +504 +505 +:lstrarg +200 s s +201 +501 +502 +512 +: ; Nameless notification +600 i i s s s ; FN chat +601 i i ; FN state change +602 i s ; FN name change +603 i ; FN destroy +604 i s ; FN create +605 i i ; FN num peers change +610 i i s s ; Transfer create +611 i i ; Transfer state change +612 i s ; Transfer nick change +613 i i ; Transfer size change +614 i i ; Transfer error update +615 i i ; Transfer progress +616 i s ; Transfer path change +617 i ; Transfer destroy +620 i i +621 i +622 i s s s i i i f +: ; Nameless connection +200 s +502 diff --git a/lib/uilib.c b/lib/uilib.c new file mode 100644 index 0000000..4d440de --- /dev/null +++ b/lib/uilib.c @@ -0,0 +1,1108 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +*/ + +/* + * Note: This code is still ugly, since I copied it almost verbatim + * from the daemon's parser. It would need serious cleanups, but at + * least it works for now. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_RESOLVER +#include +#include +#endif + +#include +#include + +#define RESP_END -1 +#define RESP_DSC 0 +#define RESP_STR 1 +#define RESP_INT 2 +#define RESP_FLOAT 3 + +struct respclass +{ + struct respclass *next; + int code; + int nwords; + int *wordt; +}; + +struct command +{ + struct command *next; + wchar_t *name; + struct respclass *classes; +}; + +struct qcmd +{ + struct qcmd *next; + struct command *cmd; + int tag; + char *buf; + size_t buflen; + int (*callback)(struct dc_response *resp); + void *data; +}; + +void dc_uimisc_disconnected(void); + +/* The first command must be the nameless connect command and second + * the notification command. */ +static struct command *commands = NULL; +static struct qcmd *queue = NULL, *queueend = NULL; +static struct dc_response *respqueue = NULL, *respqueueend = NULL; +static int state = -1; +static int fd = -1; +static iconv_t ichandle; +static int resetreader = 1; +static char *dchostname = NULL; +static struct addrinfo *hostlist = NULL, *curhost = NULL; +static int servport; + +static struct dc_response *makeresp(void) +{ + struct dc_response *new; + + new = smalloc(sizeof(*new)); + new->next = NULL; + new->prev = NULL; + new->code = 0; + new->cmdname = NULL; + new->rlines = NULL; + new->linessize = 0; + new->numlines = 0; + new->curline = 0; + new->data = NULL; + return(new); +} + +static void freeqcmd(struct qcmd *qcmd) +{ + if(qcmd->buf != NULL) + free(qcmd->buf); + free(qcmd); +} + +static void unlinkqueue(void) +{ + struct qcmd *qcmd; + + if((qcmd = queue) == NULL) + return; + queue = qcmd->next; + if(queue == NULL) + queueend = NULL; + freeqcmd(qcmd); +} + +static struct qcmd *makeqcmd(wchar_t *name) +{ + struct qcmd *new; + struct command *cmd; + static int tag = 0; + + if(name == NULL) + { + cmd = commands; + } else { + for(cmd = commands; cmd != NULL; cmd = cmd->next) + { + if((cmd->name != NULL) && !wcscmp(cmd->name, name)) + break; + } + if(cmd == NULL) + return(NULL); + } + new = smalloc(sizeof(*new)); + new->tag = tag++; + new->cmd = cmd; + new->buf = NULL; + new->buflen = 0; + new->callback = NULL; + new->next = NULL; + new->data = NULL; + if(queueend == NULL) + { + queue = queueend = new; + } else { + queueend->next = new; + queueend = new; + } + return(new); +} + +static wchar_t *quoteword(wchar_t *word) +{ + wchar_t *wp, *buf, *bp; + int dq, numbs, numc; + + dq = 0; + numbs = 0; + numc = 0; + if(*word == L'\0') + { + dq = 1; + } else { + for(wp = word; *wp != L'\0'; wp++) + { + if(!dq && iswspace(*wp)) + dq = 1; + if((*wp == L'\\') || (*wp == L'\"')) + numbs++; + numc++; + } + } + if(!dq && !numbs) + return(NULL); + bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1)); + if(dq) + *(bp++) = L'\"'; + for(wp = word; *wp != L'\0'; wp++) + { + if((*wp == L'\\') || (*wp == L'\"')) + *(bp++) = L'\\'; + *(bp++) = *wp; + } + if(dq) + *(bp++) = L'\"'; + *(bp++) = L'\0'; + return(buf); +} + +static struct command *makecmd(wchar_t *name) +{ + struct command *new; + + new = smalloc(sizeof(*new)); + new->name = name; + new->classes = NULL; + new->next = commands; + commands = new; + return(new); +} + +static struct respclass *addresp(struct command *cmd, int code, ...) +{ + struct respclass *new; + int i; + int resps[128]; + va_list args; + + va_start(args, code); + i = 0; + while((resps[i++] = va_arg(args, int)) != RESP_END); + i--; + va_end(args); + new = smalloc(sizeof(*new)); + new->code = code; + new->nwords = i; + if(i > 0) + { + new->wordt = smalloc(sizeof(int) * i); + memcpy(new->wordt, resps, sizeof(int) * i); + } else { + new->wordt = NULL; + } + new->next = cmd->classes; + cmd->classes = new; + return(new); +} + +#include "initcmds.h" + +int dc_init(void) +{ + if((ichandle = iconv_open("wchar_t", "utf-8")) == (iconv_t)-1) + return(-1); + initcmds(); + return(0); +} + +void dc_cleanup(void) +{ + iconv_close(ichandle); +} + +void dc_disconnect(void) +{ + struct dc_response *resp; + + state = -1; + if(fd >= 0) + close(fd); + fd = -1; + while(queue != NULL) + unlinkqueue(); + while((resp = dc_getresp()) != NULL) + dc_freeresp(resp); + dc_uimisc_disconnected(); + if(dchostname != NULL) + free(dchostname); + dchostname = NULL; +} + +void dc_freeresp(struct dc_response *resp) +{ + int i, o; + + for(i = 0; i < resp->numlines; i++) + { + for(o = 0; o < resp->rlines[i].argc; o++) + free(resp->rlines[i].argv[o]); + free(resp->rlines[i].argv); + } + free(resp->rlines); + free(resp); +} + +struct dc_response *dc_getresp(void) +{ + struct dc_response *ret; + + if((ret = respqueue) == NULL) + return(NULL); + respqueue = ret->next; + if(respqueue == NULL) + respqueueend = NULL; + else + respqueue->prev = NULL; + return(ret); +} + +struct dc_response *dc_gettaggedresp(int tag) +{ + struct dc_response *resp; + + for(resp = respqueue; resp != NULL; resp = resp->next) + { + if(resp->tag == tag) + { + if(resp->prev != NULL) + resp->prev->next = resp->next; + if(resp->next != NULL) + resp->next->prev = resp->prev; + if(resp == respqueue) + respqueue = resp->next; + if(resp == respqueueend) + respqueueend = resp->prev; + return(resp); + } + } + return(NULL); +} + +struct dc_response *dc_gettaggedrespsync(int tag) +{ + struct pollfd pfd; + struct dc_response *resp; + + while((resp = dc_gettaggedresp(tag)) == NULL) + { + pfd.fd = fd; + pfd.events = POLLIN; + if(dc_wantwrite()) + pfd.events |= POLLOUT; + if(poll(&pfd, 1, -1) < 0) + return(NULL); + if((pfd.revents & POLLIN) && dc_handleread()) + return(NULL); + if((pfd.revents & POLLOUT) && dc_handlewrite()) + return(NULL); + } + return(resp); +} + +int dc_wantwrite(void) +{ + switch(state) + { + case 1: + if((queue != NULL) && (queue->buflen > 0)) + return(1); + break; + } + return(0); +} + +int dc_getstate(void) +{ + return(state); +} + +int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...) +{ + struct qcmd *qcmd; + int num, freepart; + va_list al; + char *final; + wchar_t **toks; + wchar_t *buf; + wchar_t *part, *tpart; + size_t bufsize, bufdata; + + buf = NULL; + bufsize = bufdata = 0; + num = 0; + va_start(al, data); + while((part = va_arg(al, wchar_t *)) != NULL) + { + if(!wcscmp(part, L"%%a")) + { + for(toks = va_arg(al, wchar_t **); *toks != NULL; toks++) + { + part = *toks; + freepart = 0; + if((tpart = quoteword(part)) != NULL) + { + freepart = 1; + part = tpart; + } + addtobuf(buf, L' '); + bufcat(buf, part, wcslen(part)); + num++; + if(freepart) + free(part); + } + } else { + if(*part == L'%') + { + /* This demands that all arguments that are passed to the + * function are of equal length, that of an int. I know + * that GCC does that on IA32 platforms, but I do not know + * which other platforms and compilers that it applies + * to. If this breaks your platform, please mail me about + * it. + */ + part = vswprintf2(tpart = (part + 1), al); + for(; *tpart != L'\0'; tpart++) + { + if(*tpart == L'%') + { + if(tpart[1] == L'%') + tpart++; + else + va_arg(al, int); + } + } + freepart = 1; + } else { + freepart = 0; + } + if((tpart = quoteword(part)) != NULL) + { + if(freepart) + free(part); + part = tpart; + freepart = 1; + } + if(num > 0) + addtobuf(buf, L' '); + if(num == 0) + { + if((qcmd = makeqcmd(part)) == NULL) + { + if(freepart) + free(part); + if(buf != NULL) + free(buf); + return(-1); + } else { + qcmd->callback = callback; + qcmd->data = data; + } + } + bufcat(buf, part, wcslen(part)); + num++; + if(freepart) + free(part); + } + } + bufcat(buf, L"\r\n\0", 3); + if((final = icwcstombs(buf, "utf-8")) == NULL) + { + free(buf); + return(-1); + } + va_end(al); + free(buf); + qcmd->buf = final; + qcmd->buflen = strlen(final); + return(qcmd->tag); +} + +int dc_handleread(void) +{ + int ret, done; + char *p1, *p2; + size_t len; + int errnobak; + /* Ewww... this really is soo ugly. I need to clean this up some day. */ + static int pstate = 0; + static char inbuf[128]; + static size_t inbufdata = 0; + static wchar_t *cbuf = NULL; + static size_t cbufsize = 0, cbufdata = 0; + static wchar_t *pptr = NULL; + static wchar_t **argv = NULL; + static int argc = 0, args = 0; + static wchar_t *cw = NULL; + static size_t cwsize = 0, cwdata = 0; + static struct dc_response *curresp = NULL; + static int cont = 0; + static int unlink = 0; + + switch(state) + { + case -1: + return(-1); + case 0: + len = sizeof(ret); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len); + if(ret) + { + int newfd; + struct sockaddr_storage addr; + struct sockaddr_in *ipv4; + struct sockaddr_in6 *ipv6; + + for(curhost = curhost->ai_next; curhost != NULL; curhost = curhost->ai_next) + { + if((newfd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0) + { + errnobak = errno; + dc_disconnect(); + errno = errnobak; + return(-1); + } + dup2(newfd, fd); + close(newfd); + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + memcpy(&addr, curhost->ai_addr, curhost->ai_addrlen); + if(addr.ss_family == AF_INET) + { + ipv4 = (struct sockaddr_in *)&addr; + ipv4->sin_port = htons(servport); + } +#ifdef HAVE_IPV6 + if(addr.ss_family == AF_INET6) + { + ipv6 = (struct sockaddr_in6 *)&addr; + ipv6->sin6_port = htons(servport); + } +#endif + if(connect(fd, (struct sockaddr *)&addr, curhost->ai_addrlen)) + { + if(errno == EINPROGRESS) + return(0); + } else { + break; + } + } + if(curhost == NULL) + { + dc_disconnect(); + errno = ret; + return(-1); + } + } + state = 1; + resetreader = 1; + break; + case 1: + if(resetreader) + { + inbufdata = 0; + cbufdata = 0; + pptr = NULL; + pstate = 0; + resetreader = 0; + cont = 0; + if(curresp != NULL) + dc_freeresp(curresp); + curresp = NULL; + } + if(inbufdata == 128) + inbufdata = 0; + ret = read(fd, inbuf + inbufdata, 128 - inbufdata); + if(ret < 0) + { + if((errno == EAGAIN) || (errno == EINTR)) + return(0); + errnobak = errno; + dc_disconnect(); + errno = errnobak; + return(-1); + } else if(ret == 0) { + dc_disconnect(); + errno = 0; + return(-1); + } + inbufdata += ret; + done = 0; + while(!done) + { + if(cbufsize == cbufdata) + { + if(pptr != NULL) + len = pptr - cbuf; + if((cbuf = realloc(cbuf, sizeof(wchar_t) * (cbufsize += 256))) == NULL) + { + dc_disconnect(); + errno = ENOMEM; + return(-1); + } + if(pptr != NULL) + pptr = cbuf + len; + } + p1 = inbuf; + p2 = (char *)(cbuf + cbufdata); + len = sizeof(wchar_t) * (cbufsize - cbufdata); + ret = iconv(ichandle, &p1, &inbufdata, &p2, &len); + memmove(inbuf, p1, inbufdata); + cbufdata = cbufsize - (len / sizeof(wchar_t)); + if(ret < 0) + { + switch(errno) + { + case EILSEQ: + /* XXX Is this really OK? */ + inbufdata = 0; + done = 1; + break; + case EINVAL: + done = 1; + break; + case E2BIG: + break; + default: + errnobak = errno; + dc_disconnect(); + errno = errnobak; + return(-1); + } + } else { + done = 1; + } + } + if(pptr == NULL) + pptr = cbuf; + done = 0; + while(!done && (pptr - cbuf < cbufdata)) + { + switch(pstate) + { + case 0: + if(iswspace(*pptr)) + { + if(*pptr == L'\r') + { + if(pptr == cbuf + cbufdata - 1) + { + done = 1; + break; + } + if(*(++pptr) == L'\n') + { + if(curresp == NULL) + { + curresp = makeresp(); + if((argc > 0) && ((curresp->code = wcstol(argv[0], NULL, 10)) >= 600)) + { + curresp->cmdname = L".notify"; + curresp->internal = commands->next; + curresp->tag = -1; + unlink = 0; + } else { + if((curresp->cmdname = queue->cmd->name) == NULL) + curresp->cmdname = L".connect"; + curresp->data = queue->data; + curresp->tag = queue->tag; + curresp->internal = (void *)(queue->cmd); + unlink = 1; + } + } + sizebuf(&curresp->rlines, &curresp->linessize, curresp->numlines + 1, sizeof(*(curresp->rlines)), 1); + curresp->rlines[curresp->numlines].argc = argc; + curresp->rlines[curresp->numlines].argv = argv; + argv = NULL; + argc = args = 0; + curresp->numlines++; + if(!cont) + { + if((curresp->code >= 600) || (queue == NULL) || (queue->callback == NULL)) + ret = 0; + else + ret = queue->callback(curresp); + if(ret == 0) + { + if(respqueue == NULL) + { + respqueue = respqueueend = curresp; + } else { + curresp->next = NULL; + curresp->prev = respqueueend; + respqueueend->next = curresp; + respqueueend = curresp; + } + } else if(ret == 1) { + dc_freeresp(curresp); + } + curresp = NULL; + if(unlink) + unlinkqueue(); + } + wmemmove(cbuf, pptr, cbufdata -= (pptr - cbuf)); + pptr = cbuf; + } else { + pptr++; + } + } else { + pptr++; + } + } else { + pstate = 1; + cwdata = 0; + } + break; + case 1: + if(iswspace(*pptr) || ((argc == 0) && (*pptr == L'-'))) + { + if(argc == 0) + { + if(*pptr == L'-') + { + cont = 1; + pptr++; + } else { + cont = 0; + } + } + addtobuf(cw, L'\0'); + sizebuf(&argv, &args, argc + 1, sizeof(*argv), 1); + argv[argc++] = cw; + cw = NULL; + cwsize = cwdata = 0; + pstate = 0; + } else if(*pptr == L'\"') { + pstate = 2; + pptr++; + } else if(*pptr == L'\\') { + if(pptr == cbuf + cbufdata - 1) + { + done = 1; + break; + } + addtobuf(cw, *(++pptr)); + pptr++; + } else { + addtobuf(cw, *(pptr++)); + } + break; + case 2: + if(*pptr == L'\"') + { + pstate = 1; + } else if(*pptr == L'\\') { + addtobuf(cw, *(++pptr)); + } else { + addtobuf(cw, *pptr); + } + pptr++; + break; + } + } + break; + } + return(0); +} + +int dc_handlewrite(void) +{ + int ret; + int errnobak; + + switch(state) + { + case 1: + if(queue->buflen > 0) + { + ret = send(fd, queue->buf, queue->buflen, MSG_NOSIGNAL | MSG_DONTWAIT); + if(ret < 0) + { + if((errno == EAGAIN) || (errno == EINTR)) + return(0); + errnobak = errno; + dc_disconnect(); + errno = errnobak; + return(-1); + } + if(ret > 0) + memmove(queue->buf, queue->buf + ret, queue->buflen -= ret); + } + break; + } + return(0); +} + +#ifdef HAVE_RESOLVER +static char *readname(unsigned char *msg, unsigned char *eom, unsigned char **p) +{ + char *name, *tname; + unsigned char *tp; + size_t namesize, namedata, len; + + name = NULL; + namesize = namedata = 0; + while(1) + { + len = *((*p)++); + if(len == 0) + { + addtobuf(name, 0); + return(name); + } else if(len == 0xc0) { + tp = msg + *((*p)++); + if((tname = readname(msg, eom, &tp)) == NULL) + { + if(name != NULL) + free(name); + return(NULL); + } + bufcat(name, tname, strlen(tname)); + addtobuf(name, 0); + free(tname); + return(name); + } else if(*p + len >= eom) { + if(name != NULL) + free(name); + return(NULL); + } + bufcat(name, *p, len); + *p += len; + addtobuf(name, '.'); + } +} + +static int skipname(unsigned char *msg, unsigned char *eom, unsigned char **p) +{ + size_t len; + + while(1) + { + len = *((*p)++); + if(len == 0) + { + return(0); + } else if(len == 0xc0) { + (*p)++; + return(0); + } else if(*p + len >= eom) { + return(-1); + } + *p += len; + } +} + +static int getsrvrr(char *name, char **host, int *port) +{ + int i; + char *name2, *rrname; + unsigned char *eom, *p; + unsigned char buf[1024]; + int flags, num, class, type; + size_t len; + int ret; + + if(!(_res.options & RES_INIT)) + { + if(res_init() < 0) + return(-1); + } + /* res_querydomain doesn't work for some reason */ + name2 = smalloc(strlen("_dolcon._tcp.") + strlen(name) + 2); + strcpy(name2, "_dolcon._tcp."); + strcat(name2, name); + len = strlen(name2); + if(name2[len - 1] != '.') + { + name2[len] = '.'; + name2[len + 1] = 0; + } + ret = res_query(name2, C_IN, T_SRV, buf, sizeof(buf)); + if(ret < 0) + { + free(name2); + return(-1); + } + eom = buf + ret; + flags = (buf[2] << 8) + buf[3]; + if((flags & 0xfa0f) != 0x8000) + { + free(name2); + return(-1); + } + num = (buf[4] << 8) + buf[5]; + p = buf + 12; + for(i = 0; i < num; i++) + { + if(skipname(buf, eom, &p)) + { + free(name2); + return(-1); + } + p += 4; + } + num = (buf[6] << 8) + buf[7]; + for(i = 0; i < num; i++) + { + if((rrname = readname(buf, eom, &p)) == NULL) + { + free(name2); + return(-1); + } + type = *(p++) << 8; + type += *(p++); + class = *(p++) << 8; + class += *(p++); + p += 4; + len = *(p++) << 8; + len += *(p++); + if((class == C_IN) && (type == T_SRV) && !strcmp(rrname, name2)) + { + free(rrname); + free(name2); + /* Noone will want to have alternative DC servers, so + * don't care about priority and weigth */ + p += 4; + if(port == NULL) + { + p += 2; + } else { + *port = *(p++) << 8; + *port += *(p++); + } + if(host != NULL) + { + if((rrname = readname(buf, eom, &p)) == NULL) + return(-1); + *host = rrname; + } + return(0); + } + p += len; + free(rrname); + } + free(name2); + return(-1); +} +#else +static int getsrvrr(char *name, char **host, int *port) +{ + errno = EOPNOTSUP; + return(-1); +} +#endif + +int dc_connect(char *host, int port) +{ + struct addrinfo hint; + struct sockaddr_storage addr; + struct sockaddr_in *ipv4; +#ifdef HAVE_IPV6 + struct sockaddr_in6 *ipv6; +#endif + struct qcmd *qcmd; + char *newhost; + int getsrv, freehost; + int errnobak; + + if(fd >= 0) + dc_disconnect(); + state = -1; + freehost = 0; + if(port < 0) + { + port = 1500; + getsrv = 1; + } else { + getsrv = 0; + } + memset(&hint, 0, sizeof(hint)); + hint.ai_socktype = SOCK_STREAM; + if(getsrv) + { + if(!getsrvrr(host, &newhost, &port)) + { + host = newhost; + freehost = 1; + } + } + servport = port; + if(hostlist != NULL) + freeaddrinfo(hostlist); + if(getaddrinfo(host, NULL, &hint, &hostlist)) + { + errno = ENONET; + if(freehost) + free(host); + return(-1); + } + for(curhost = hostlist; curhost != NULL; curhost = curhost->ai_next) + { + if((fd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0) + { + errnobak = errno; + if(freehost) + free(host); + errno = errnobak; + return(-1); + } + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + memcpy(&addr, curhost->ai_addr, curhost->ai_addrlen); + if(addr.ss_family == AF_INET) + { + ipv4 = (struct sockaddr_in *)&addr; + ipv4->sin_port = htons(port); + } +#ifdef HAVE_IPV6 + if(addr.ss_family == AF_INET6) + { + ipv6 = (struct sockaddr_in6 *)&addr; + ipv6->sin6_port = htons(port); + } +#endif + if(connect(fd, (struct sockaddr *)&addr, curhost->ai_addrlen)) + { + if(errno == EINPROGRESS) + { + state = 0; + break; + } + close(fd); + fd = -1; + } else { + state = 1; + break; + } + } + qcmd = makeqcmd(NULL); + resetreader = 1; + if(dchostname != NULL) + free(dchostname); + dchostname = sstrdup(host); + if(freehost) + free(host); + return(fd); +} + +struct dc_intresp *dc_interpret(struct dc_response *resp) +{ + int i; + struct dc_intresp *iresp; + struct command *cmd; + struct respclass *cls; + int code; + int args; + + if((resp->numlines == 0) || (resp->rlines[0].argc == 0) || (resp->curline >= resp->numlines)) + return(NULL); + code = wcstol(resp->rlines[0].argv[0], NULL, 10); + cmd = (struct command *)(resp->internal); + for(cls = cmd->classes; cls != NULL; cls = cls->next) + { + if(cls->code == code) + break; + } + if(cls == NULL) + return(NULL); + if(cls->nwords >= resp->rlines[resp->curline].argc) + return(NULL); + iresp = smalloc(sizeof(*iresp)); + iresp->code = code; + iresp->argv = NULL; + iresp->argc = 0; + args = 0; + for(i = 0; i < cls->nwords; i++) + { + switch(cls->wordt[i]) + { + case RESP_DSC: + break; + case RESP_STR: + sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1); + iresp->argv[iresp->argc].val.str = swcsdup(resp->rlines[resp->curline].argv[i + 1]); + iresp->argv[iresp->argc].type = cls->wordt[i]; + iresp->argc++; + break; + case RESP_INT: + sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1); + iresp->argv[iresp->argc].val.num = wcstol(resp->rlines[resp->curline].argv[i + 1], NULL, 0); + iresp->argv[iresp->argc].type = cls->wordt[i]; + iresp->argc++; + break; + case RESP_FLOAT: + sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1); + iresp->argv[iresp->argc].val.flnum = wcstod(resp->rlines[resp->curline].argv[i + 1], NULL); + iresp->argv[iresp->argc].type = cls->wordt[i]; + iresp->argc++; + break; + } + } + resp->curline++; + return(iresp); +} + +void dc_freeires(struct dc_intresp *ires) +{ + int i; + + for(i = 0; i < ires->argc; i++) + { + if(ires->argv[i].type == RESP_STR) + free(ires->argv[i].val.str); + } + free(ires->argv); + free(ires); +} + +const char *dc_gethostname(void) +{ + return(dchostname); +} diff --git a/lib/uilib.h b/lib/uilib.h new file mode 100644 index 0000000..7c0efe4 --- /dev/null +++ b/lib/uilib.h @@ -0,0 +1,23 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 _UILIB_H +#define _UILIB_H + +#endif diff --git a/lib/uimisc.c b/lib/uimisc.c new file mode 100644 index 0000000..6a759be --- /dev/null +++ b/lib/uimisc.c @@ -0,0 +1,1055 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include + +#ifdef HAVE_KRB5 +#include +#include +#endif + +struct logindata; + +struct authmech +{ + wchar_t *name; + void (*process)(struct dc_response *resp, struct logindata *data); + int (*init)(struct logindata *data); + void (*release)(struct logindata *data); +}; + +struct logindata +{ + int (*conv)(int type, wchar_t *text, char **resp, void *data); + void (*callback)(int err, wchar_t *reason, void *data); + char *username; + int freeusername; + int useauthless; + void *data; + void *mechdata; + struct authmech *mech; +}; + +struct gencbdata +{ + void (*callback)(int resp, void *data); + void *data; +}; + +struct dc_fnetnode *dc_fnetnodes = NULL; +struct dc_transfer *dc_transfers = NULL; + +static void freelogindata(struct logindata *data) +{ + if((data->mech != NULL) && (data->mech->release != NULL)) + data->mech->release(data); + if(data->freeusername) + free(data->username); + free(data); +} + +static int logincallback(struct dc_response *resp); + +static void process_authless(struct dc_response *resp, struct logindata *data) +{ + switch(resp->code) + { + case 200: + data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data); + freelogindata(data); + break; +/* + case 303: + case 304: + if((ires = dc_interpret(resp)) != NULL) + { + buf = NULL; + if(data->conv((resp->code == 303)?DC_LOGIN_CONV_INFO:DC_LOGIN_CONV_ERROR, ires->argv[0].val.str, &buf, data->data)) + { + data->callback(DC_LOGIN_ERR_CONV, NULL, data->data); + freelogindata(data); + } else { + dc_queuecmd(logincallback, data, L"pass", L"", NULL); + } + if(buf != NULL) + { + memset(buf, 0, strlen(buf)); + free(buf); + } + dc_freeires(ires); + } + break; +*/ + case 505: + data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data); + freelogindata(data); + break; + case 506: + data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data); + freelogindata(data); + break; + default: + data->callback(DC_LOGIN_ERR_USER, NULL, data->data); + freelogindata(data); + break; + } +} + +static void process_pam(struct dc_response *resp, struct logindata *data) +{ + struct dc_intresp *ires; + int convtype; + char *buf; + + switch(resp->code) + { + case 200: + data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data); + freelogindata(data); + break; + case 301: + case 302: + case 303: + case 304: + if(resp->code == 301) + convtype = DC_LOGIN_CONV_NOECHO; + else if(resp->code == 302) + convtype = DC_LOGIN_CONV_ECHO; + else if(resp->code == 303) + convtype = DC_LOGIN_CONV_INFO; + else if(resp->code == 304) + convtype = DC_LOGIN_CONV_ERROR; + if((ires = dc_interpret(resp)) != NULL) + { + buf = NULL; + if(data->conv(convtype, ires->argv[0].val.str, &buf, data->data)) + { + data->callback(DC_LOGIN_ERR_CONV, NULL, data->data); + freelogindata(data); + } else { + dc_queuecmd(logincallback, data, L"pass", L"%%s", buf, NULL); + } + if(buf != NULL) + { + memset(buf, 0, strlen(buf)); + free(buf); + } + dc_freeires(ires); + } + break; + case 505: + data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data); + freelogindata(data); + break; + case 506: + data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data); + freelogindata(data); + break; + default: + data->callback(DC_LOGIN_ERR_USER, NULL, data->data); + freelogindata(data); + break; + } +} + +#ifdef HAVE_KRB5 +struct krb5data +{ + int state; + krb5_context context; + krb5_principal sprinc, myprinc; + krb5_ccache ccache; + krb5_auth_context authcon; + krb5_data reqbuf; + krb5_creds *servcreds; + int valid, fwd, fwded; +}; + +static char *hexencode(char *data, size_t datalen) +{ + char *buf, this; + size_t bufsize, bufdata; + int dig; + + buf = NULL; + bufsize = bufdata = 0; + for(; datalen > 0; datalen--, data++) + { + dig = (*data & 0xF0) >> 4; + if(dig > 9) + this = 'A' + dig - 10; + else + this = dig + '0'; + addtobuf(buf, this); + dig = *data & 0x0F; + if(dig > 9) + this = 'A' + dig - 10; + else + this = dig + '0'; + addtobuf(buf, this); + } + addtobuf(buf, 0); + return(buf); +} + +static char *hexdecode(char *data, size_t *len) +{ + char *buf, this; + size_t bufsize, bufdata; + + buf = NULL; + bufsize = bufdata = 0; + for(; *data; data++) + { + if((*data >= 'A') && (*data <= 'F')) + { + this = (this & 0x0F) | ((*data - 'A' + 10) << 4); + } else if((*data >= '0') && (*data <= '9')) { + this = (this & 0x0F) | ((*data - '0') << 4); + } else { + if(buf != NULL) + free(buf); + return(NULL); + } + data++; + if(!*data) + { + if(buf != NULL) + free(buf); + return(NULL); + } + if((*data >= 'A') && (*data <= 'F')) + { + this = (this & 0xF0) | (*data - 'A' + 10); + } else if((*data >= '0') && (*data <= '9')) { + this = (this & 0xF0) | (*data - '0'); + } else { + if(buf != NULL) + free(buf); + return(NULL); + } + addtobuf(buf, this); + } + addtobuf(buf, 0); + if(len != NULL) + *len = bufdata - 1; + return(buf); +} + +static void process_krb5(struct dc_response *resp, struct logindata *data) +{ + int ret; + struct dc_intresp *ires; + struct krb5data *krb; + krb5_data k5d; + krb5_ap_rep_enc_part *repl; + char *buf; + + krb = data->mechdata; + switch(resp->code) + { + case 200: + data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data); + freelogindata(data); + break; + case 300: + switch(krb->state) + { + case 0: + buf = hexencode(krb->reqbuf.data, krb->reqbuf.length); + dc_queuecmd(logincallback, data, L"pass", L"%%s", buf, NULL); + free(buf); + krb->state = 1; + break; + case 1: + if((ires = dc_interpret(resp)) != NULL) + { + k5d.data = hexdecode(icswcstombs(ires->argv[0].val.str, NULL, NULL), &k5d.length); + if(!krb->valid) + { + if((ret = krb5_rd_rep(krb->context, krb->authcon, &k5d, &repl)) != 0) + { + data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data); + freelogindata(data); + break; + } + /* XXX: Do I need to do something with this? */ + krb->valid = 1; + krb5_free_ap_rep_enc_part(krb->context, repl); + } + if(krb->fwd && !krb->fwded) + { + if(krb->reqbuf.data != NULL) + free(krb->reqbuf.data); + krb->reqbuf.data = NULL; + if((ret = krb5_fwd_tgt_creds(krb->context, krb->authcon, NULL, krb->servcreds->client, krb->servcreds->server, 0, 1, &krb->reqbuf)) != 0) + { + fprintf(stderr, "krb5_fwd_tgt_creds reported an error: %s\n", error_message(ret)); + dc_queuecmd(logincallback, data, L"pass", L"31", NULL); + krb->fwd = 0; + krb->state = 2; + } else { + dc_queuecmd(logincallback, data, L"pass", L"32", NULL); + krb->state = 0; + krb->fwded = 1; + } + } else { + dc_queuecmd(logincallback, data, L"pass", L"31", NULL); + krb->state = 2; + } + free(k5d.data); + dc_freeires(ires); + } + break; + default: + data->callback(DC_LOGIN_ERR_USER, NULL, data->data); + freelogindata(data); + break; + } + break; + case 505: + data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data); + freelogindata(data); + break; + case 506: + data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data); + freelogindata(data); + break; + default: + data->callback(DC_LOGIN_ERR_USER, NULL, data->data); + freelogindata(data); + break; + } +} + +static int init_krb5(struct logindata *data) +{ + int ret; + struct krb5data *krb; + krb5_data cksum; + krb5_creds creds; + + krb = smalloc(sizeof(*krb)); + memset(krb, 0, sizeof(*krb)); + krb->fwd = 1; + krb->fwded = 0; + data->mechdata = krb; + if((ret = krb5_init_context(&krb->context)) != 0) + { + fprintf(stderr, "krb5_init_context reported an error: %s\n", error_message(ret)); + return(1); + } + if((ret = krb5_auth_con_init(krb->context, &krb->authcon)) != 0) + { + fprintf(stderr, "krb5_auth_con_init reported an error: %s\n", error_message(ret)); + return(1); + } + krb5_auth_con_setflags(krb->context, krb->authcon, KRB5_AUTH_CONTEXT_DO_SEQUENCE); + if((ret = krb5_sname_to_principal(krb->context, dc_gethostname(), "doldacond", KRB5_NT_SRV_HST, &krb->sprinc)) != 0) + { + fprintf(stderr, "krb5_sname_to_principal reported an error: %s\n", error_message(ret)); + return(1); + } + if((ret = krb5_cc_default(krb->context, &krb->ccache)) != 0) + { + fprintf(stderr, "krb5_cc_default reported an error: %s\n", error_message(ret)); + return(1); + } + if((ret = krb5_cc_get_principal(krb->context, krb->ccache, &krb->myprinc)) != 0) + { + fprintf(stderr, "krb5_cc_default reported an error: %s\n", error_message(ret)); + return(1); + } + memset(&creds, 0, sizeof(creds)); + creds.client = krb->myprinc; + creds.server = krb->sprinc; + if((ret = krb5_get_credentials(krb->context, 0, krb->ccache, &creds, &krb->servcreds)) != 0) + { + fprintf(stderr, "krb5_get_credentials reported an error: %s\n", error_message(ret)); + return(1); + } + /* WTF is this checksum stuff?! The Krb docs don't say a word about it! */ + cksum.data = sstrdup(dc_gethostname()); + cksum.length = strlen(cksum.data); + if((ret = krb5_mk_req_extended(krb->context, &krb->authcon, AP_OPTS_MUTUAL_REQUIRED, &cksum, krb->servcreds, &krb->reqbuf)) != 0) + { + fprintf(stderr, "krb5_mk_req_extended reported an error: %s\n", error_message(ret)); + return(1); + } + free(cksum.data); + krb->state = 0; + return(0); +} + +static void release_krb5(struct logindata *data) +{ + struct krb5data *krb; + + if((krb = data->mechdata) == NULL) + return; + if(krb->servcreds != NULL) + krb5_free_creds(krb->context, krb->servcreds); + if(krb->reqbuf.data != NULL) + free(krb->reqbuf.data); + if(krb->sprinc != NULL) + krb5_free_principal(krb->context, krb->sprinc); + if(krb->myprinc != NULL) + krb5_free_principal(krb->context, krb->myprinc); + if(krb->ccache != NULL) + krb5_cc_close(krb->context, krb->ccache); + if(krb->authcon != NULL) + krb5_auth_con_free(krb->context, krb->authcon); + if(krb->context != NULL) + krb5_free_context(krb->context); + free(krb); +} +#endif + +/* Arranged in order of priority */ +static struct authmech authmechs[] = +{ +#ifdef HAVE_KRB5 + { + .name = L"krb5", + .process = process_krb5, + .init = init_krb5, + .release = release_krb5 + }, +#endif + { + .name = L"authless", + .process = process_authless, + .init = NULL, + .release = NULL + }, + { + .name = L"pam", + .process = process_pam, + .init = NULL, + .release = NULL + }, + { + .name = NULL + } +}; + +static int builtinconv(int type, wchar_t *text, char **resp, void *data) +{ + char *buf, *pass; + + if(isatty(0)) + { + if((buf = icwcstombs(text, NULL)) == NULL) + return(1); + pass = getpass(buf); + free(buf); + *resp = sstrdup(pass); + memset(pass, 0, strlen(pass)); + return(0); + } + return(1); +} + +static int logincallback(struct dc_response *resp) +{ + int i; + struct dc_intresp *ires; + struct logindata *data; + int mech; + char *username; + struct passwd *pwent; + void *odata, *ndata; + + data = resp->data; + if(!wcscmp(resp->cmdname, L"lsauth")) + { + if(resp->code == 201) + { + data->callback(DC_LOGIN_ERR_NOLOGIN, NULL, data->data); + freelogindata(data); + } else { + mech = -1; + while((ires = dc_interpret(resp)) != NULL) + { + if(!data->useauthless && !wcscmp(ires->argv[0].val.str, L"authless")) + { + dc_freeires(ires); + continue; + } + for(i = 0; authmechs[i].name != NULL; i++) + { + if(!wcscmp(authmechs[i].name, ires->argv[0].val.str) && ((i < mech) || (mech == -1))) + { + odata = data->mechdata; + data->mechdata = NULL; + if((authmechs[i].init != NULL) && authmechs[i].init(data)) + { + if(authmechs[i].release != NULL) + authmechs[i].release(data); + data->mechdata = odata; + fprintf(stderr, "authentication mechanism %ls failed, trying further...\n", authmechs[i].name); + } else { + if((data->mech != NULL) && data->mech->release != NULL) + { + ndata = data->mechdata; + data->mechdata = odata; + data->mech->release(data); + data->mechdata = ndata; + } + mech = i; + data->mech = authmechs + i; + } + break; + } + } + dc_freeires(ires); + } + if(mech == -1) + { + data->callback(DC_LOGIN_ERR_NOLOGIN, NULL, data->data); + freelogindata(data); + } else { + if((username = data->username) == NULL) + { + if((pwent = getpwuid(getuid())) == NULL) + { + data->callback(DC_LOGIN_ERR_USER, NULL, data->data); + freelogindata(data); + return(1); + } + username = pwent->pw_name; + } + dc_queuecmd(logincallback, data, L"login", data->mech->name, L"%%s", username, NULL); + } + } + } else if(!wcscmp(resp->cmdname, L"login") || !wcscmp(resp->cmdname, L"pass")) { + data->mech->process(resp, data); + } + return(1); +} + +void dc_loginasync(char *username, int useauthless, int (*conv)(int, wchar_t *, char **, void *), void (*callback)(int, wchar_t *, void *), void *udata) +{ + struct logindata *data; + + data = smalloc(sizeof(*data)); + if(conv == NULL) + conv = builtinconv; + data->conv = conv; + data->mech = NULL; + data->data = udata; + data->mechdata = NULL; + data->callback = callback; + data->useauthless = useauthless; + data->freeusername = 0; + if(username == NULL) + { + data->username = NULL; + } else { + data->username = sstrdup(username); + data->freeusername = 1; + } + dc_queuecmd(logincallback, data, L"lsauth", NULL); +} + +static struct dc_fnetnode *newfn(void) +{ + struct dc_fnetnode *fn; + + fn = smalloc(sizeof(*fn)); + memset(fn, 0, sizeof(*fn)); + fn->id = -1; + fn->name = NULL; + fn->fnet = NULL; + fn->state = fn->numusers = fn->found = 0; + fn->destroycb = NULL; + fn->udata = NULL; + fn->next = dc_fnetnodes; + fn->prev = NULL; + if(dc_fnetnodes != NULL) + dc_fnetnodes->prev = fn; + dc_fnetnodes = fn; + return(fn); +} + +static void freefn(struct dc_fnetnode *fn) +{ + if(fn->next != NULL) + fn->next->prev = fn->prev; + if(fn->prev != NULL) + fn->prev->next = fn->next; + if(fn == dc_fnetnodes) + dc_fnetnodes = fn->next; + if(fn->destroycb != NULL) + fn->destroycb(fn); + if(fn->name != NULL) + free(fn->name); + if(fn->fnet != NULL) + free(fn->fnet); + free(fn); +} + +struct dc_fnetnode *dc_findfnetnode(int id) +{ + struct dc_fnetnode *fn; + + for(fn = dc_fnetnodes; fn != NULL; fn = fn->next) + { + if(fn->id == id) + break; + } + return(fn); +} + +static struct dc_transfer *newtransfer(void) +{ + struct dc_transfer *transfer; + + transfer = smalloc(sizeof(*transfer)); + memset(transfer, 0, sizeof(*transfer)); + transfer->id = -1; + transfer->peerid = transfer->peernick = transfer->path = NULL; + transfer->state = DC_TRNS_WAITING; + transfer->dir = DC_TRNSD_UNKNOWN; + transfer->size = -1; + transfer->curpos = -1; + transfer->destroycb = NULL; + transfer->udata = NULL; + transfer->next = dc_transfers; + transfer->prev = NULL; + if(dc_transfers != NULL) + dc_transfers->prev = transfer; + dc_transfers = transfer; + return(transfer); +} + +static void freetransfer(struct dc_transfer *transfer) +{ + if(transfer->next != NULL) + transfer->next->prev = transfer->prev; + if(transfer->prev != NULL) + transfer->prev->next = transfer->next; + if(transfer == dc_transfers) + dc_transfers = transfer->next; + if(transfer->destroycb != NULL) + transfer->destroycb(transfer); + if(transfer->peerid != NULL) + free(transfer->peerid); + if(transfer->peernick != NULL) + free(transfer->peernick); + if(transfer->path != NULL) + free(transfer->path); + free(transfer); +} + +struct dc_transfer *dc_findtransfer(int id) +{ + struct dc_transfer *transfer; + + for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next) + { + if(transfer->id == id) + break; + } + return(transfer); +} + +static int getfnlistcallback(struct dc_response *resp) +{ + struct dc_intresp *ires; + struct gencbdata *data; + struct dc_fnetnode *fn, *next; + + data = resp->data; + if(resp->code == 200) + { + for(fn = dc_fnetnodes; fn != NULL; fn = fn->next) + fn->found = 0; + while((ires = dc_interpret(resp)) != NULL) + { + if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL) + { + fn->found = 1; + if(fn->fnet != NULL) + free(fn->fnet); + fn->fnet = swcsdup(ires->argv[1].val.str); + if(fn->name != NULL) + free(fn->name); + fn->name = swcsdup(ires->argv[2].val.str); + fn->numusers = ires->argv[3].val.num; + fn->state = ires->argv[4].val.num; + } else { + fn = newfn(); + fn->id = ires->argv[0].val.num; + fn->fnet = swcsdup(ires->argv[1].val.str); + fn->name = swcsdup(ires->argv[2].val.str); + fn->numusers = ires->argv[3].val.num; + fn->state = ires->argv[4].val.num; + fn->found = 1; + } + dc_freeires(ires); + } + for(fn = dc_fnetnodes; fn != NULL; fn = next) + { + next = fn->next; + if(!fn->found) + freefn(fn); + } + data->callback(200, data->data); + free(resp->data); + } else if(resp->code == 201) { + while(dc_fnetnodes != NULL) + freefn(dc_fnetnodes); + data->callback(201, data->data); + free(resp->data); + } else if(resp->code == 502) { + while(dc_fnetnodes != NULL) + freefn(dc_fnetnodes); + data->callback(502, data->data); + free(resp->data); + } + return(1); +} + +static int gettrlistcallback(struct dc_response *resp) +{ + struct dc_intresp *ires; + struct gencbdata *data; + struct dc_transfer *transfer, *next; + + data = resp->data; + if(resp->code == 200) + { + for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next) + transfer->found = 0; + while((ires = dc_interpret(resp)) != NULL) + { + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + { + transfer->found = 1; + if((transfer->path == NULL) || wcscmp(transfer->path, ires->argv[5].val.str)) + { + if(transfer->path != NULL) + free(transfer->path); + transfer->path = swcsdup(ires->argv[5].val.str); + } + if((transfer->peerid == NULL) || wcscmp(transfer->peerid, ires->argv[3].val.str)) + { + if(transfer->peerid != NULL) + free(transfer->peerid); + transfer->peerid = swcsdup(ires->argv[3].val.str); + } + if((transfer->peernick == NULL) || wcscmp(transfer->peernick, ires->argv[4].val.str)) + { + if(transfer->peernick != NULL) + free(transfer->peernick); + transfer->peernick = swcsdup(ires->argv[4].val.str); + } + transfer->dir = ires->argv[1].val.num; + transfer->state = ires->argv[2].val.num; + transfer->size = ires->argv[6].val.num; + transfer->curpos = ires->argv[7].val.num; + } else { + transfer = newtransfer(); + transfer->id = ires->argv[0].val.num; + transfer->dir = ires->argv[1].val.num; + transfer->state = ires->argv[2].val.num; + transfer->peerid = swcsdup(ires->argv[3].val.str); + transfer->peernick = swcsdup(ires->argv[4].val.str); + transfer->path = swcsdup(ires->argv[5].val.str); + transfer->size = ires->argv[6].val.num; + transfer->curpos = ires->argv[7].val.num; + transfer->found = 1; + } + dc_freeires(ires); + } + for(transfer = dc_transfers; transfer != NULL; transfer = next) + { + next = transfer->next; + if(!transfer->found) + freetransfer(transfer); + } + data->callback(200, data->data); + free(data); + } else if(resp->code == 201) { + while(dc_transfers != NULL) + freetransfer(dc_transfers); + data->callback(201, data->data); + free(data); + } else if(resp->code == 502) { + while(dc_transfers != NULL) + freetransfer(dc_transfers); + data->callback(502, data->data); + free(data); + } + return(1); +} + +void dc_getfnlistasync(void (*callback)(int, void *), void *udata) +{ + struct gencbdata *data; + + data = smalloc(sizeof(*data)); + data->callback = callback; + data->data = udata; + dc_queuecmd(getfnlistcallback, data, L"lsnodes", NULL); +} + +void dc_gettrlistasync(void (*callback)(int, void *), void *udata) +{ + struct gencbdata *data; + + data = smalloc(sizeof(*data)); + data->callback = callback; + data->data = udata; + dc_queuecmd(gettrlistcallback, data, L"lstrans", NULL); +} + +void dc_uimisc_disconnected(void) +{ + while(dc_fnetnodes != NULL) + freefn(dc_fnetnodes); + while(dc_transfers != NULL) + freetransfer(dc_transfers); +} + +void dc_uimisc_handlenotify(struct dc_response *resp) +{ + struct dc_fnetnode *fn; + struct dc_transfer *transfer; + struct dc_intresp *ires; + + if((ires = dc_interpret(resp)) == NULL) + return; + switch(resp->code) + { + case 601: + if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL) + fn->state = ires->argv[1].val.num; + break; + case 602: + if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL) + { + if(fn->name != NULL) + free(fn->name); + fn->name = swcsdup(ires->argv[1].val.str); + } + break; + case 603: + if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL) + freefn(fn); + break; + case 604: + fn = newfn(); + fn->id = ires->argv[0].val.num; + if(fn->fnet != NULL) + free(fn->fnet); + fn->fnet = swcsdup(ires->argv[1].val.str); + fn->state = DC_FNN_STATE_SYN; + fn->numusers = 0; + break; + case 605: + if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL) + fn->numusers = ires->argv[1].val.num; + break; + case 610: + transfer = newtransfer(); + transfer->id = ires->argv[0].val.num; + transfer->dir = ires->argv[1].val.num; + if(transfer->dir == DC_TRNSD_UP) + transfer->state = DC_TRNS_HS; + transfer->peerid = swcsdup(ires->argv[2].val.str); + if(ires->argv[3].val.str[0]) + transfer->path = swcsdup(ires->argv[3].val.str); + break; + case 611: + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + transfer->state = ires->argv[1].val.num; + break; + case 612: + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + { + if(transfer->peernick != NULL) + free(transfer->peernick); + transfer->peernick = swcsdup(ires->argv[1].val.str); + } + break; + case 613: + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + transfer->size = ires->argv[1].val.num; + break; + case 614: + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + { + transfer->error = ires->argv[1].val.num; + time(&transfer->errortime); + } + break; + case 615: + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + transfer->curpos = ires->argv[1].val.num; + break; + case 616: + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + { + if(transfer->path != NULL) + free(transfer->path); + transfer->path = swcsdup(ires->argv[1].val.str); + } + break; + case 617: + if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL) + freetransfer(transfer); + break; + default: + break; + } + dc_freeires(ires); + resp->curline = 0; +} + +/* Note the backspace handling - it's not as elegant as possible, but + * it helps avoid the "box-of-toothpicks" syndrome when writing search + * expressions manually. */ +wchar_t **dc_lexsexpr(wchar_t *sexpr) +{ + wchar_t **ret; + wchar_t *buf; + size_t retsize, retdata, bufsize, bufdata; + int state; + + ret = NULL; + buf = NULL; + retsize = retdata = bufsize = bufdata = 0; + state = 0; + while(*sexpr != L'\0') + { + switch(state) + { + case 0: + if(!iswspace(*sexpr)) + state = 1; + else + sexpr++; + break; + case 1: + if(iswspace(*sexpr)) + { + if(buf != NULL) + { + addtobuf(buf, L'\0'); + addtobuf(ret, buf); + buf = NULL; + bufsize = bufdata = 0; + } + state = 0; + } else if((*sexpr == L'(') || + (*sexpr == L')') || + (*sexpr == L'&') || + (*sexpr == L'|') || + (*sexpr == L'!')) { + if(buf != NULL) + { + addtobuf(buf, L'\0'); + addtobuf(ret, buf); + buf = NULL; + bufsize = bufdata = 0; + } + addtobuf(buf, *sexpr); + addtobuf(buf, L'\0'); + addtobuf(ret, buf); + buf = NULL; + bufsize = bufdata = 0; + sexpr++; + } else if(*sexpr == L'\"') { + sexpr++; + state = 2; + } else if(*sexpr == L'\\') { + sexpr++; + if(*sexpr == L'\0') + { + addtobuf(buf, *sexpr); + } else if((*sexpr == L'\\') || (*sexpr == L'\"')) { + addtobuf(buf, *sexpr); + sexpr++; + } else { + addtobuf(buf, L'\\'); + addtobuf(buf, *sexpr); + sexpr++; + } + } else { + addtobuf(buf, *(sexpr++)); + } + break; + case 2: + if(*sexpr == L'\\') + { + sexpr++; + if(*sexpr == L'\0') + { + addtobuf(buf, *sexpr); + } else if((*sexpr == L'\\') || (*sexpr == L'\"')) { + addtobuf(buf, *sexpr); + sexpr++; + } else { + addtobuf(buf, L'\\'); + addtobuf(buf, *sexpr); + sexpr++; + } + } else if(*sexpr == L'\"') { + state = 1; + sexpr++; + } else { + addtobuf(buf, *(sexpr++)); + } + break; + } + } + if(buf != NULL) + { + addtobuf(buf, L'\0'); + addtobuf(ret, buf); + } + addtobuf(ret, NULL); + return(ret); +} + +void dc_freewcsarr(wchar_t **arr) +{ + wchar_t **buf; + + if(arr == NULL) + return; + for(buf = arr; *buf != NULL; buf++) + free(*buf); + free(arr); +} diff --git a/lib/utils.c b/lib/utils.c new file mode 100644 index 0000000..f5cd7d0 --- /dev/null +++ b/lib/utils.c @@ -0,0 +1,239 @@ +/* + * Dolda Connect - Modular multiuser Direct Connect-style client + * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include + +extern int vswprintf (wchar_t *__restrict __s, size_t __n, + __const wchar_t *__restrict __format, + __gnuc_va_list __arg); + +char *vsprintf2(char *format, va_list al) +{ + int ret; + char *buf; + + ret = vsnprintf(NULL, 0, format, al); + if((buf = malloc(ret + 1)) == NULL) + { + return(NULL); + } + vsnprintf(buf, ret + 1, format, al); + return(buf); +} + +char *sprintf2(char *format, ...) +{ + va_list args; + char *buf; + + va_start(args, format); + buf = vsprintf2(format, args); + va_end(args); + return(buf); +} + +wchar_t *vswprintf2(wchar_t *format, va_list al) +{ + int ret; + wchar_t *buf; + size_t bufsize; + + buf = smalloc(sizeof(wchar_t) * (bufsize = 1024)); + while((ret = vswprintf(buf, bufsize, format, al)) < 0) + buf = srealloc(buf, sizeof(wchar_t) * (bufsize *= 2)); + if(bufsize > ret + 1) + buf = srealloc(buf, sizeof(wchar_t) * (ret + 1)); + return(buf); +} + +wchar_t *swprintf2(wchar_t *format, ...) +{ + va_list args; + wchar_t *buf; + + va_start(args, format); + buf = vswprintf2(format, args); + va_end(args); + return(buf); +} + +wchar_t *icmbstowcs(char *mbs, char *charset) +{ + int ret; + char *buf; + char *p, *p2; + size_t len1, len2, bufsize, data; + iconv_t cd; + + len1 = strlen(mbs) + 1; + bufsize = len2 = len1 * sizeof(wchar_t); + if((buf = malloc(bufsize)) == NULL) + { + return(NULL); + } + if(charset == NULL) + charset = nl_langinfo(CODESET); + if((cd = iconv_open("wchar_t", charset)) == (iconv_t)-1) + { + free(buf); + return(NULL); + } + p = buf; + while(len1 > 0) + { + ret = iconv(cd, &mbs, &len1, &p, &len2); + if(ret < 0) + { + if(errno == E2BIG) + { + data = p - buf; + len2 += 128; + bufsize += 128; + if((p2 = realloc(buf, bufsize)) == NULL) + { + free(buf); + return(NULL); + } + buf = p2; + p = buf + data; + } else { + free(buf); + return(NULL); + } + } + } + if(len2 > 0) + buf = realloc(buf, p - buf); + iconv_close(cd); + return((wchar_t *)buf); +} + +wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def) +{ + static wchar_t *buf = NULL; + + if(buf != NULL) + free(buf); + if((buf = icmbstowcs(mbs, charset)) == NULL) + return(def); + return(buf); +} + +char *icwcstombs(wchar_t *wcs, char *charset) +{ + int ret; + char *buf; + char *p, *p2; + size_t len1, len2, bufsize, data; + iconv_t cd; + + len1 = sizeof(wchar_t) * (wcslen(wcs) + 1); + bufsize = len2 = len1; + if((buf = malloc(bufsize)) == NULL) + { + return(NULL); + } + if(charset == NULL) + charset = nl_langinfo(CODESET); + if((cd = iconv_open(charset, "wchar_t")) == (iconv_t)-1) + { + free(buf); + return(NULL); + } + p = buf; + while(len1 > 0) + { + ret = iconv(cd, (char **)&wcs, &len1, &p, &len2); + if(ret < 0) + { + if(errno == E2BIG) + { + data = p - buf; + len2 += 128; + bufsize += 128; + if((p2 = realloc(buf, bufsize)) == NULL) + { + free(buf); + return(NULL); + } + buf = p2; + p = buf + data; + } else { + free(buf); + return(NULL); + } + } + } + if(len2 > 0) + buf = realloc(buf, p - buf); + iconv_close(cd); + return(buf); +} + +char *icswcstombs(wchar_t *wcs, char *charset, char *def) +{ + static char *buf = NULL; + + if(buf != NULL) + free(buf); + if((buf = icwcstombs(wcs, charset)) == NULL) + return(def); + return(buf); +} + +wchar_t *wcstolower(wchar_t *wcs) +{ + wchar_t *p; + + for(p = wcs; *p != L'\0'; p++) + *p = towlower(*p); + return(wcs); +} + +void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo) +{ + if(*bufsize >= reqsize) + return; + switch(algo) + { + case 0: + *buf = srealloc(*buf, elsize * ((*bufsize) = reqsize)); + break; + case 1: + if(*bufsize == 0) + *bufsize = 1; + while(*bufsize < reqsize) + *bufsize *= 2; + *buf = srealloc(*buf, elsize * (*bufsize)); + break; + } +} diff --git a/po/CVS/Entries b/po/CVS/Entries new file mode 100644 index 0000000..0d64cf3 --- /dev/null +++ b/po/CVS/Entries @@ -0,0 +1,6 @@ +/ChangeLog/1.1/Fri Aug 13 18:08:40 2004// +/LINGUAS/1.2/Fri Aug 13 23:00:24 2004// +/Makevars/1.1/Fri Aug 13 18:08:41 2004// +/POTFILES.in/1.2/Sat Jan 1 17:37:01 2005// +/sv.po/1.12/Fri Oct 14 23:22:19 2005// +D diff --git a/po/CVS/Repository b/po/CVS/Repository new file mode 100644 index 0000000..4a5c94d --- /dev/null +++ b/po/CVS/Repository @@ -0,0 +1 @@ +doldaconnect/po diff --git a/po/CVS/Root b/po/CVS/Root new file mode 100644 index 0000000..2886064 --- /dev/null +++ b/po/CVS/Root @@ -0,0 +1 @@ +:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect diff --git a/po/ChangeLog b/po/ChangeLog new file mode 100644 index 0000000..4e6a12b --- /dev/null +++ b/po/ChangeLog @@ -0,0 +1,11 @@ +2004-08-13 gettextize + + * Makefile.in.in: New file, from gettext-0.12.1. + * boldquot.sed: New file, from gettext-0.12.1. + * en@boldquot.header: New file, from gettext-0.12.1. + * en@quot.header: New file, from gettext-0.12.1. + * insert-header.sin: New file, from gettext-0.12.1. + * quot.sed: New file, from gettext-0.12.1. + * remove-potcdate.sin: New file, from gettext-0.12.1. + * Rules-quot: New file, from gettext-0.12.1. + diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..b917a40 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1,2 @@ +sv + diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..eb62ce1 --- /dev/null +++ b/po/Makevars @@ -0,0 +1,41 @@ +# Makefile variables for PO directory in any package using GNU gettext. + +# Usually the message domain is the same as the package name. +DOMAIN = $(PACKAGE) + +# These two variables depend on the location of this directory. +subdir = po +top_builddir = .. + +# These options get passed to xgettext. +XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ + +# This is the copyright holder that gets inserted into the header of the +# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding +# package. (Note that the msgstr strings, extracted from the package's +# sources, belong to the copyright holder of the package.) Translators are +# expected to transfer the copyright for their translations to this person +# or entity, or to disclaim their copyright. The empty string stands for +# the public domain; in this case the translators are expected to disclaim +# their copyright. +COPYRIGHT_HOLDER = Fredrik Tolf + +# This is the email address or URL to which the translators shall report +# bugs in the untranslated strings: +# - Strings which are not entire sentences, see the maintainer guidelines +# in the GNU gettext documentation, section 'Preparing Strings'. +# - Strings which use unclear terms or require additional context to be +# understood. +# - Strings which make invalid assumptions about notation of date, time or +# money. +# - Pluralisation problems. +# - Incorrect English spelling. +# - Incorrect formatting. +# It can be your email address, or a mailing list address where translators +# can write to without being subscribed, or the URL of a web page through +# which the translators can contact you. +MSGID_BUGS_ADDRESS = fredrik@dolda2000.com + +# This is the list of locale categories, beyond LC_MESSAGES, for which the +# message catalogs shall be used. It is usually empty. +EXTRA_LOCALE_CATEGORIES = diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..1b57489 --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,7 @@ +clients/gtk2/main.c +clients/gtk2/inpdialog.gtk +clients/gtk2/mainwnd.gtk +clients/gtk2/pref.gtk +clients/gnome-trans-applet/dolcon-trans-applet.c +clients/gnome-trans-applet/conduit.c +clients/gnome-trans-applet/conduit-pipe.c diff --git a/po/sv.po b/po/sv.po new file mode 100644 index 0000000..f9d6287 --- /dev/null +++ b/po/sv.po @@ -0,0 +1,439 @@ +# Swedish translations for doldaconnect package +# Svenska översättningar för paket doldaconnect. +# Copyright (C) 2004 Fredrik Tolf +# This file is distributed under the same license as the doldaconnect package. +# Fredrik Tolf , 2004. +# +msgid "" +msgstr "" +"Project-Id-Version: doldaconnect 0.1.1\n" +"Report-Msgid-Bugs-To: fredrik@dolda2000.com\n" +"POT-Creation-Date: 2005-07-09 05:31+0200\n" +"PO-Revision-Date: 2005-08-15 03:48+0200\n" +"Last-Translator: Fredrik Tolf \n" +"Language-Team: Swedish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: clients/gtk2/main.c:215 clients/gtk2/main.c:238 clients/gtk2/main.c:270 +#: clients/gtk2/main.c:304 clients/gtk2/main.c:400 clients/gtk2/main.c:419 +msgid "Unknown" +msgstr "Okänt" + +#: clients/gtk2/main.c:319 +msgid "Not found" +msgstr "Kunde inte hittas" + +#: clients/gtk2/main.c:321 +msgid "No slots" +msgstr "Inga slots lediga" + +#: clients/gtk2/main.c:322 +msgid "%H:%M:%S" +msgstr "%H:%M:%S" + +#: clients/gtk2/main.c:323 +#, c-format +msgid "%s (reported at %s)" +msgstr "%s (rapporterades kl. %s)" + +#: clients/gtk2/main.c:484 clients/gtk2/main.c:1807 +msgid "Disconnected" +msgstr "Frånkopplad" + +#: clients/gtk2/main.c:607 +msgid "Could not get your home directory!" +msgstr "Kunde inte hitta din hemkatalog!" + +#: clients/gtk2/main.c:614 +#, c-format +msgid "Could not open configuration file for writing: %s" +msgstr "Kunde inte skriva till konfigurationsfilen: %s" + +#: clients/gtk2/main.c:655 clients/gtk2/main.c:659 +msgid "Login" +msgstr "Logga in" + +#: clients/gtk2/main.c:695 +msgid "Could not negotiate an acceptable authentication mechanism" +msgstr "Kunde inte förhandla fram en acceptabel autentiseringsmekanism" + +#: clients/gtk2/main.c:700 +msgid "The server has encountered an error" +msgstr "Servern har råkat ut för ett fel" + +#: clients/gtk2/main.c:705 +msgid "Internal client error" +msgstr "Internt fel i klienten" + +#: clients/gtk2/main.c:714 +msgid "Login attempt failed!" +msgstr "Inloggningsförsöket misslyckades!" + +#: clients/gtk2/main.c:760 +msgid "Discrete sizes" +msgstr "Enskilda storlekar" + +#: clients/gtk2/main.c:881 +msgid "Connected" +msgstr "Ansluten" + +#: clients/gtk2/main.c:884 +msgid "The server refused the connection" +msgstr "Servern vägrade förbindelsen" + +#: clients/gtk2/main.c:1083 +msgid "The server has closed the connection" +msgstr "Servern har stängt förbindelsen" + +#: clients/gtk2/main.c:1085 +#, c-format +msgid "" +"The connection to the server failed:\n" +"\n" +"%s" +msgstr "" +"Anslutning till servern misslyckades:\n" +"\n" +"%s" + +#: clients/gtk2/main.c:1105 +msgid "Preferences" +msgstr "Inställningar" + +#: clients/gtk2/main.c:1131 +#, c-format +msgid "" +"Could not connect:\n" +"\n" +"%s" +msgstr "" +"Kunde inte ansluta:\n" +"\n" +"%s" + +#: clients/gtk2/main.c:1138 +#: clients/gnome-trans-applet/dolcon-trans-applet.c:84 +msgid "Connecting..." +msgstr "Ansluter..." + +#: clients/gtk2/main.c:1147 +msgid "Connect" +msgstr "Anslut" + +#: clients/gtk2/main.c:1147 +msgid "Server address:" +msgstr "Serverns adress:" + +#: clients/gtk2/main.c:1160 clients/gtk2/main.c:1181 clients/gtk2/main.c:1360 +#: clients/gtk2/main.c:1402 clients/gtk2/main.c:1464 clients/gtk2/main.c:1552 +#: clients/gtk2/main.c:1647 +msgid "Not connected to DC server" +msgstr "Ej ansluten till DC-servern" + +#: clients/gtk2/main.c:1167 clients/gtk2/main.c:1195 clients/gtk2/main.c:1373 +#: clients/gtk2/main.c:1420 clients/gtk2/main.c:1477 clients/gtk2/main.c:1570 +#: clients/gtk2/main.c:1625 clients/gtk2/main.c:1687 +msgid "You do not have permission to do that" +msgstr "Du har inte tillstånd att göra det" + +#: clients/gtk2/main.c:1197 clients/gtk2/main.c:1422 +msgid "The server could not parse that address" +msgstr "Servern kunde inte tolka den adressen" + +#: clients/gtk2/main.c:1225 +#, c-format +msgid "Could not read from public hub listing process: %s" +msgstr "Kunde inte läsa från processen som hämtar den publika hublistan: %s" + +#: clients/gtk2/main.c:1262 +#, c-format +msgid "Could not decode hublist - aborting at this point: %s" +msgstr "Kunde inte dekoda hublistan - stannar här: %s" + +#: clients/gtk2/main.c:1365 clients/gtk2/main.c:1469 +msgid "No hub selected" +msgstr "Ingen hub vald" + +#: clients/gtk2/main.c:1479 +msgid "" +"This hub could not support all the types of characters in your chat message" +msgstr "Den här hubben klarar inte av alla sorters bokstäver i ditt meddelande" + +#: clients/gtk2/main.c:1481 +msgid "This hub does not support chatting" +msgstr "Den här hubben klarar inte av att chatta" + +#: clients/gtk2/main.c:1483 +#, c-format +msgid "An error occurred while trying to chat (%i)" +msgstr "Ett fel uppstod under försöket att chatta (%i)" + +#: clients/gtk2/main.c:1560 +msgid "Please enter a search expression before searching" +msgstr "Skriv in ett sökuttryck först" + +#: clients/gtk2/main.c:1568 +msgid "Could not find any hubs to search on" +msgstr "Kunde inte hitta någon hub att söka på" + +#: clients/gtk2/main.c:1572 +msgid "The server could not parse your search expression" +msgstr "Servern kunde inte tolka ditt sökuttryck" + +#: clients/gtk2/main.c:1574 +#, c-format +msgid "An error occurred while trying to search (%i)" +msgstr "Ett fel uppstod under försöket att söka (%i)" + +#: clients/gtk2/main.c:1627 +#, c-format +msgid "An error occurred while trying to cancel (%i)" +msgstr "Ett fel uppstod under försöket att avbryta (%i)" + +#: clients/gtk2/main.c:1689 +#, c-format +msgid "An error occurred while trying to queue the download (%i)" +msgstr "Ett fel uppstod under försöket att lägga till nerladdningen (%i)" + +#: clients/gtk2/main.c:1720 clients/gtk2/mainwnd.gtk:398 +#, c-format +msgid "Ready to search" +msgstr "Redo att söka" + +#: clients/gtk2/main.c:1722 +#, c-format +msgid "Search scheduled and will be submitted in %i seconds" +msgstr "Sökningen är schemalagd och kommer genomföras om %i sekunder" + +#: clients/gtk2/inpdialog.gtk:16 +msgid " " +msgstr " " + +#: clients/gtk2/mainwnd.gtk:45 +msgid "_Main" +msgstr "Huvud_meny" + +#: clients/gtk2/mainwnd.gtk:50 +msgid "_Connect" +msgstr "_Anslut" + +#: clients/gtk2/mainwnd.gtk:57 +msgid "_Disconnect" +msgstr "_Koppla från" + +#: clients/gtk2/mainwnd.gtk:69 +msgid "_Shut down daemon" +msgstr "_Stäng av demonen" + +#: clients/gtk2/mainwnd.gtk:84 +msgid "Op_tions" +msgstr "Alternati_v" + +#: clients/gtk2/mainwnd.gtk:112 +msgid "Connected hu_bs" +msgstr "_Anslutna hubbar" + +#: clients/gtk2/mainwnd.gtk:124 clients/gtk2/mainwnd.gtk:260 +msgid "Hub name" +msgstr "Hubnamn" + +#: clients/gtk2/mainwnd.gtk:138 clients/gtk2/mainwnd.gtk:175 +msgid "# users" +msgstr "Antal användare" + +#: clients/gtk2/mainwnd.gtk:149 +msgid "D_isconnect" +msgstr "_Koppla från" + +#: clients/gtk2/mainwnd.gtk:160 +msgid "_Public hub list" +msgstr "_Publik hublista" + +#: clients/gtk2/mainwnd.gtk:180 +msgid "Name" +msgstr "Namn" + +#: clients/gtk2/mainwnd.gtk:185 +msgid "Description" +msgstr "Beskrivning" + +#: clients/gtk2/mainwnd.gtk:198 +msgid "_Filter:" +msgstr "_Filter:" + +#: clients/gtk2/mainwnd.gtk:209 +msgid "_Get public hub list" +msgstr "Hämta _publik hublista" + +#: clients/gtk2/mainwnd.gtk:214 +msgid "_Address:" +msgstr "_Adress:" + +#: clients/gtk2/mainwnd.gtk:225 +msgid "C_onnect" +msgstr "_Anslut" + +#: clients/gtk2/mainwnd.gtk:239 +msgid "_Hub connections" +msgstr "_Hubanslutningar" + +#: clients/gtk2/mainwnd.gtk:247 +msgid "Hu_bs" +msgstr "Hu_bbar" + +#: clients/gtk2/mainwnd.gtk:298 +msgid "Chat st_ring:" +msgstr "Chatm_eddelande:" + +#: clients/gtk2/mainwnd.gtk:309 +msgid "S_end" +msgstr "S_kicka" + +#: clients/gtk2/mainwnd.gtk:321 +msgid "_Chat" +msgstr "_Chat" + +#: clients/gtk2/mainwnd.gtk:328 +msgid "S_imple search:" +msgstr "_Enkel sökning:" + +#: clients/gtk2/mainwnd.gtk:340 +msgid "S_earch" +msgstr "S_ök" + +#: clients/gtk2/mainwnd.gtk:346 +msgid "C_ancel" +msgstr "_Avbryt" + +#: clients/gtk2/mainwnd.gtk:357 +msgid "Displa_y results with free slots only" +msgstr "_Visa endast resultat med fria slots" + +#: clients/gtk2/mainwnd.gtk:363 +msgid "Ad_vanced" +msgstr "A_vancerat" + +#: clients/gtk2/mainwnd.gtk:368 +msgid "C_omplete search expression:" +msgstr "_Komplett sökuttryck:" + +#: clients/gtk2/mainwnd.gtk:380 +msgid "Filter ar_gument:" +msgstr "Filterar_gument:" + +#: clients/gtk2/mainwnd.gtk:407 +msgid "Search _results:" +msgstr "Sök_resultat:" + +#: clients/gtk2/mainwnd.gtk:422 +msgid "#" +msgstr "#" + +#: clients/gtk2/mainwnd.gtk:432 +msgid "Peer name" +msgstr "Användare" + +#: clients/gtk2/mainwnd.gtk:437 +msgid "File name" +msgstr "Filnamn" + +#: clients/gtk2/mainwnd.gtk:444 clients/gtk2/mainwnd.gtk:520 +#: clients/gtk2/mainwnd.gtk:590 +msgid "Size" +msgstr "Storlek" + +#: clients/gtk2/mainwnd.gtk:454 +msgid "Slots" +msgstr "Slots" + +#: clients/gtk2/mainwnd.gtk:460 +msgid "Known speed" +msgstr "Känd hastighet" + +#: clients/gtk2/mainwnd.gtk:470 +msgid "Rsp. time" +msgstr "Svarstid" + +#: clients/gtk2/mainwnd.gtk:482 +msgid "_Search" +msgstr "_Sök" + +#: clients/gtk2/mainwnd.gtk:492 +msgid "_List of downloads:" +msgstr "_Lista över nerladdningar:" + +#: clients/gtk2/mainwnd.gtk:504 clients/gtk2/mainwnd.gtk:574 +msgid "User Name" +msgstr "Användarnamn" + +#: clients/gtk2/mainwnd.gtk:516 clients/gtk2/mainwnd.gtk:586 +msgid "File Name" +msgstr "Filenamn" + +#: clients/gtk2/mainwnd.gtk:529 clients/gtk2/mainwnd.gtk:599 +msgid "Position" +msgstr "Position" + +#: clients/gtk2/mainwnd.gtk:542 +msgid "Error" +msgstr "Felmeddelande" + +#: clients/gtk2/mainwnd.gtk:557 +msgid "_Downloads" +msgstr "_Nerladdningar" + +#: clients/gtk2/mainwnd.gtk:562 +msgid "_List of uploads:" +msgstr "_Lista över uppladdningar:" + +#: clients/gtk2/mainwnd.gtk:618 +msgid "_Uploads" +msgstr "_Uppladdningar" + +#: clients/gtk2/pref.gtk:12 +msgid "_Public hub list URL:" +msgstr "_URL för publik hublista:" + +#: clients/gtk2/pref.gtk:22 +msgid "_Dolda connect user name:" +msgstr "Användarnamn för _Dolda Connect:" + +#: clients/gtk2/pref.gtk:32 +msgid "Dolda Connect _server:" +msgstr "Dolda Connect-_server:" + +#: clients/gtk2/pref.gtk:42 +msgid "Connect _automatically on startup" +msgstr "Anslut au_tomatiskt vid uppstart" + +#: clients/gnome-trans-applet/dolcon-trans-applet.c:57 +msgid "Calculating remaining time..." +msgstr "Beräknar återstående tid..." + +#: clients/gnome-trans-applet/dolcon-trans-applet.c:62 +msgid "Time left: Infinite (Transfer is standing still)" +msgstr "Tid kvar: Oändlig (Överföringen står still)" + +#: clients/gnome-trans-applet/dolcon-trans-applet.c:66 +#, c-format +msgid "Time left: %i:%02i" +msgstr "Tid kvar: %i:%02i" + +#: clients/gnome-trans-applet/dolcon-trans-applet.c:80 +msgid "Not connected" +msgstr "Ej ansluten" + +#: clients/gnome-trans-applet/dolcon-trans-applet.c:92 +msgid "No transfers to display" +msgstr "Det finns inga överföringar att visa" + +#: clients/gnome-trans-applet/dolcon-trans-applet.c:96 +msgid "No transfer selected" +msgstr "Ingen överföring vald" + +#: clients/gnome-trans-applet/dolcon-trans-applet.c:105 +msgid "Initializing" +msgstr "Förbereder"