Transfer from CVS at SourceForge
authorfredrik <fredrik@959494ce-11ee-0310-bf91-de5d638817bd>
Fri, 14 Oct 2005 23:35:54 +0000 (23:35 +0000)
committerfredrik <fredrik@959494ce-11ee-0310-bf91-de5d638817bd>
Fri, 14 Oct 2005 23:35:54 +0000 (23:35 +0000)
git-svn-id: svn+ssh://svn.dolda2000.com/srv/svn/repos/src/doldaconnect@356 959494ce-11ee-0310-bf91-de5d638817bd

129 files changed:
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
CVS/Entries [new file with mode: 0644]
CVS/Repository [new file with mode: 0644]
CVS/Root [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
autopackage/CVS/Entries [new file with mode: 0644]
autopackage/CVS/Repository [new file with mode: 0644]
autopackage/CVS/Root [new file with mode: 0644]
autopackage/dolcon.apspec.in [new file with mode: 0644]
bootstrap [new file with mode: 0755]
clients/CVS/Entries [new file with mode: 0644]
clients/CVS/Repository [new file with mode: 0644]
clients/CVS/Root [new file with mode: 0644]
clients/Makefile.am [new file with mode: 0644]
clients/gnome-trans-applet/CVS/Entries [new file with mode: 0644]
clients/gnome-trans-applet/CVS/Repository [new file with mode: 0644]
clients/gnome-trans-applet/CVS/Root [new file with mode: 0644]
clients/gnome-trans-applet/Dolcon_Transferapplet_Factory.server.in [new file with mode: 0644]
clients/gnome-trans-applet/Makefile.am [new file with mode: 0644]
clients/gnome-trans-applet/conduit-dclib.c [new file with mode: 0644]
clients/gnome-trans-applet/conduit-pipe.c [new file with mode: 0644]
clients/gnome-trans-applet/conduit.c [new file with mode: 0644]
clients/gnome-trans-applet/conduit.h [new file with mode: 0644]
clients/gnome-trans-applet/dctrmon [new file with mode: 0755]
clients/gnome-trans-applet/dolcon-trans-applet.c [new file with mode: 0644]
clients/gtk2/CVS/Entries [new file with mode: 0644]
clients/gtk2/CVS/Repository [new file with mode: 0644]
clients/gtk2/CVS/Root [new file with mode: 0644]
clients/gtk2/Makefile.am [new file with mode: 0644]
clients/gtk2/emacs-local [new file with mode: 0644]
clients/gtk2/inpdialog.desc [new file with mode: 0644]
clients/gtk2/main.c [new file with mode: 0644]
clients/gtk2/mainwnd.desc [new file with mode: 0644]
clients/gtk2/makegdesc [new file with mode: 0755]
clients/gtk2/pref.desc [new file with mode: 0644]
clients/gtk2/progressbar.c [new file with mode: 0644]
clients/gtk2/progressbar.h [new file with mode: 0644]
clients/hellodolda.jpg [new file with mode: 0644]
clients/test.c [new file with mode: 0644]
config/CVS/Entries [new file with mode: 0644]
config/CVS/Repository [new file with mode: 0644]
config/CVS/Root [new file with mode: 0644]
config/Makefile.am [new file with mode: 0644]
config/dc-filter [new file with mode: 0644]
config/dc-filtercmd [new file with mode: 0755]
config/doldacond.conf [new file with mode: 0644]
config/locktouch.c [new file with mode: 0644]
config/speedrec.c [new file with mode: 0644]
configure.in [new file with mode: 0644]
daemon/CVS/Entries [new file with mode: 0644]
daemon/CVS/Repository [new file with mode: 0644]
daemon/CVS/Root [new file with mode: 0644]
daemon/Makefile.am [new file with mode: 0644]
daemon/auth-krb5.c [new file with mode: 0644]
daemon/auth-pam.c [new file with mode: 0644]
daemon/auth.c [new file with mode: 0644]
daemon/auth.h [new file with mode: 0644]
daemon/client.c [new file with mode: 0644]
daemon/client.h [new file with mode: 0644]
daemon/conf.c [new file with mode: 0644]
daemon/conf.h [new file with mode: 0644]
daemon/emacs-local [new file with mode: 0644]
daemon/filenet.c [new file with mode: 0644]
daemon/filenet.h [new file with mode: 0644]
daemon/fnet-dc.c [new file with mode: 0644]
daemon/log.c [new file with mode: 0644]
daemon/log.h [new file with mode: 0644]
daemon/main.c [new file with mode: 0644]
daemon/module.h [new file with mode: 0644]
daemon/net.c [new file with mode: 0644]
daemon/net.h [new file with mode: 0644]
daemon/search.c [new file with mode: 0644]
daemon/search.h [new file with mode: 0644]
daemon/sysevents.h [new file with mode: 0644]
daemon/tiger.c [new file with mode: 0644]
daemon/tiger.h [new file with mode: 0644]
daemon/transfer.c [new file with mode: 0644]
daemon/transfer.h [new file with mode: 0644]
daemon/ui.c [new file with mode: 0644]
daemon/uiretref [new file with mode: 0644]
daemon/utils.c [new file with mode: 0644]
daemon/utils.h [new file with mode: 0644]
include/CVS/Entries [new file with mode: 0644]
include/CVS/Repository [new file with mode: 0644]
include/CVS/Root [new file with mode: 0644]
include/Makefile.am [new file with mode: 0644]
include/doldaconnect/CVS/Entries [new file with mode: 0644]
include/doldaconnect/CVS/Repository [new file with mode: 0644]
include/doldaconnect/CVS/Root [new file with mode: 0644]
include/doldaconnect/uilib.h [new file with mode: 0644]
include/doldaconnect/uimisc.h [new file with mode: 0644]
include/doldaconnect/utils.h [new file with mode: 0644]
lib/CVS/Entries [new file with mode: 0644]
lib/CVS/Repository [new file with mode: 0644]
lib/CVS/Root [new file with mode: 0644]
lib/Makefile.am [new file with mode: 0644]
lib/guile/CVS/Entries [new file with mode: 0644]
lib/guile/CVS/Repository [new file with mode: 0644]
lib/guile/CVS/Root [new file with mode: 0644]
lib/guile/Makefile.am [new file with mode: 0644]
lib/guile/autodl [new file with mode: 0755]
lib/guile/chatlog [new file with mode: 0755]
lib/guile/dolcon-guile.c [new file with mode: 0644]
lib/guile/dolcon/CVS/Entries [new file with mode: 0644]
lib/guile/dolcon/CVS/Repository [new file with mode: 0644]
lib/guile/dolcon/CVS/Root [new file with mode: 0644]
lib/guile/dolcon/Makefile.am [new file with mode: 0644]
lib/guile/dolcon/ui.scm [new file with mode: 0644]
lib/guile/dolcon/util.scm [new file with mode: 0644]
lib/initcmds.h [new file with mode: 0644]
lib/makecmds [new file with mode: 0755]
lib/uicmds [new file with mode: 0644]
lib/uilib.c [new file with mode: 0644]
lib/uilib.h [new file with mode: 0644]
lib/uimisc.c [new file with mode: 0644]
lib/utils.c [new file with mode: 0644]
po/CVS/Entries [new file with mode: 0644]
po/CVS/Repository [new file with mode: 0644]
po/CVS/Root [new file with mode: 0644]
po/ChangeLog [new file with mode: 0644]
po/LINGUAS [new file with mode: 0644]
po/Makevars [new file with mode: 0644]
po/POTFILES.in [new file with mode: 0644]
po/sv.po [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..d17f040
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Maintainer, developer, documenter, etc.:
+Fredrik Tolf <fredrik@dolda2000.com>
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+           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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
index 0000000..2a2ae7f
--- /dev/null
@@ -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 (file)
index 0000000..48b3e60
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect
diff --git a/CVS/Root b/CVS/Root
new file mode 100644 (file)
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 (file)
index 0000000..d4e4aa6
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,62 @@
+2005-07-09  Fredrik Tolf  <fredrik@dolda2000.com>
+
+       * Cleaned up CVS heavily.
+
+2004-08-13  gettextize  <bug-gnu-gettext@gnu.org>
+
+       * 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  <dolda2000@pc7.dolda2000.com>
+
+       * 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 (file)
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 (file)
index 0000000..a08c847
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/autopackage/CVS/Entries b/autopackage/CVS/Entries
new file mode 100644 (file)
index 0000000..b5479b2
--- /dev/null
@@ -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 (file)
index 0000000..1564dad
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/autopackage
diff --git a/autopackage/CVS/Root b/autopackage/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -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 (file)
index 0000000..0fbfbf8
--- /dev/null
@@ -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 <fredrik@dolda2000.com>
+Packager: Fredrik Tolf <fredrik@dolda2000.com>
+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 (executable)
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 (file)
index 0000000..fcfb102
--- /dev/null
@@ -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 (file)
index 0000000..e46c3bb
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/clients
diff --git a/clients/CVS/Root b/clients/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -0,0 +1 @@
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
diff --git a/clients/Makefile.am b/clients/Makefile.am
new file mode 100644 (file)
index 0000000..aca20d2
--- /dev/null
@@ -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 (file)
index 0000000..8beace3
--- /dev/null
@@ -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 (file)
index 0000000..4ebbf5a
--- /dev/null
@@ -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 (file)
index 0000000..2886064
--- /dev/null
@@ -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 (file)
index 0000000..b058242
--- /dev/null
@@ -0,0 +1,21 @@
+<oaf_info>
+    <oaf_server iid="OAFIID:Dolcon_Transferapplet_Factory" type="exe" location="@LIBEXECDIR@/dolcon-trans-applet">
+        <oaf_attribute name="repo_ids" type="stringv">
+           <item value="IDL:Bonobo/GenericFactory:1.0" />
+           <item value="IDL:Bonobo/Unknown:1.0" />
+       </oaf_attribute>
+       <oaf_attribute name="name" type="string" value="Doldaconnect Transfer Viewer Factory" />
+       <oaf_attribute name="description" type="string" value="Factory for creating Doldaconnect Transfer Viewer applets" />
+    </oaf_server>
+    
+    <oaf_server iid="OAFIID:Dolcon_Transferapplet" type="factory" location="OAFIID:Dolcon_Transferapplet_Factory">
+        <oaf_attribute name="repo_ids" type="stringv">
+           <item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0" />
+           <item value="IDL:Bonobo/Control:1.0" />
+           <item value="IDL:Bonobo/Unknown:1.0" />
+       </oaf_attribute>
+       <oaf_attribute name="name" type="string" value="Doldaconnect Transfer Viewer" />
+       <oaf_attribute name="description" type="string" value="Easily grasp the current status of your Doldaconnect transfers" />
+       <oaf_attribute name="panel:icon" type="string" value="hellodolda.jpg" />
+    </oaf_server>
+</oaf_info>
diff --git a/clients/gnome-trans-applet/Makefile.am b/clients/gnome-trans-applet/Makefile.am
new file mode 100644 (file)
index 0000000..c73f993
--- /dev/null
@@ -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 (file)
index 0000000..afc90c3
--- /dev/null
@@ -0,0 +1,314 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <panel-applet.h>
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+
+#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 (file)
index 0000000..c58f4b4
--- /dev/null
@@ -0,0 +1,190 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <panel-applet.h>
+#include <doldaconnect/utils.h>
+
+#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 (file)
index 0000000..5959a27
--- /dev/null
@@ -0,0 +1,145 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <doldaconnect/utils.h>
+#include <panel-applet.h>
+#include <time.h>
+
+#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 (file)
index 0000000..2540960
--- /dev/null
@@ -0,0 +1,55 @@
+#ifndef _CONDUIT_H
+#define _CONDUIT_H
+
+#include <sys/types.h>
+
+#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 (executable)
index 0000000..8b9e8cb
--- /dev/null
@@ -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 (file)
index 0000000..b09e529
--- /dev/null
@@ -0,0 +1,229 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/utils.h>
+#include <panel-applet.h>
+#include <gtk/gtk.h>
+#include <time.h>
+
+#include "conduit.h"
+
+struct appletdata
+{
+    PanelApplet *applet;
+    GtkLabel *label;
+    GtkProgressBar *pbar;
+    GtkTooltips *tips;
+    gint tiptimeout;
+    struct conduit *conduit;
+    struct transfer *curdisplay;
+};
+
+static char *ctxtmenu =
+"<popup name='button3'>"
+"    <menuitem name='Preferences' verb='dca_pref' _label='Preferences' pixtype='stock' pixname='gtk-properties'>"
+"    </menuitem>"
+"</popup>";
+
+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 (file)
index 0000000..2282c03
--- /dev/null
@@ -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 (file)
index 0000000..26ff2c0
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/clients/gtk2
diff --git a/clients/gtk2/CVS/Root b/clients/gtk2/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -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 (file)
index 0000000..6afbb6f
--- /dev/null
@@ -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 (file)
index 0000000..f7263a1
--- /dev/null
@@ -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 (file)
index 0000000..b441f33
--- /dev/null
@@ -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 (file)
index 0000000..66b1cce
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdarg.h>
+#include <gtk/gtk.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <gdk/gdkkeysyms.h>
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+#include <errno.h>
+#include <regex.h>
+#include <signal.h>
+#include <time.h>
+#include <pwd.h>
+#include <locale.h>
+#include <libintl.h>
+#include <assert.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..c5d653e
--- /dev/null
@@ -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 (executable)
index 0000000..31cd9a0
--- /dev/null
@@ -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 (file)
index 0000000..f14aaeb
--- /dev/null
@@ -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 (file)
index 0000000..2dff9b2
--- /dev/null
@@ -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 (file)
index 0000000..4b773f3
--- /dev/null
@@ -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 <gtk/gtk.h>
+
+/* 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 (file)
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 (file)
index 0000000..25deedb
--- /dev/null
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+
+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 (file)
index 0000000..f757bc9
--- /dev/null
@@ -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 (file)
index 0000000..65bc6a8
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/config
diff --git a/config/CVS/Root b/config/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -0,0 +1 @@
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
diff --git a/config/Makefile.am b/config/Makefile.am
new file mode 100644 (file)
index 0000000..56c357c
--- /dev/null
@@ -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 (file)
index 0000000..0b31bbf
--- /dev/null
@@ -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 (executable)
index 0000000..96444eb
--- /dev/null
@@ -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 (file)
index 0000000..dd7b332
--- /dev/null
@@ -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 (file)
index 0000000..da7b862
--- /dev/null
@@ -0,0 +1,27 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+
+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 (file)
index 0000000..be0ed2d
--- /dev/null
@@ -0,0 +1,134 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/file.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/poll.h>
+#include <signal.h>
+
+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 (file)
index 0000000..139635b
--- /dev/null
@@ -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 <netinet/in.h>])
+
+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 (file)
index 0000000..186e6b2
--- /dev/null
@@ -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 (file)
index 0000000..b88236b
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/daemon
diff --git a/daemon/CVS/Root b/daemon/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -0,0 +1 @@
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
new file mode 100644 (file)
index 0000000..342ad7a
--- /dev/null
@@ -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 (file)
index 0000000..62827c6
--- /dev/null
@@ -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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <pwd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "auth.h"
+#include "utils.h"
+#include "conf.h"
+#include "log.h"
+#include "module.h"
+#include "sysevents.h"
+
+#ifdef HAVE_KRB5
+
+#include <krb5.h>
+#include <com_err.h>
+
+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 (file)
index 0000000..48dbcbd
--- /dev/null
@@ -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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ucontext.h>
+#include <security/pam_appl.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..0d34fbb
--- /dev/null
@@ -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 <wchar.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..0763841
--- /dev/null
@@ -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 <wchar.h>
+
+#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 (file)
index 0000000..e7fa0e7
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <wchar.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..7aee5cf
--- /dev/null
@@ -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 <sys/types.h>
+
+#include <utils.h>
+
+#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 (file)
index 0000000..ea3229e
--- /dev/null
@@ -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 <langinfo.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <wctype.h>
+#include <stddef.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <arpa/inet.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..7edc835
--- /dev/null
@@ -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 <stddef.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <utils.h>
+
+#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 (file)
index 0000000..f7263a1
--- /dev/null
@@ -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 (file)
index 0000000..81a83c4
--- /dev/null
@@ -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 <string.h>
+#include <wchar.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..8e3da7b
--- /dev/null
@@ -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 <wchar.h>
+#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 (file)
index 0000000..4afd0c0
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <alloca.h>
+#include <wctype.h>
+#include <time.h>
+#include <errno.h>
+#include <bzlib.h>
+#include <zlib.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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"&amp;"},
+    {L'\"', L"&#34;"},
+/*  {L'\'', L"&quot;"},  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, "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n");
+    for(i = 0; i < sizeof(cidbuf) - 1; i++)
+       cidbuf[i] = (rand() % ('Z' - 'A' + 1)) + 'A';
+    cidbuf[i] = 0;
+    fprintf(fs, "<FileListing Version=\"1\" CID=\"%s\" Base=\"/\" Generator=\"%s\">\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, "<Directory Name=\"%s\">\r\n", namebuf);
+               node = node->child;
+               lev++;
+               continue;
+           } else {
+               fprintf(fs, "<File Name=\"%s\" Size=\"%i\"", namebuf, node->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, "</Directory>\r\n");
+               }
+           }
+           if(node == shareroot)
+               break;
+           node = node->next;
+       }
+    }
+    
+#ifdef DCPP_MASQUERADE
+    fprintf(fs, "</FileListing>");
+#else
+    fprintf(fs, "</FileListing>\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 (file)
index 0000000..49a444c
--- /dev/null
@@ -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 <syslog.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/uio.h>
+#include <string.h>
+#include <malloc.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..b411418
--- /dev/null
@@ -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 <syslog.h>
+
+#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 (file)
index 0000000..299fd0d
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+#include <signal.h>
+#include <getopt.h>
+#include <time.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include <fcntl.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..385f6c9
--- /dev/null
@@ -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 <stdio.h>
+
+#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 (file)
index 0000000..f16441f
--- /dev/null
@@ -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 <config.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/signal.h>
+#include <printf.h>
+#ifdef HAVE_LINUX_SOCKIOS_H
+#include <linux/sockios.h>
+#endif
+#include <errno.h>
+#include <net/if.h>
+
+#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 (file)
index 0000000..451c398
--- /dev/null
@@ -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 <sys/socket.h>
+
+#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 (file)
index 0000000..040ec30
--- /dev/null
@@ -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 <stdlib.h>
+#include <malloc.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <errno.h>
+#include <regex.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..dff4c80
--- /dev/null
@@ -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 <regex.h>
+#include <wchar.h>
+
+#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 (file)
index 0000000..47e574f
--- /dev/null
@@ -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 <sys/types.h>
+#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 (file)
index 0000000..6465dbb
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..a6322ff
--- /dev/null
@@ -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 (file)
index 0000000..15bd5d2
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..af50310
--- /dev/null
@@ -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 <wchar.h>
+#include <sys/types.h>
+
+#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 (file)
index 0000000..0302fa1
--- /dev/null
@@ -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 <unistd.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <iconv.h>
+#include <pwd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..8d10b2c
--- /dev/null
@@ -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 (file)
index 0000000..216ae77
--- /dev/null
@@ -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 <malloc.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <errno.h>
+#include <string.h>
+#include <wctype.h>
+#include <langinfo.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 (file)
index 0000000..b8b6803
--- /dev/null
@@ -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 <stdarg.h>
+#include <stdlib.h>
+#include <malloc.h>
+#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 (file)
index 0000000..25536c8
--- /dev/null
@@ -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 (file)
index 0000000..9845500
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/include
diff --git a/include/CVS/Root b/include/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -0,0 +1 @@
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644 (file)
index 0000000..4c6fb9a
--- /dev/null
@@ -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 (file)
index 0000000..a6ad5de
--- /dev/null
@@ -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 (file)
index 0000000..f2b63cf
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/include/doldaconnect
diff --git a/include/doldaconnect/CVS/Root b/include/doldaconnect/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -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 (file)
index 0000000..d67afb3
--- /dev/null
@@ -0,0 +1,56 @@
+#ifndef _UILIB_H
+#define _UILIB_H
+
+#include <wchar.h>
+
+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 (file)
index 0000000..a7bc3e7
--- /dev/null
@@ -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 <time.h>
+
+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 (file)
index 0000000..60e06ad
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+/* "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 (file)
index 0000000..01eba9f
--- /dev/null
@@ -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 (file)
index 0000000..9ae7dde
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/lib
diff --git a/lib/CVS/Root b/lib/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -0,0 +1 @@
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644 (file)
index 0000000..2449ba7
--- /dev/null
@@ -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 <uicmds >initcmds.h
+
+AM_CPPFLAGS=-I$(top_srcdir)/include
diff --git a/lib/guile/CVS/Entries b/lib/guile/CVS/Entries
new file mode 100644 (file)
index 0000000..6c1ad48
--- /dev/null
@@ -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 (file)
index 0000000..328ead7
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/lib/guile
diff --git a/lib/guile/CVS/Root b/lib/guile/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -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 (file)
index 0000000..caf7c84
--- /dev/null
@@ -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 (executable)
index 0000000..f57423f
--- /dev/null
@@ -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 (executable)
index 0000000..0c5b097
--- /dev/null
@@ -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 (file)
index 0000000..9e05753
--- /dev/null
@@ -0,0 +1,339 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/poll.h>
+#include <errno.h>
+#include <libguile.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+
+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("#<dc-response ", port);
+    scm_display(SCM_MAKINUM(data->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 (file)
index 0000000..662f626
--- /dev/null
@@ -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 (file)
index 0000000..952d17b
--- /dev/null
@@ -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 (file)
index 0000000..2886064
--- /dev/null
@@ -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 (file)
index 0000000..4996736
--- /dev/null
@@ -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 (file)
index 0000000..73828df
--- /dev/null
@@ -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 (file)
index 0000000..c772efc
--- /dev/null
@@ -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 (file)
index 0000000..70475c0
--- /dev/null
@@ -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 (executable)
index 0000000..750b43d
--- /dev/null
@@ -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 (file)
index 0000000..d59d03d
--- /dev/null
@@ -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 (file)
index 0000000..4d440de
--- /dev/null
@@ -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 <config.h>
+#endif
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iconv.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/poll.h>
+#ifdef HAVE_RESOLVER
+#include <arpa/nameser.h>
+#include <resolv.h>
+#endif
+
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/utils.h>
+
+#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 (file)
index 0000000..7c0efe4
--- /dev/null
@@ -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 (file)
index 0000000..6a759be
--- /dev/null
@@ -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 <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <pwd.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdio.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+
+#ifdef HAVE_KRB5
+#include <krb5.h>
+#include <com_err.h>
+#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 (file)
index 0000000..f5cd7d0
--- /dev/null
@@ -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 <malloc.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <errno.h>
+#include <string.h>
+#include <wctype.h>
+#include <langinfo.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <doldaconnect/utils.h>
+
+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 (file)
index 0000000..0d64cf3
--- /dev/null
@@ -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 (file)
index 0000000..4a5c94d
--- /dev/null
@@ -0,0 +1 @@
+doldaconnect/po
diff --git a/po/CVS/Root b/po/CVS/Root
new file mode 100644 (file)
index 0000000..2886064
--- /dev/null
@@ -0,0 +1 @@
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
diff --git a/po/ChangeLog b/po/ChangeLog
new file mode 100644 (file)
index 0000000..4e6a12b
--- /dev/null
@@ -0,0 +1,11 @@
+2004-08-13  gettextize  <bug-gnu-gettext@gnu.org>
+
+       * 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 (file)
index 0000000..b917a40
--- /dev/null
@@ -0,0 +1,2 @@
+sv
+
diff --git a/po/Makevars b/po/Makevars
new file mode 100644 (file)
index 0000000..eb62ce1
--- /dev/null
@@ -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 (file)
index 0000000..1b57489
--- /dev/null
@@ -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 (file)
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 <fredrik@dolda2000.com>, 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 <fredrik@dolda2000.com>\n"
+"Language-Team: Swedish <sv@li.org>\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"