mirror of
https://github.com/NishiOwO/ircservices5.git
synced 2025-04-21 08:44:38 +00:00
Add files via upload
This commit is contained in:
parent
ec261a57cc
commit
ee2108e123
397
FDL.txt
Normal file
397
FDL.txt
Normal file
@ -0,0 +1,397 @@
|
||||
GNU Free Documentation License
|
||||
Version 1.2, November 2002
|
||||
|
||||
|
||||
Copyright (C) 2000,2001,2002 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
0. PREAMBLE
|
||||
|
||||
The purpose of this License is to make a manual, textbook, or other
|
||||
functional and useful document "free" in the sense of freedom: to
|
||||
assure everyone the effective freedom to copy and redistribute it,
|
||||
with or without modifying it, either commercially or noncommercially.
|
||||
Secondarily, this License preserves for the author and publisher a way
|
||||
to get credit for their work, while not being considered responsible
|
||||
for modifications made by others.
|
||||
|
||||
This License is a kind of "copyleft", which means that derivative
|
||||
works of the document must themselves be free in the same sense. It
|
||||
complements the GNU General Public License, which is a copyleft
|
||||
license designed for free software.
|
||||
|
||||
We have designed this License in order to use it for manuals for free
|
||||
software, because free software needs free documentation: a free
|
||||
program should come with manuals providing the same freedoms that the
|
||||
software does. But this License is not limited to software manuals;
|
||||
it can be used for any textual work, regardless of subject matter or
|
||||
whether it is published as a printed book. We recommend this License
|
||||
principally for works whose purpose is instruction or reference.
|
||||
|
||||
|
||||
1. APPLICABILITY AND DEFINITIONS
|
||||
|
||||
This License applies to any manual or other work, in any medium, that
|
||||
contains a notice placed by the copyright holder saying it can be
|
||||
distributed under the terms of this License. Such a notice grants a
|
||||
world-wide, royalty-free license, unlimited in duration, to use that
|
||||
work under the conditions stated herein. The "Document", below,
|
||||
refers to any such manual or work. Any member of the public is a
|
||||
licensee, and is addressed as "you". You accept the license if you
|
||||
copy, modify or distribute the work in a way requiring permission
|
||||
under copyright law.
|
||||
|
||||
A "Modified Version" of the Document means any work containing the
|
||||
Document or a portion of it, either copied verbatim, or with
|
||||
modifications and/or translated into another language.
|
||||
|
||||
A "Secondary Section" is a named appendix or a front-matter section of
|
||||
the Document that deals exclusively with the relationship of the
|
||||
publishers or authors of the Document to the Document's overall subject
|
||||
(or to related matters) and contains nothing that could fall directly
|
||||
within that overall subject. (Thus, if the Document is in part a
|
||||
textbook of mathematics, a Secondary Section may not explain any
|
||||
mathematics.) The relationship could be a matter of historical
|
||||
connection with the subject or with related matters, or of legal,
|
||||
commercial, philosophical, ethical or political position regarding
|
||||
them.
|
||||
|
||||
The "Invariant Sections" are certain Secondary Sections whose titles
|
||||
are designated, as being those of Invariant Sections, in the notice
|
||||
that says that the Document is released under this License. If a
|
||||
section does not fit the above definition of Secondary then it is not
|
||||
allowed to be designated as Invariant. The Document may contain zero
|
||||
Invariant Sections. If the Document does not identify any Invariant
|
||||
Sections then there are none.
|
||||
|
||||
The "Cover Texts" are certain short passages of text that are listed,
|
||||
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
|
||||
the Document is released under this License. A Front-Cover Text may
|
||||
be at most 5 words, and a Back-Cover Text may be at most 25 words.
|
||||
|
||||
A "Transparent" copy of the Document means a machine-readable copy,
|
||||
represented in a format whose specification is available to the
|
||||
general public, that is suitable for revising the document
|
||||
straightforwardly with generic text editors or (for images composed of
|
||||
pixels) generic paint programs or (for drawings) some widely available
|
||||
drawing editor, and that is suitable for input to text formatters or
|
||||
for automatic translation to a variety of formats suitable for input
|
||||
to text formatters. A copy made in an otherwise Transparent file
|
||||
format whose markup, or absence of markup, has been arranged to thwart
|
||||
or discourage subsequent modification by readers is not Transparent.
|
||||
An image format is not Transparent if used for any substantial amount
|
||||
of text. A copy that is not "Transparent" is called "Opaque".
|
||||
|
||||
Examples of suitable formats for Transparent copies include plain
|
||||
ASCII without markup, Texinfo input format, LaTeX input format, SGML
|
||||
or XML using a publicly available DTD, and standard-conforming simple
|
||||
HTML, PostScript or PDF designed for human modification. Examples of
|
||||
transparent image formats include PNG, XCF and JPG. Opaque formats
|
||||
include proprietary formats that can be read and edited only by
|
||||
proprietary word processors, SGML or XML for which the DTD and/or
|
||||
processing tools are not generally available, and the
|
||||
machine-generated HTML, PostScript or PDF produced by some word
|
||||
processors for output purposes only.
|
||||
|
||||
The "Title Page" means, for a printed book, the title page itself,
|
||||
plus such following pages as are needed to hold, legibly, the material
|
||||
this License requires to appear in the title page. For works in
|
||||
formats which do not have any title page as such, "Title Page" means
|
||||
the text near the most prominent appearance of the work's title,
|
||||
preceding the beginning of the body of the text.
|
||||
|
||||
A section "Entitled XYZ" means a named subunit of the Document whose
|
||||
title either is precisely XYZ or contains XYZ in parentheses following
|
||||
text that translates XYZ in another language. (Here XYZ stands for a
|
||||
specific section name mentioned below, such as "Acknowledgements",
|
||||
"Dedications", "Endorsements", or "History".) To "Preserve the Title"
|
||||
of such a section when you modify the Document means that it remains a
|
||||
section "Entitled XYZ" according to this definition.
|
||||
|
||||
The Document may include Warranty Disclaimers next to the notice which
|
||||
states that this License applies to the Document. These Warranty
|
||||
Disclaimers are considered to be included by reference in this
|
||||
License, but only as regards disclaiming warranties: any other
|
||||
implication that these Warranty Disclaimers may have is void and has
|
||||
no effect on the meaning of this License.
|
||||
|
||||
|
||||
2. VERBATIM COPYING
|
||||
|
||||
You may copy and distribute the Document in any medium, either
|
||||
commercially or noncommercially, provided that this License, the
|
||||
copyright notices, and the license notice saying this License applies
|
||||
to the Document are reproduced in all copies, and that you add no other
|
||||
conditions whatsoever to those of this License. You may not use
|
||||
technical measures to obstruct or control the reading or further
|
||||
copying of the copies you make or distribute. However, you may accept
|
||||
compensation in exchange for copies. If you distribute a large enough
|
||||
number of copies you must also follow the conditions in section 3.
|
||||
|
||||
You may also lend copies, under the same conditions stated above, and
|
||||
you may publicly display copies.
|
||||
|
||||
|
||||
3. COPYING IN QUANTITY
|
||||
|
||||
If you publish printed copies (or copies in media that commonly have
|
||||
printed covers) of the Document, numbering more than 100, and the
|
||||
Document's license notice requires Cover Texts, you must enclose the
|
||||
copies in covers that carry, clearly and legibly, all these Cover
|
||||
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
|
||||
the back cover. Both covers must also clearly and legibly identify
|
||||
you as the publisher of these copies. The front cover must present
|
||||
the full title with all words of the title equally prominent and
|
||||
visible. You may add other material on the covers in addition.
|
||||
Copying with changes limited to the covers, as long as they preserve
|
||||
the title of the Document and satisfy these conditions, can be treated
|
||||
as verbatim copying in other respects.
|
||||
|
||||
If the required texts for either cover are too voluminous to fit
|
||||
legibly, you should put the first ones listed (as many as fit
|
||||
reasonably) on the actual cover, and continue the rest onto adjacent
|
||||
pages.
|
||||
|
||||
If you publish or distribute Opaque copies of the Document numbering
|
||||
more than 100, you must either include a machine-readable Transparent
|
||||
copy along with each Opaque copy, or state in or with each Opaque copy
|
||||
a computer-network location from which the general network-using
|
||||
public has access to download using public-standard network protocols
|
||||
a complete Transparent copy of the Document, free of added material.
|
||||
If you use the latter option, you must take reasonably prudent steps,
|
||||
when you begin distribution of Opaque copies in quantity, to ensure
|
||||
that this Transparent copy will remain thus accessible at the stated
|
||||
location until at least one year after the last time you distribute an
|
||||
Opaque copy (directly or through your agents or retailers) of that
|
||||
edition to the public.
|
||||
|
||||
It is requested, but not required, that you contact the authors of the
|
||||
Document well before redistributing any large number of copies, to give
|
||||
them a chance to provide you with an updated version of the Document.
|
||||
|
||||
|
||||
4. MODIFICATIONS
|
||||
|
||||
You may copy and distribute a Modified Version of the Document under
|
||||
the conditions of sections 2 and 3 above, provided that you release
|
||||
the Modified Version under precisely this License, with the Modified
|
||||
Version filling the role of the Document, thus licensing distribution
|
||||
and modification of the Modified Version to whoever possesses a copy
|
||||
of it. In addition, you must do these things in the Modified Version:
|
||||
|
||||
A. Use in the Title Page (and on the covers, if any) a title distinct
|
||||
from that of the Document, and from those of previous versions
|
||||
(which should, if there were any, be listed in the History section
|
||||
of the Document). You may use the same title as a previous version
|
||||
if the original publisher of that version gives permission.
|
||||
B. List on the Title Page, as authors, one or more persons or entities
|
||||
responsible for authorship of the modifications in the Modified
|
||||
Version, together with at least five of the principal authors of the
|
||||
Document (all of its principal authors, if it has fewer than five),
|
||||
unless they release you from this requirement.
|
||||
C. State on the Title page the name of the publisher of the
|
||||
Modified Version, as the publisher.
|
||||
D. Preserve all the copyright notices of the Document.
|
||||
E. Add an appropriate copyright notice for your modifications
|
||||
adjacent to the other copyright notices.
|
||||
F. Include, immediately after the copyright notices, a license notice
|
||||
giving the public permission to use the Modified Version under the
|
||||
terms of this License, in the form shown in the Addendum below.
|
||||
G. Preserve in that license notice the full lists of Invariant Sections
|
||||
and required Cover Texts given in the Document's license notice.
|
||||
H. Include an unaltered copy of this License.
|
||||
I. Preserve the section Entitled "History", Preserve its Title, and add
|
||||
to it an item stating at least the title, year, new authors, and
|
||||
publisher of the Modified Version as given on the Title Page. If
|
||||
there is no section Entitled "History" in the Document, create one
|
||||
stating the title, year, authors, and publisher of the Document as
|
||||
given on its Title Page, then add an item describing the Modified
|
||||
Version as stated in the previous sentence.
|
||||
J. Preserve the network location, if any, given in the Document for
|
||||
public access to a Transparent copy of the Document, and likewise
|
||||
the network locations given in the Document for previous versions
|
||||
it was based on. These may be placed in the "History" section.
|
||||
You may omit a network location for a work that was published at
|
||||
least four years before the Document itself, or if the original
|
||||
publisher of the version it refers to gives permission.
|
||||
K. For any section Entitled "Acknowledgements" or "Dedications",
|
||||
Preserve the Title of the section, and preserve in the section all
|
||||
the substance and tone of each of the contributor acknowledgements
|
||||
and/or dedications given therein.
|
||||
L. Preserve all the Invariant Sections of the Document,
|
||||
unaltered in their text and in their titles. Section numbers
|
||||
or the equivalent are not considered part of the section titles.
|
||||
M. Delete any section Entitled "Endorsements". Such a section
|
||||
may not be included in the Modified Version.
|
||||
N. Do not retitle any existing section to be Entitled "Endorsements"
|
||||
or to conflict in title with any Invariant Section.
|
||||
O. Preserve any Warranty Disclaimers.
|
||||
|
||||
If the Modified Version includes new front-matter sections or
|
||||
appendices that qualify as Secondary Sections and contain no material
|
||||
copied from the Document, you may at your option designate some or all
|
||||
of these sections as invariant. To do this, add their titles to the
|
||||
list of Invariant Sections in the Modified Version's license notice.
|
||||
These titles must be distinct from any other section titles.
|
||||
|
||||
You may add a section Entitled "Endorsements", provided it contains
|
||||
nothing but endorsements of your Modified Version by various
|
||||
parties--for example, statements of peer review or that the text has
|
||||
been approved by an organization as the authoritative definition of a
|
||||
standard.
|
||||
|
||||
You may add a passage of up to five words as a Front-Cover Text, and a
|
||||
passage of up to 25 words as a Back-Cover Text, to the end of the list
|
||||
of Cover Texts in the Modified Version. Only one passage of
|
||||
Front-Cover Text and one of Back-Cover Text may be added by (or
|
||||
through arrangements made by) any one entity. If the Document already
|
||||
includes a cover text for the same cover, previously added by you or
|
||||
by arrangement made by the same entity you are acting on behalf of,
|
||||
you may not add another; but you may replace the old one, on explicit
|
||||
permission from the previous publisher that added the old one.
|
||||
|
||||
The author(s) and publisher(s) of the Document do not by this License
|
||||
give permission to use their names for publicity for or to assert or
|
||||
imply endorsement of any Modified Version.
|
||||
|
||||
|
||||
5. COMBINING DOCUMENTS
|
||||
|
||||
You may combine the Document with other documents released under this
|
||||
License, under the terms defined in section 4 above for modified
|
||||
versions, provided that you include in the combination all of the
|
||||
Invariant Sections of all of the original documents, unmodified, and
|
||||
list them all as Invariant Sections of your combined work in its
|
||||
license notice, and that you preserve all their Warranty Disclaimers.
|
||||
|
||||
The combined work need only contain one copy of this License, and
|
||||
multiple identical Invariant Sections may be replaced with a single
|
||||
copy. If there are multiple Invariant Sections with the same name but
|
||||
different contents, make the title of each such section unique by
|
||||
adding at the end of it, in parentheses, the name of the original
|
||||
author or publisher of that section if known, or else a unique number.
|
||||
Make the same adjustment to the section titles in the list of
|
||||
Invariant Sections in the license notice of the combined work.
|
||||
|
||||
In the combination, you must combine any sections Entitled "History"
|
||||
in the various original documents, forming one section Entitled
|
||||
"History"; likewise combine any sections Entitled "Acknowledgements",
|
||||
and any sections Entitled "Dedications". You must delete all sections
|
||||
Entitled "Endorsements".
|
||||
|
||||
|
||||
6. COLLECTIONS OF DOCUMENTS
|
||||
|
||||
You may make a collection consisting of the Document and other documents
|
||||
released under this License, and replace the individual copies of this
|
||||
License in the various documents with a single copy that is included in
|
||||
the collection, provided that you follow the rules of this License for
|
||||
verbatim copying of each of the documents in all other respects.
|
||||
|
||||
You may extract a single document from such a collection, and distribute
|
||||
it individually under this License, provided you insert a copy of this
|
||||
License into the extracted document, and follow this License in all
|
||||
other respects regarding verbatim copying of that document.
|
||||
|
||||
|
||||
7. AGGREGATION WITH INDEPENDENT WORKS
|
||||
|
||||
A compilation of the Document or its derivatives with other separate
|
||||
and independent documents or works, in or on a volume of a storage or
|
||||
distribution medium, is called an "aggregate" if the copyright
|
||||
resulting from the compilation is not used to limit the legal rights
|
||||
of the compilation's users beyond what the individual works permit.
|
||||
When the Document is included in an aggregate, this License does not
|
||||
apply to the other works in the aggregate which are not themselves
|
||||
derivative works of the Document.
|
||||
|
||||
If the Cover Text requirement of section 3 is applicable to these
|
||||
copies of the Document, then if the Document is less than one half of
|
||||
the entire aggregate, the Document's Cover Texts may be placed on
|
||||
covers that bracket the Document within the aggregate, or the
|
||||
electronic equivalent of covers if the Document is in electronic form.
|
||||
Otherwise they must appear on printed covers that bracket the whole
|
||||
aggregate.
|
||||
|
||||
|
||||
8. TRANSLATION
|
||||
|
||||
Translation is considered a kind of modification, so you may
|
||||
distribute translations of the Document under the terms of section 4.
|
||||
Replacing Invariant Sections with translations requires special
|
||||
permission from their copyright holders, but you may include
|
||||
translations of some or all Invariant Sections in addition to the
|
||||
original versions of these Invariant Sections. You may include a
|
||||
translation of this License, and all the license notices in the
|
||||
Document, and any Warranty Disclaimers, provided that you also include
|
||||
the original English version of this License and the original versions
|
||||
of those notices and disclaimers. In case of a disagreement between
|
||||
the translation and the original version of this License or a notice
|
||||
or disclaimer, the original version will prevail.
|
||||
|
||||
If a section in the Document is Entitled "Acknowledgements",
|
||||
"Dedications", or "History", the requirement (section 4) to Preserve
|
||||
its Title (section 1) will typically require changing the actual
|
||||
title.
|
||||
|
||||
|
||||
9. TERMINATION
|
||||
|
||||
You may not copy, modify, sublicense, or distribute the Document except
|
||||
as expressly provided for under this License. Any other attempt to
|
||||
copy, modify, sublicense or distribute the Document 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.
|
||||
|
||||
|
||||
10. FUTURE REVISIONS OF THIS LICENSE
|
||||
|
||||
The Free Software Foundation may publish new, revised versions
|
||||
of the GNU Free Documentation 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. See
|
||||
http://www.gnu.org/copyleft/.
|
||||
|
||||
Each version of the License is given a distinguishing version number.
|
||||
If the Document specifies that a particular numbered version of this
|
||||
License "or any later version" applies to it, you have the option of
|
||||
following the terms and conditions either of that specified version or
|
||||
of any later version that has been published (not as a draft) by the
|
||||
Free Software Foundation. If the Document does not specify a version
|
||||
number of this License, you may choose any version ever published (not
|
||||
as a draft) by the Free Software Foundation.
|
||||
|
||||
|
||||
ADDENDUM: How to use this License for your documents
|
||||
|
||||
To use this License in a document you have written, include a copy of
|
||||
the License in the document and put the following copyright and
|
||||
license notices just after the title page:
|
||||
|
||||
Copyright (c) YEAR YOUR NAME.
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.2
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
A copy of the license is included in the section entitled "GNU
|
||||
Free Documentation License".
|
||||
|
||||
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
|
||||
replace the "with...Texts." line with this:
|
||||
|
||||
with the Invariant Sections being LIST THEIR TITLES, with the
|
||||
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
|
||||
|
||||
If you have Invariant Sections without Cover Texts, or some other
|
||||
combination of the three, merge those two alternatives to suit the
|
||||
situation.
|
||||
|
||||
If your document contains nontrivial examples of program code, we
|
||||
recommend releasing these examples in parallel under your choice of
|
||||
free software license, such as the GNU General Public License,
|
||||
to permit their use in free software.
|
339
GPL.txt
Normal file
339
GPL.txt
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <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.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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 Lesser General
|
||||
Public License instead of this License.
|
188
Makefile
Normal file
188
Makefile
Normal file
@ -0,0 +1,188 @@
|
||||
# Makefile for Services.
|
||||
#
|
||||
# IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
# E-mail: <achurch@achurch.org>
|
||||
# Parts written by Andrew Kempe and others.
|
||||
# This program is free but copyrighted software; see the file GPL.txt for
|
||||
# details.
|
||||
|
||||
|
||||
include Makefile.inc
|
||||
TOPDIR=.
|
||||
|
||||
default: all
|
||||
|
||||
###########################################################################
|
||||
########################## Configuration section ##########################
|
||||
|
||||
# NOTE: Compilation options (CLEAN_COMPILE, etc.) are now all controlled by
|
||||
# the configure script.
|
||||
|
||||
# Add any extra flags you want here. The default line below enables
|
||||
# warnings and debugging symbols on GCC. If you have a non-GCC compiler,
|
||||
# you may want to comment it out or change it. ("icc" below is the Intel
|
||||
# C compiler for Linux, which doesn't understand GCC's -W/-f options.)
|
||||
|
||||
ifeq ($(CC),icc)
|
||||
MORE_CFLAGS = -g
|
||||
else
|
||||
MORE_CFLAGS = -g -Wall -Wmissing-prototypes
|
||||
endif
|
||||
|
||||
######################## End configuration section ########################
|
||||
###########################################################################
|
||||
|
||||
|
||||
ifneq ($(STATIC_MODULES),)
|
||||
CDEFS += -DSTATIC_MODULES
|
||||
MODLIB = modules/modules.a
|
||||
endif
|
||||
|
||||
CFLAGS = $(CDEFS) $(BASE_CFLAGS) $(MORE_CFLAGS)
|
||||
|
||||
|
||||
OBJS = actions.o channels.o commands.o compat.o conffile.o databases.o \
|
||||
encrypt.o ignore.o init.o language.o log.o main.o memory.o \
|
||||
messages.o misc.o modes.o modules.o process.o send.o servers.o \
|
||||
signals.o sockets.o timeout.o users.o $(VSNPRINTF_O)
|
||||
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
###########################################################################
|
||||
|
||||
.PHONY: default config-check all myall myclean clean spotless distclean \
|
||||
install instbin instmod modules languages tools
|
||||
|
||||
###########################################################################
|
||||
|
||||
config-check:
|
||||
@if ./configure -check ; then : ; else \
|
||||
echo 'Either you have not yet run the "configure" script, or the script has been' ; \
|
||||
echo 'updated since you last ran it. Please run "./configure" before running' ; \
|
||||
echo '"$(MAKE)".' ; \
|
||||
exit 1 ; \
|
||||
fi
|
||||
|
||||
all: config-check myall
|
||||
@echo Now run \"$(MAKE) install\" to install Services.
|
||||
|
||||
myall: $(PROGRAM) languages tools
|
||||
|
||||
myclean:
|
||||
rm -f *.o $(PROGRAM) version.c.old
|
||||
|
||||
clean: myclean
|
||||
$(MAKE) -C lang clean
|
||||
$(MAKE) -C modules clean
|
||||
$(MAKE) -C tools clean
|
||||
|
||||
spotless distclean: myclean
|
||||
$(MAKE) -C lang spotless
|
||||
$(MAKE) -C modules spotless
|
||||
$(MAKE) -C tools spotless
|
||||
rm -rf config.cache config.h* configure.log conf-tmp Makefile.inc* \
|
||||
langstrs.h version.c
|
||||
|
||||
install: config-check myall myinstall
|
||||
@echo ""
|
||||
@echo "Don't forget to create/update your ircservices.conf and modules.conf files!"
|
||||
@echo "See the README for details."
|
||||
@echo ""
|
||||
|
||||
myinstall: instdirs instbin instmod
|
||||
$(MAKE) -C lang install
|
||||
$(MAKE) -C tools install
|
||||
$(MAKE) -C data install
|
||||
|
||||
instdirs:
|
||||
mkdir -p "$(INSTALL_PREFIX)$(BINDEST)"
|
||||
mkdir -p "$(INSTALL_PREFIX)$(DATDEST)"
|
||||
|
||||
instbin:
|
||||
$(INSTALL_EXE) $(PROGRAM)$(EXE_SUFFIX) "$(INSTALL_PREFIX)$(BINDEST)/$(PROGRAM)$(EXE_SUFFIX)"
|
||||
|
||||
ifneq ($(STATIC_MODULES),)
|
||||
instmod:
|
||||
else
|
||||
instmod:
|
||||
$(MAKE) -C modules install
|
||||
endif
|
||||
|
||||
###########################################################################
|
||||
|
||||
$(PROGRAM): $(OBJS) modules version.o
|
||||
$(CC) $(LFLAGS) $(OBJS) version.o $(MODLIB) $(LIBS) -o $@
|
||||
|
||||
ifneq ($(STATIC_MODULES),)
|
||||
modules:
|
||||
@$(MAKE) -C modules all-static CFLAGS="$(CFLAGS)"
|
||||
else
|
||||
modules:
|
||||
@$(MAKE) -C modules all-dynamic CFLAGS="$(CFLAGS)"
|
||||
endif
|
||||
|
||||
languages:
|
||||
@$(MAKE) -C lang CFLAGS="$(CFLAGS)"
|
||||
|
||||
tools: services.h
|
||||
@$(MAKE) -C tools CFLAGS="$(CFLAGS)"
|
||||
|
||||
|
||||
# Catch any changes in compilation options at the top of this file or the
|
||||
# configure output
|
||||
$(OBJS): Makefile Makefile.inc
|
||||
|
||||
$(OBJS): services.h
|
||||
|
||||
actions.o: actions.c language.h modules.h timeout.h
|
||||
channels.o: channels.c modules.h hash.h
|
||||
commands.o: commands.c modules.h commands.h language.h
|
||||
compat.o: compat.c
|
||||
conffile.o: conffile.c conffile.h
|
||||
databases.o: databases.c databases.h encrypt.h modules.h
|
||||
encrypt.o: encrypt.c encrypt.h
|
||||
ignore.o: ignore.c
|
||||
init.o: init.c conffile.h databases.h messages.h modules.h \
|
||||
language.h version.h
|
||||
language.o: language.c language.h modules/nickserv/nickserv.h
|
||||
log.o: log.c
|
||||
main.o: main.c databases.h modules.h timeout.h
|
||||
memory.o: memory.c
|
||||
messages.o: messages.c messages.h language.h modules.h version.h \
|
||||
modules/operserv/operserv.h
|
||||
misc.o: misc.c
|
||||
modes.o: modes.c
|
||||
modules.o: modules.c modules.h conffile.h
|
||||
process.o: process.c modules.h messages.h
|
||||
send.o: send.c modules.h language.h
|
||||
servers.o: servers.c modules.h hash.h
|
||||
signals.o: signals.c
|
||||
sockets.o: sockets.c
|
||||
timeout.o: timeout.c timeout.h
|
||||
users.o: users.c modules.h hash.h
|
||||
vsnprintf.o: vsnprintf.c
|
||||
|
||||
|
||||
services.h: config.h defs.h memory.h list-array.h log.h sockets.h \
|
||||
send.h modes.h users.h channels.h servers.h extern.h
|
||||
-touch $@
|
||||
|
||||
version.c: $(OBJS) modules/.stamp
|
||||
sh version.sh
|
||||
modules/.stamp: modules
|
||||
|
||||
databases.h: modules.h
|
||||
-touch $@
|
||||
|
||||
language.h: langstrs.h
|
||||
-touch $@
|
||||
|
||||
langstrs.h: lang/langstrs.h
|
||||
cp -p lang/langstrs.h .
|
||||
|
||||
.PHONY: lang/langstrs.h
|
||||
lang/langstrs.h:
|
||||
$(MAKE) -C lang langstrs.h
|
||||
|
||||
###########################################################################
|
16
README
Normal file
16
README
Normal file
@ -0,0 +1,16 @@
|
||||
IRC Services -- a system of IRC services for IRC networks
|
||||
---------------------------------------------------------
|
||||
|
||||
IRC Services is copyright (c) 1996-2009 Andrew Church. There is absolutely
|
||||
NO WARRANTY provided with this program; if it blows up in your face, you
|
||||
get to clean up the mess. Services is provided under the terms of the GNU
|
||||
General Public License, version 2 or later, and may be freely redistributed,
|
||||
with certain limitations described in that license (notably, if you
|
||||
distribute IRC Services at all then you must distribute the source code as
|
||||
well). See the GNU General Public License (in the file GPL.txt) and the
|
||||
relevant section of the manual (docs/copyright.html).
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Documentation is located in the docs/ directory; point your browser at
|
||||
docs/index.html to start reading.
|
712
actions.c
Normal file
712
actions.c
Normal file
@ -0,0 +1,712 @@
|
||||
/* Various routines to perform simple actions.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "language.h"
|
||||
#include "modules.h"
|
||||
#include "timeout.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static int cb_clear_channel = -1;
|
||||
static int cb_set_topic = -1;
|
||||
|
||||
/* Sender to be used with clear_channel() (empty string: use server name) */
|
||||
static char clear_channel_sender[NICKMAX] = {0};
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
int actions_init(int ac, char **av)
|
||||
{
|
||||
cb_clear_channel = register_callback("clear channel");
|
||||
cb_set_topic = register_callback("set topic");
|
||||
if (cb_clear_channel < 0 || cb_set_topic < 0) {
|
||||
log("actions_init: register_callback() failed\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void actions_cleanup(void)
|
||||
{
|
||||
unregister_callback(cb_set_topic);
|
||||
unregister_callback(cb_clear_channel);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Note a bad password attempt for the given user from the given service.
|
||||
* If they've used up their limit, toss them off. `service' is used only
|
||||
* for the sender of the bad-password and warning messages; these messages
|
||||
* are not sent if `service' is NULL. `what' describes what the password
|
||||
* was for, and is used in the kill message if the user is killed. The
|
||||
* function's return value is 1 if the user was warned, 2 if the user was
|
||||
* killed, and 0 otherwise.
|
||||
*/
|
||||
|
||||
int bad_password(const char *service, User *u, const char *what)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (service)
|
||||
notice_lang(service, u, PASSWORD_INCORRECT);
|
||||
|
||||
if (!BadPassLimit)
|
||||
return 0;
|
||||
|
||||
if (BadPassTimeout > 0 && u->bad_pw_time > 0
|
||||
&& now >= u->bad_pw_time + BadPassTimeout)
|
||||
u->bad_pw_count = 0;
|
||||
u->bad_pw_count++;
|
||||
u->bad_pw_time = now;
|
||||
if (u->bad_pw_count >= BadPassLimit) {
|
||||
char buf[BUFSIZE];
|
||||
snprintf(buf, sizeof(buf), "Too many invalid passwords (%s)", what);
|
||||
kill_user(NULL, u->nick, buf);
|
||||
return 2;
|
||||
} else if (u->bad_pw_count == BadPassLimit-1) {
|
||||
if (service)
|
||||
notice_lang(service, u, PASSWORD_WARNING);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Clear modes/users from a channel. The "what" parameter is one or more
|
||||
* of the CLEAR_* constants defined in services.h. "param" is:
|
||||
* - for CLEAR_USERS, a kick message (const char *)
|
||||
* - for CLEAR_UMODES, a bitmask of modes to clear (int32)
|
||||
* - for CLEAR_BANS and CLEAR_EXCEPTS, a User * to match against, or
|
||||
* NULL for all bans/exceptions
|
||||
* Note that CLEAR_EXCEPTS must be handled via callback for protocols which
|
||||
* support it.
|
||||
*/
|
||||
|
||||
static void clear_modes(const char *sender, Channel *chan);
|
||||
static void clear_bans(const char *sender, Channel *chan, User *u);
|
||||
static void clear_umodes(const char *sender, Channel *chan, int32 modes);
|
||||
static void clear_users(const char *sender, Channel *chan, const char *reason);
|
||||
|
||||
void clear_channel(Channel *chan, int what, const void *param)
|
||||
{
|
||||
const char *sender =
|
||||
*clear_channel_sender ? clear_channel_sender : ServerName;
|
||||
|
||||
if (call_callback_4(cb_clear_channel, sender, chan, what, param) > 0) {
|
||||
set_cmode(NULL, chan);
|
||||
return;
|
||||
}
|
||||
|
||||
if (what & CLEAR_USERS) {
|
||||
clear_users(sender, chan, (const char *)param);
|
||||
/* Once we kick all the users, nothing else will matter */
|
||||
return;
|
||||
}
|
||||
|
||||
if (what & CLEAR_MODES)
|
||||
clear_modes(sender, chan);
|
||||
if (what & CLEAR_BANS)
|
||||
clear_bans(sender, chan, (User *)param);
|
||||
if (what & CLEAR_UMODES)
|
||||
clear_umodes(sender, chan, (int32)(long)param);
|
||||
set_cmode(NULL, chan); /* Flush modes out */
|
||||
}
|
||||
|
||||
static void clear_modes(const char *sender, Channel *chan)
|
||||
{
|
||||
char buf[BUFSIZE];
|
||||
snprintf(buf, sizeof(buf), "-%s",
|
||||
mode_flags_to_string(chan->mode & ~chanmode_reg, MODE_CHANNEL));
|
||||
set_cmode(sender, chan, buf, chan->key);
|
||||
}
|
||||
|
||||
static void clear_bans(const char *sender, Channel *chan, User *u)
|
||||
{
|
||||
int i, count;
|
||||
char **bans;
|
||||
|
||||
if (!chan->bans_count)
|
||||
return;
|
||||
|
||||
/* Save original ban info */
|
||||
count = chan->bans_count;
|
||||
bans = smalloc(sizeof(char *) * count);
|
||||
memcpy(bans, chan->bans, sizeof(char *) * count);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (!u || match_usermask(bans[i], u))
|
||||
set_cmode(sender, chan, "-b", bans[i]);
|
||||
}
|
||||
free(bans);
|
||||
}
|
||||
|
||||
static void clear_umodes(const char *sender, Channel *chan, int32 modes)
|
||||
{
|
||||
struct c_userlist *cu;
|
||||
|
||||
LIST_FOREACH (cu, chan->users) {
|
||||
int32 to_clear = cu->mode & modes; /* modes we need to clear */
|
||||
int32 flag = 1; /* mode we're clearing now */
|
||||
while (to_clear) {
|
||||
if (flag == MODE_INVALID) {
|
||||
log("BUG: hit invalid flag in clear_umodes!"
|
||||
" modes to clear = %08X, user modes = %08X",
|
||||
to_clear, cu->mode);
|
||||
break;
|
||||
}
|
||||
if (to_clear & flag) {
|
||||
char buf[3] = "-x";
|
||||
buf[1] = mode_flag_to_char(flag, MODE_CHANUSER);
|
||||
set_cmode(sender, chan, buf, cu->user->nick);
|
||||
to_clear &= ~flag;
|
||||
}
|
||||
flag <<= 1;
|
||||
}
|
||||
cu->mode &= ~modes;
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_users(const char *sender, Channel *chan, const char *reason)
|
||||
{
|
||||
char *av[3];
|
||||
struct c_userlist *cu, *next;
|
||||
|
||||
/* Prevent anyone from coming back in. The ban will disappear
|
||||
* once everyone's gone. */
|
||||
set_cmode(sender, chan, "+b", "*!*@*");
|
||||
set_cmode(NULL, chan); /* Flush modes out */
|
||||
|
||||
av[0] = chan->name;
|
||||
av[2] = (char *)reason;
|
||||
LIST_FOREACH_SAFE (cu, chan->users, next) {
|
||||
av[1] = cu->user->nick;
|
||||
send_channel_cmd(sender, "KICK %s %s :%s",
|
||||
av[0], av[1], av[2]);
|
||||
do_kick(sender, 3, av);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set the nickname to be used to send commands in clear_channel() calls.
|
||||
* If NULL, the server name is used; if PTR_INVALID, the name is not
|
||||
* changed. Returns the old value of the sender, or the empty string if no
|
||||
* nickname was set, in a static buffer.
|
||||
*/
|
||||
|
||||
const char *set_clear_channel_sender(const char *newsender)
|
||||
{
|
||||
static char oldsender[NICKMAX];
|
||||
strbcpy(oldsender, clear_channel_sender);
|
||||
if (newsender != PTR_INVALID) {
|
||||
if (newsender) {
|
||||
strbcpy(clear_channel_sender, newsender);
|
||||
} else {
|
||||
*clear_channel_sender = 0;
|
||||
}
|
||||
}
|
||||
return oldsender;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove a user from the IRC network. `source' is the nick which should
|
||||
* generate the kill, or NULL for a server-generated kill.
|
||||
*/
|
||||
|
||||
void kill_user(const char *source, const char *user, const char *reason)
|
||||
{
|
||||
char *av[2];
|
||||
char buf[BUFSIZE];
|
||||
|
||||
if (!user || !*user)
|
||||
return;
|
||||
if (!source || !*source)
|
||||
source = ServerName;
|
||||
if (!reason)
|
||||
reason = "";
|
||||
snprintf(buf, sizeof(buf), "%s (%s)", source, reason);
|
||||
av[0] = (char *)user;
|
||||
av[1] = buf;
|
||||
send_cmd(source, "KILL %s :%s", user, av[1]);
|
||||
do_kill(source, 2, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set the topic on a channel. `setter' must not be NULL. `source' is the
|
||||
* nick to use to send the TOPIC message; if NULL, the server name is used.
|
||||
*/
|
||||
|
||||
void set_topic(const char *source, Channel *c, const char *topic,
|
||||
const char *setter, time_t t)
|
||||
{
|
||||
if (!source)
|
||||
source = ServerName;
|
||||
call_callback_5(cb_set_topic, source, c, topic, setter, t);
|
||||
free(c->topic);
|
||||
if (topic && *topic)
|
||||
c->topic = sstrdup(topic);
|
||||
else
|
||||
c->topic = NULL;
|
||||
strbcpy(c->topic_setter, setter);
|
||||
if (call_callback_5(cb_set_topic, source, c, NULL, NULL, t) > 0)
|
||||
return;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* set_cmode(): Set modes for a channel and send those modes to remote
|
||||
* servers. Using this routine eliminates the necessity to modify the
|
||||
* internal Channel structure and send the command out separately, and also
|
||||
* allows the modes for a channel to be collected up over several calls and
|
||||
* sent out in a single command, decreasing network traffic (and scroll).
|
||||
* This function should be called as either:
|
||||
* set_cmode(sender, channel, modes, param1, param2...)
|
||||
* to send one or more channel modes, or
|
||||
* set_cmode(NULL, channel)
|
||||
* to flush buffered modes for a channel (if `channel' is NULL, flushes
|
||||
* buffered modes for all channels).
|
||||
*
|
||||
* NOTE: When setting modes with parameters, all parameters MUST be
|
||||
* strings. Numeric parameters must be converted to strings (with
|
||||
* snprintf() or the like) before being passed.
|
||||
*/
|
||||
|
||||
#define MAXMODES 6 /* Maximum number of mode parameters */
|
||||
|
||||
#define MAXPARAMSLEN \
|
||||
(510- NICKMAX - 6 - CHANMAX -(3+31+2*MAXMODES)-MAXMODES)
|
||||
/* |:sender| | MODE | |#channel| | -...+...| { param}*
|
||||
* Note that "-...+..." can contain at most 31 (binary) plus 2*MAXMODES
|
||||
* (MAXMODES modes with parameters plus +/-) characters. */
|
||||
|
||||
static struct modedata {
|
||||
time_t used;
|
||||
Channel *channel;
|
||||
char sender[NICKMAX];
|
||||
int32 binmodes_on;
|
||||
int32 binmodes_off;
|
||||
char opmodes[MAXMODES*2+1];
|
||||
char params[MAXMODES][MAXPARAMSLEN+1];
|
||||
int nopmodes, nparams, paramslen;
|
||||
Timeout *timeout; /* For timely flushing */
|
||||
} modedata[MERGE_CHANMODES_MAX];
|
||||
|
||||
static void possibly_remove_mode(struct modedata *md, char mode,
|
||||
const char *user);
|
||||
static void add_mode_with_params(struct modedata *md, char mode, int is_add,
|
||||
int params, const char *parambuf, int len);
|
||||
static void flush_cmode(struct modedata *md);
|
||||
static void flush_cmode_callback(Timeout *t);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void set_cmode(const char *sender, Channel *channel, ...)
|
||||
{
|
||||
va_list args;
|
||||
const char *modes, *modes_orig;
|
||||
struct modedata *md;
|
||||
int which = -1, add;
|
||||
int i;
|
||||
char c;
|
||||
|
||||
|
||||
/* If `sender' is NULL, flush out pending modes for the channel (for
|
||||
* all channels if `channel' is also NULL) and return. */
|
||||
if (!sender) {
|
||||
for (i = 0; i < MERGE_CHANMODES_MAX; i++) {
|
||||
if (modedata[i].used && (!channel || modedata[i].channel==channel))
|
||||
flush_cmode(&modedata[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the mode string from the argument list; save the original value
|
||||
* for error messages. */
|
||||
va_start(args, channel);
|
||||
modes = modes_orig = va_arg(args, const char *);
|
||||
|
||||
/* See if we already have pending modes for the channel; if so, reuse
|
||||
* that entry (if the entry is for a different sender, flush out the
|
||||
* pending modes first). */
|
||||
for (i = 0; i < MERGE_CHANMODES_MAX; i++) {
|
||||
if (modedata[i].used != 0 && modedata[i].channel == channel) {
|
||||
if (irc_stricmp(modedata[i].sender, sender) != 0)
|
||||
flush_cmode(&modedata[i]);
|
||||
which = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* If there are no pending modes for the channel, look for an empty
|
||||
* slot in the array. */
|
||||
if (which < 0) {
|
||||
for (i = 0; i < MERGE_CHANMODES_MAX; i++) {
|
||||
if (modedata[i].used == 0) {
|
||||
which = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* If no slots are free, we'll have to purge one. Find the oldest,
|
||||
* send its modes out, then clear and reuse it. */
|
||||
if (which < 0) {
|
||||
int oldest = 0;
|
||||
time_t oldest_time = modedata[0].used;
|
||||
for (i = 1; i < MERGE_CHANMODES_MAX; i++) {
|
||||
if (modedata[i].used < oldest_time) {
|
||||
oldest_time = modedata[i].used;
|
||||
oldest = i;
|
||||
}
|
||||
}
|
||||
flush_cmode(&modedata[oldest]);
|
||||
which = oldest;
|
||||
}
|
||||
|
||||
/* Save a pointer to the entry, then set up sender and channel. */
|
||||
md = &modedata[which];
|
||||
strbcpy(md->sender, sender);
|
||||
md->channel = channel;
|
||||
|
||||
/* Loop through and process all modes in the mode string. */
|
||||
add = -2; /* -2 means we haven't warned about a missing leading +/- yet */
|
||||
while ((c = *modes++) != 0) {
|
||||
int32 flag;
|
||||
int params, is_chanuser;
|
||||
|
||||
log_debug(2, "set_cmode(%s,%s): char=%c(%02X)",
|
||||
sender, channel->name, c<0x20||c>0x7E ? '.' : c, c);
|
||||
|
||||
/* + and - are handled specially. */
|
||||
if (c == '+') {
|
||||
add = 1;
|
||||
continue;
|
||||
} else if (c == '-') {
|
||||
add = 0;
|
||||
continue;
|
||||
}
|
||||
/* If we see any other character without first seeing a + or -,
|
||||
* note a bug in the logfile and move along. */
|
||||
if (add < 0) {
|
||||
if (add == -2) {
|
||||
log("set_cmode(): BUG: mode string `%s' needs leading +/-",
|
||||
modes_orig);
|
||||
add = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Find the flag value and parameter count for the character. */
|
||||
is_chanuser = 0;
|
||||
flag = mode_char_to_flag(c, MODE_CHANNEL);
|
||||
params = mode_char_to_params(c, MODE_CHANNEL);
|
||||
if (!flag) {
|
||||
is_chanuser = 1;
|
||||
flag = mode_char_to_flag(c, MODE_CHANUSER);
|
||||
params = mode_char_to_params(c, MODE_CHANUSER);
|
||||
if (!flag) {
|
||||
log("set_cmode: bad mode '%c'", c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
params = (params >> (add*8)) & 0xFF;
|
||||
|
||||
if (params) { /* Mode with parameters */
|
||||
char parambuf[BUFSIZE]; /* for putting the parameters in */
|
||||
int len = 0;
|
||||
|
||||
if (params > MAXMODES) {
|
||||
/* Sanity check */
|
||||
fatal("set_cmode(): too many parameters (%d) for mode `%c'\n",
|
||||
params, c);
|
||||
}
|
||||
/* Merge all the parameters into a single string (with no
|
||||
* leading whitespace) */
|
||||
for (i = 0; i < params; i++) {
|
||||
const char *s = va_arg(args, const char *);
|
||||
log_debug(2, "set_cmode(%s,%s): param=%s",
|
||||
sender, channel->name, s);
|
||||
len += snprintf(parambuf+len, sizeof(parambuf)-len,
|
||||
"%s%s", len ? " " : "", s);
|
||||
}
|
||||
if (flag != MODE_INVALID) {
|
||||
/* If it's a binary mode, see if we've set this mode before.
|
||||
* If so (and if the nick is the same for channel user
|
||||
* modes), remove it; the new one will be appended
|
||||
* afterwards. Note that this assumes that setting each
|
||||
* mode is independent, i.e. that -a+ba 2 1 has the same
|
||||
* effect as +ba 2 1 by itself when +a is set. This doesn't
|
||||
* work for +k/-k, so we let multiple such modes remain. */
|
||||
if (c != 'k') {
|
||||
possibly_remove_mode(md, c, is_chanuser ? parambuf : NULL);
|
||||
}
|
||||
}
|
||||
add_mode_with_params(md, c, add, params, parambuf, len);
|
||||
} else { /* Binary mode */
|
||||
/* Note that `flag' should already be set to this value, since
|
||||
* all channel user modes take parameters and thus will never
|
||||
* get here, but just in case... */
|
||||
flag = mode_char_to_flag(c, MODE_CHANNEL);
|
||||
if (add) {
|
||||
md->binmodes_on |= flag;
|
||||
md->binmodes_off &= ~flag;
|
||||
} else {
|
||||
md->binmodes_off |= flag;
|
||||
md->binmodes_on &= ~flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
md->used = time(NULL);
|
||||
|
||||
if (MergeChannelModes) {
|
||||
if (!md->timeout) {
|
||||
md->timeout = add_timeout_ms(MergeChannelModes,
|
||||
flush_cmode_callback, 0);
|
||||
md->timeout->data = md;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove the most recent occurrence of mode `mode' from the mode list if
|
||||
* there is one, provided either `user' is NULL or the parameter associated
|
||||
* with the previous mode is equal (according to irc_stricmp()) to the
|
||||
* string pointed to by `user'.
|
||||
*/
|
||||
|
||||
static void possibly_remove_mode(struct modedata *md, char mode,
|
||||
const char *user)
|
||||
{
|
||||
int i;
|
||||
char *s;
|
||||
|
||||
log_debug(2, "possibly_remove_mode %c from %.*s%s%s",
|
||||
mode, md->nopmodes*2, md->opmodes,
|
||||
user ? " for user " : "", user ? user : "");
|
||||
for (i = md->nopmodes-1; i >= 0; i--) {
|
||||
if (md->opmodes[i*2+1] == mode) {
|
||||
/* We've already set this mode once */
|
||||
if (user) {
|
||||
/* Only remove the old mode if the nick matches */
|
||||
if (irc_stricmp(md->params[i], user) != 0)
|
||||
continue;
|
||||
}
|
||||
/* Remove the mode */
|
||||
log_debug(2, " removing mode %d/%d", i, md->nopmodes);
|
||||
md->nopmodes--;
|
||||
s = md->opmodes + (i*2);
|
||||
memmove(s, s+2, strlen(s+2)+1);
|
||||
/* Count parameters for this mode and decrement total by count */
|
||||
md->nparams--;
|
||||
s = md->params[i]-1;
|
||||
while ((s = strchr(s+1, ' ')) != NULL)
|
||||
md->nparams--;
|
||||
/* Move parameter pointers */
|
||||
if (i < md->nopmodes) {
|
||||
memmove(md->params+i, md->params+i+1,
|
||||
sizeof(md->params[0])*(md->nopmodes-i));
|
||||
}
|
||||
/* Clear tail slot */
|
||||
memset(md->params+md->nopmodes, 0, sizeof(md->params[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Add a single mode with parameters to the given mode data structure.
|
||||
* `params' is the number of parameters, `parambuf' is the space-separated
|
||||
* parameter list, and `len' is strlen(parambuf).
|
||||
*/
|
||||
|
||||
static void add_mode_with_params(struct modedata *md, char mode, int is_add,
|
||||
int params, const char *parambuf, int len)
|
||||
{
|
||||
char *s;
|
||||
|
||||
if (len < 0) {
|
||||
log("add_mode_with_params(): BUG: parameter length < 0 (%d)", len);
|
||||
len = 0;
|
||||
}
|
||||
log_debug(2, "add_mode_with_params: current=%.*s mode=%c add=%d"
|
||||
" params=%d[%.*s]", md->nopmodes*2, md->opmodes, mode, is_add,
|
||||
params, len, parambuf);
|
||||
|
||||
/* Check for overflow of parameter count or length */
|
||||
if (md->nparams+params > MAXMODES
|
||||
|| md->paramslen+1+len > MAXPARAMSLEN
|
||||
) {
|
||||
/* Doesn't fit, so flush modes out first */
|
||||
struct modedata mdtmp = *md;
|
||||
log_debug(2, "add_mode_with_params: ...flushing first");
|
||||
flush_cmode(md);
|
||||
memcpy(md->sender, mdtmp.sender, sizeof(md->sender));
|
||||
md->channel = mdtmp.channel;
|
||||
md->used = time(NULL);
|
||||
}
|
||||
s = md->opmodes + 2*md->nopmodes;
|
||||
*s++ = is_add ? '+' : '-';
|
||||
*s++ = mode;
|
||||
if (len > sizeof(md->params[0])-1) {
|
||||
log("set_cmode(): Parameter string for mode %c%c is too long,"
|
||||
" truncating to %d characters",
|
||||
is_add ? '+' : '-', mode, sizeof(md->params[0])-1);
|
||||
len = sizeof(md->params[0])-1;
|
||||
}
|
||||
if (len > 0)
|
||||
memcpy(md->params[md->nopmodes], parambuf, len);
|
||||
md->params[md->nopmodes][len] = 0;
|
||||
md->nopmodes++;
|
||||
md->nparams += params;
|
||||
if (md->paramslen)
|
||||
md->paramslen++;
|
||||
md->paramslen += len;
|
||||
/* If the parameters for this mode alone exceed MAXPARAMSLEN,
|
||||
* we'll now have a string longer than MAXPARAMSLEN in
|
||||
* md->params; not much we can do about it, though, and it'll
|
||||
* get flushed next time around anyway. */
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Flush out pending mode changes for the given mode data structure. If
|
||||
* `clear' is nonzero, clear the entry, else leave it alone.
|
||||
*/
|
||||
|
||||
static void flush_cmode(struct modedata *md)
|
||||
{
|
||||
char buf[BUFSIZE], *s;
|
||||
char *argv[MAXMODES+2];
|
||||
int len = 0, i;
|
||||
char lastc = 0;
|
||||
|
||||
/* Clear timeout for this entry if one is set */
|
||||
if (md->timeout) {
|
||||
del_timeout(md->timeout);
|
||||
md->timeout = NULL;
|
||||
}
|
||||
|
||||
if (!md->channel) {
|
||||
/* This entry is unused, just return */
|
||||
goto done;
|
||||
}
|
||||
if (!md->binmodes_on && !md->binmodes_off && !*md->opmodes) {
|
||||
/* No actual modes here */
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (debug >= 2) {
|
||||
char onbuf[512];
|
||||
strbcpy(onbuf, mode_flags_to_string(md->binmodes_on,MODE_CHANNEL));
|
||||
log_debug(2, "flush_cmode(%s): bin_on=%s bin_off=%s opmodes=%d(%.*s)",
|
||||
md->channel->name, onbuf,
|
||||
mode_flags_to_string(md->binmodes_off, MODE_CHANNEL),
|
||||
md->nopmodes, md->nopmodes*2, md->opmodes);
|
||||
}
|
||||
|
||||
/* Note that - must come before + because some servers (Unreal, others?)
|
||||
* ignore +s if followed by -p. */
|
||||
if (md->binmodes_off) {
|
||||
len += snprintf(buf+len, sizeof(buf)-len, "-%s",
|
||||
mode_flags_to_string(md->binmodes_off, MODE_CHANNEL));
|
||||
lastc = '-';
|
||||
}
|
||||
if (md->binmodes_on) {
|
||||
len += snprintf(buf+len, sizeof(buf)-len, "+%s",
|
||||
mode_flags_to_string(md->binmodes_on, MODE_CHANNEL));
|
||||
lastc = '+';
|
||||
}
|
||||
s = md->opmodes;
|
||||
while (*s) {
|
||||
if (*s == lastc) {
|
||||
/* +/- matches last mode change */
|
||||
s++;
|
||||
} else {
|
||||
if (len < sizeof(buf)-1)
|
||||
buf[len++] = *s;
|
||||
else
|
||||
fatal("BUG: buf too small in flush_cmode() (1)");
|
||||
lastc = *s++;
|
||||
}
|
||||
if (len < sizeof(buf)-1) {
|
||||
buf[len++] = *s;
|
||||
buf[len] = 0;
|
||||
} else {
|
||||
fatal("BUG: buf too small in flush_cmode() (2)");
|
||||
}
|
||||
s++;
|
||||
}
|
||||
for (i = 0; i < md->nopmodes; i++) {
|
||||
if (*md->params[i])
|
||||
len += snprintf(buf+len, sizeof(buf)-len, " %s", md->params[i]);
|
||||
}
|
||||
|
||||
/* Actually send the command */
|
||||
send_cmode_cmd(md->sender, md->channel->name, "%s", buf);
|
||||
|
||||
/* Split buffer back up into individual parameters for do_cmode().
|
||||
* This is inefficient, but taking the faster route of setting modes
|
||||
* when they are sent to set_cmode() runs the risk of temporary desyncs.
|
||||
* (Example: SomeNick enters #channel -> autoop, but delayed -> SomeNick
|
||||
* does /cs op SomeNick -> ChanServ says "SomeNick is already opped" ->
|
||||
* SomeNick goes "Huh?")
|
||||
*/
|
||||
argv[0] = md->channel->name;
|
||||
s = buf;
|
||||
for (i = 0; i <= md->nparams; i++) {
|
||||
argv[i+1] = s;
|
||||
s = strchr(s, ' ');
|
||||
if (!s) {
|
||||
md->nparams = i;
|
||||
break;
|
||||
}
|
||||
*s++ = 0;
|
||||
}
|
||||
/* Clear md->channel so a recursive set_cmode() doesn't find this entry
|
||||
* and try to use/flush it */
|
||||
md->channel = NULL;
|
||||
/* Adjust our idea of the channel modes */
|
||||
do_cmode(md->sender, md->nparams+2, argv);
|
||||
|
||||
done:
|
||||
/* Clear entry and return */
|
||||
memset(md, 0, sizeof(*md));
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Timeout called to flush mode changes for a channel after
|
||||
* `MergeChannelModes' seconds of inactivity.
|
||||
*/
|
||||
|
||||
static void flush_cmode_callback(Timeout *t)
|
||||
{
|
||||
flush_cmode((struct modedata *)t->data);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
500
channels.c
Normal file
500
channels.c
Normal file
@ -0,0 +1,500 @@
|
||||
/* Channel-handling routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "modules.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#define HASHFUNC(key) DEFAULT_HASHFUNC(key+1)
|
||||
#define add_channel static add_channel
|
||||
#define del_channel static del_channel
|
||||
#include "hash.h"
|
||||
DEFINE_HASH(channel, Channel, name)
|
||||
#undef add_channel
|
||||
#undef del_channel
|
||||
|
||||
static int cb_create = -1;
|
||||
static int cb_delete = -1;
|
||||
static int cb_join = -1;
|
||||
static int cb_join_check = -1;
|
||||
static int cb_mode = -1;
|
||||
static int cb_mode_change = -1;
|
||||
static int cb_umode_change = -1;
|
||||
static int cb_topic = -1;
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
int channel_init(int ac, char **av)
|
||||
{
|
||||
cb_create = register_callback("channel create");
|
||||
cb_delete = register_callback("channel delete");
|
||||
cb_join = register_callback("channel JOIN");
|
||||
cb_join_check = register_callback("channel JOIN check");
|
||||
cb_mode = register_callback("channel MODE");
|
||||
cb_mode_change = register_callback("channel mode change");
|
||||
cb_umode_change = register_callback("channel umode change");
|
||||
cb_topic = register_callback("channel TOPIC");
|
||||
if (cb_create < 0 || cb_delete < 0 || cb_join < 0 || cb_join_check < 0
|
||||
|| cb_mode < 0 || cb_mode_change < 0 || cb_umode_change < 0
|
||||
|| cb_topic < 0
|
||||
) {
|
||||
log("channel_init: register_callback() failed\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void channel_cleanup(void)
|
||||
{
|
||||
Channel *c;
|
||||
|
||||
for (c = first_channel(); c; c = next_channel())
|
||||
del_channel(c);
|
||||
unregister_callback(cb_topic);
|
||||
unregister_callback(cb_umode_change);
|
||||
unregister_callback(cb_mode_change);
|
||||
unregister_callback(cb_mode);
|
||||
unregister_callback(cb_join_check);
|
||||
unregister_callback(cb_join);
|
||||
unregister_callback(cb_delete);
|
||||
unregister_callback(cb_create);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return statistics. Pointers are assumed to be valid. */
|
||||
|
||||
void get_channel_stats(long *nrec, long *memuse)
|
||||
{
|
||||
long count = 0, mem = 0;
|
||||
Channel *chan;
|
||||
struct c_userlist *cu;
|
||||
int i;
|
||||
|
||||
for (chan = first_channel(); chan; chan = next_channel()) {
|
||||
count++;
|
||||
mem += sizeof(*chan);
|
||||
if (chan->topic)
|
||||
mem += strlen(chan->topic)+1;
|
||||
if (chan->key)
|
||||
mem += strlen(chan->key)+1;
|
||||
ARRAY_FOREACH (i, chan->bans) {
|
||||
mem += sizeof(char *);
|
||||
if (chan->bans[i])
|
||||
mem += strlen(chan->bans[i])+1;
|
||||
}
|
||||
ARRAY_FOREACH (i, chan->excepts) {
|
||||
mem += sizeof(char *);
|
||||
if (chan->excepts[i])
|
||||
mem += strlen(chan->excepts[i])+1;
|
||||
}
|
||||
LIST_FOREACH (cu, chan->users)
|
||||
mem += sizeof(*cu);
|
||||
}
|
||||
*nrec = count;
|
||||
*memuse = mem;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Add/remove a user to/from a channel, creating or deleting the channel as
|
||||
* necessary. If creating the channel, restore mode lock and topic as
|
||||
* necessary. Also check for auto-opping and auto-voicing. If a mode is
|
||||
* given, it is assumed to have been set by the remote server.
|
||||
* Returns the Channel structure for the given channel, or NULL if the user
|
||||
* was refused access to the channel (by the join check callback).
|
||||
*/
|
||||
|
||||
Channel *chan_adduser(User *user, const char *chan, int32 modes)
|
||||
{
|
||||
Channel *c = get_channel(chan);
|
||||
int newchan = !c;
|
||||
struct c_userlist *u;
|
||||
|
||||
if (call_callback_2(cb_join_check, chan, user) > 0)
|
||||
return NULL;
|
||||
if (newchan) {
|
||||
log_debug(1, "Creating channel %s", chan);
|
||||
/* Allocate pre-cleared memory */
|
||||
c = scalloc(sizeof(Channel), 1);
|
||||
strbcpy(c->name, chan);
|
||||
c->creation_time = time(NULL);
|
||||
add_channel(c);
|
||||
call_callback_3(cb_create, c, user, modes);
|
||||
}
|
||||
u = smalloc(sizeof(struct c_userlist));
|
||||
LIST_INSERT(u, c->users);
|
||||
u->user = user;
|
||||
u->mode = modes;
|
||||
u->flags = 0;
|
||||
call_callback_2(cb_join, c, u);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
void chan_deluser(User *user, Channel *c)
|
||||
{
|
||||
struct c_userlist *u;
|
||||
int i;
|
||||
|
||||
LIST_SEARCH_SCALAR(c->users, user, user, u);
|
||||
if (!u) {
|
||||
log("channel: BUG: chan_deluser() called for %s in %s but they "
|
||||
"were not found on the channel's userlist.",
|
||||
user->nick, c->name);
|
||||
return;
|
||||
}
|
||||
LIST_REMOVE(u, c->users);
|
||||
free(u);
|
||||
|
||||
if (!c->users) {
|
||||
log_debug(1, "Deleting channel %s", c->name);
|
||||
call_callback_1(cb_delete, c);
|
||||
set_cmode(NULL, c); /* make sure nothing's left buffered */
|
||||
free(c->topic);
|
||||
free(c->key);
|
||||
free(c->link);
|
||||
free(c->flood);
|
||||
for (i = 0; i < c->bans_count; i++)
|
||||
free(c->bans[i]);
|
||||
free(c->bans);
|
||||
for (i = 0; i < c->excepts_count; i++)
|
||||
free(c->excepts[i]);
|
||||
free(c->excepts);
|
||||
del_channel(c);
|
||||
free(c);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search for the given ban on the given channel, and return the index into
|
||||
* chan->bans[] if found, -1 otherwise. Nicknames are compared using
|
||||
* irc_stricmp(), usernames and hostnames using stricmp().
|
||||
*/
|
||||
static int find_ban(const Channel *chan, const char *ban)
|
||||
{
|
||||
char *s, *t;
|
||||
int i;
|
||||
|
||||
t = strchr(ban, '!');
|
||||
i = 0;
|
||||
ARRAY_FOREACH (i, chan->bans) {
|
||||
s = strchr(chan->bans[i], '!');
|
||||
if (s && t) {
|
||||
if (s-(chan->bans[i]) == t-ban
|
||||
&& irc_strnicmp(chan->bans[i], ban, s-(chan->bans[i])) == 0
|
||||
&& stricmp(s+1, t+1) == 0
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
} else if (!s && !t) {
|
||||
/* Bans without '!' should be impossible; just
|
||||
* do a case-insensitive compare */
|
||||
if (stricmp(chan->bans[i], ban) == 0)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search for the given ban (case-insensitive) on the channel; return 1 if
|
||||
* it exists, 0 if not.
|
||||
*/
|
||||
|
||||
int chan_has_ban(const char *chan, const char *ban)
|
||||
{
|
||||
Channel *c = get_channel(chan);
|
||||
if (!c)
|
||||
return 0;
|
||||
return find_ban(c, ban) >= 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a channel MODE command.
|
||||
* When called internally to modify channel modes, callers may assume that
|
||||
* the contents of the argument strings will not be modified.
|
||||
*/
|
||||
|
||||
/* Hack to allow -o+v to work without having to search the whole channel
|
||||
* user list for changed modes. */
|
||||
#define MAX_CUMODES 16
|
||||
static struct {
|
||||
struct c_userlist *user;
|
||||
int32 add, remove;
|
||||
} cumode_changes[MAX_CUMODES];
|
||||
static int cumode_count = 0;
|
||||
|
||||
static void do_cumode(const char *source, Channel *chan, int32 flag, int add,
|
||||
const char *nick);
|
||||
static void finish_cumode(const char *source, Channel *chan);
|
||||
|
||||
void do_cmode(const char *source, int ac, char **av)
|
||||
{
|
||||
Channel *chan;
|
||||
char *s;
|
||||
int add = 1; /* 1 if adding modes, 0 if deleting */
|
||||
char *modestr;
|
||||
|
||||
chan = get_channel(av[0]);
|
||||
if (!chan) {
|
||||
log_debug(1, "channel: MODE %s for nonexistent channel %s",
|
||||
merge_args(ac-1, av+1), av[0]);
|
||||
return;
|
||||
}
|
||||
if ((protocol_features & PF_MODETS_FIRST) && isdigit(av[1][0])) {
|
||||
ac--;
|
||||
av++;
|
||||
}
|
||||
modestr = av[1];
|
||||
|
||||
if (!NoBouncyModes) {
|
||||
/* Count identical server mode changes per second (mode bounce check)*/
|
||||
/* Doesn't trigger on +/-[bov] or other multiply-settable modes */
|
||||
static char multimodes[BUFSIZE];
|
||||
if (!*multimodes) {
|
||||
int i = 0;
|
||||
i = snprintf(multimodes, sizeof(multimodes), "%s",
|
||||
chanmode_multiple);
|
||||
snprintf(multimodes+i, sizeof(multimodes)-i, "%s",
|
||||
mode_flags_to_string(MODE_ALL,MODE_CHANUSER));
|
||||
}
|
||||
if (strchr(source, '.') && strcmp(source, ServerName) != 0
|
||||
&& !modestr[strcspn(modestr, multimodes)]
|
||||
) {
|
||||
static char lastmodes[BUFSIZE];
|
||||
if (time(NULL) != chan->server_modetime
|
||||
|| strcmp(modestr, lastmodes) != 0
|
||||
) {
|
||||
chan->server_modecount = 0;
|
||||
chan->server_modetime = time(NULL);
|
||||
strbcpy(lastmodes, modestr);
|
||||
}
|
||||
chan->server_modecount++;
|
||||
}
|
||||
}
|
||||
|
||||
s = modestr;
|
||||
ac -= 2;
|
||||
av += 2;
|
||||
cumode_count = 0;
|
||||
|
||||
while (*s) {
|
||||
char modechar = *s++;
|
||||
int32 flag;
|
||||
int params;
|
||||
|
||||
if (modechar == '+') {
|
||||
add = 1;
|
||||
continue;
|
||||
} else if (modechar == '-') {
|
||||
add = 0;
|
||||
continue;
|
||||
} else if (add < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check for it as a channel user mode */
|
||||
|
||||
flag = mode_char_to_flag(modechar, MODE_CHANUSER);
|
||||
if (flag) {
|
||||
if (--ac < 0) {
|
||||
log("channel: MODE %s %s: missing parameter for %c%c",
|
||||
chan->name, modestr, add ? '+' : '-', modechar);
|
||||
break;
|
||||
}
|
||||
do_cumode(source, chan, flag, add, *av++);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Nope, must be a regular channel mode */
|
||||
|
||||
flag = mode_char_to_flag(modechar, MODE_CHANNEL);
|
||||
if (!flag)
|
||||
continue;
|
||||
if (flag == MODE_INVALID)
|
||||
flag = 0;
|
||||
params = mode_char_to_params(modechar, MODE_CHANNEL);
|
||||
params = (params >> (add*8)) & 0xFF;
|
||||
if (ac < params) {
|
||||
log("channel: MODE %s %s: missing parameter(s) for %c%c",
|
||||
chan->name, modestr, add ? '+' : '-', modechar);
|
||||
break;
|
||||
}
|
||||
|
||||
if (call_callback_5(cb_mode, source, chan, modechar, add, av) <= 0) {
|
||||
|
||||
if (add)
|
||||
chan->mode |= flag;
|
||||
else
|
||||
chan->mode &= ~flag;
|
||||
|
||||
switch (modechar) {
|
||||
case 'k':
|
||||
free(chan->key);
|
||||
if (add)
|
||||
chan->key = sstrdup(av[0]);
|
||||
else
|
||||
chan->key = NULL;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (add)
|
||||
chan->limit = atoi(av[0]);
|
||||
else
|
||||
chan->limit = 0;
|
||||
break;
|
||||
|
||||
case 'b': {
|
||||
int i = find_ban(chan, av[0]);
|
||||
if (add) {
|
||||
if (i < 0) { /* Don't add if it already exists */
|
||||
ARRAY_EXTEND(chan->bans);
|
||||
chan->bans[chan->bans_count-1] = sstrdup(av[0]);
|
||||
}
|
||||
} else {
|
||||
if (i >= 0) {
|
||||
free(chan->bans[i]);
|
||||
ARRAY_REMOVE(chan->bans, i);
|
||||
} else {
|
||||
log("channel: MODE %s -b %s: ban not found",
|
||||
chan->name, *av);
|
||||
}
|
||||
}
|
||||
break;
|
||||
} /* case 'b' */
|
||||
|
||||
} /* switch (modechar) */
|
||||
|
||||
} /* if (callback() <= 0) */
|
||||
|
||||
ac -= params;
|
||||
av += params;
|
||||
|
||||
} /* while (*s) */
|
||||
|
||||
call_callback_2(cb_mode_change, source, chan);
|
||||
finish_cumode(source, chan);
|
||||
}
|
||||
|
||||
/* Modify a user's CUMODE. */
|
||||
static void do_cumode(const char *source, Channel *chan, int32 flag, int add,
|
||||
const char *nick)
|
||||
{
|
||||
struct c_userlist *u;
|
||||
User *user;
|
||||
int i;
|
||||
|
||||
user = get_user(nick);
|
||||
if (!user) {
|
||||
log_debug(1, "channel: MODE %s %c%c for nonexistent user %s",
|
||||
chan->name, add ? '+' : '-',
|
||||
mode_flag_to_char(flag, MODE_CHANUSER), nick);
|
||||
return;
|
||||
}
|
||||
LIST_SEARCH_SCALAR(chan->users, user, user, u);
|
||||
if (!u) {
|
||||
log("channel: MODE %s %c%c for user %s not on channel",
|
||||
chan->name, add ? '+' : '-',
|
||||
mode_flag_to_char(flag, MODE_CHANUSER), nick);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flag == MODE_INVALID)
|
||||
return;
|
||||
for (i = 0; i < cumode_count; i++) {
|
||||
if (cumode_changes[i].user == u)
|
||||
break;
|
||||
}
|
||||
if (i >= MAX_CUMODES) {
|
||||
finish_cumode(source, chan);
|
||||
i = cumode_count = 0;
|
||||
}
|
||||
cumode_changes[i].user = u;
|
||||
if (i >= cumode_count) {
|
||||
cumode_changes[i].add = cumode_changes[i].remove = 0;
|
||||
}
|
||||
if (add) {
|
||||
cumode_changes[i].add |= flag;
|
||||
cumode_changes[i].remove &= ~flag;
|
||||
} else {
|
||||
cumode_changes[i].remove |= flag;
|
||||
cumode_changes[i].add &= ~flag;
|
||||
}
|
||||
if (i >= cumode_count)
|
||||
cumode_count = i+1;
|
||||
}
|
||||
|
||||
static void finish_cumode(const char *source, Channel *chan)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < cumode_count; i++) {
|
||||
struct c_userlist *u = cumode_changes[i].user;
|
||||
int32 oldmode = u->mode;
|
||||
u->mode |= cumode_changes[i].add;
|
||||
u->mode &= ~cumode_changes[i].remove;
|
||||
if (u->mode != oldmode)
|
||||
call_callback_4(cb_umode_change, source, chan, u, oldmode);
|
||||
}
|
||||
cumode_count = 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a TOPIC command. */
|
||||
|
||||
void do_topic(const char *source, int ac, char **av)
|
||||
{
|
||||
Channel *c = get_channel(av[0]);
|
||||
const char *topic;
|
||||
char *s;
|
||||
|
||||
if (!c) {
|
||||
log_debug(1, "channel: TOPIC %s for nonexistent channel %s",
|
||||
merge_args(ac-1, av+1), av[0]);
|
||||
return;
|
||||
}
|
||||
s = strchr(av[1], '!');
|
||||
if (s)
|
||||
*s = 0;
|
||||
if (ac > 3)
|
||||
topic = av[3];
|
||||
else
|
||||
topic = "";
|
||||
if (call_callback_4(cb_topic, c, topic, av[1], strtotime(av[2],NULL)) > 0)
|
||||
return;
|
||||
strbcpy(c->topic_setter, av[1]);
|
||||
if (c->topic) {
|
||||
free(c->topic);
|
||||
c->topic = NULL;
|
||||
}
|
||||
if (ac > 3 && *av[3])
|
||||
c->topic = sstrdup(av[3]);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
70
channels.h
Normal file
70
channels.h
Normal file
@ -0,0 +1,70 @@
|
||||
/* Online channel data structure.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef CHANNELS_H
|
||||
#define CHANNELS_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
struct channel_ {
|
||||
Channel *next, *prev;
|
||||
|
||||
char name[CHANMAX];
|
||||
ChannelInfo *ci; /* Corresponding ChannelInfo */
|
||||
time_t creation_time; /* When channel was created */
|
||||
|
||||
char *topic;
|
||||
char topic_setter[NICKMAX]; /* Who set the topic */
|
||||
time_t topic_time; /* When topic was set */
|
||||
|
||||
int32 mode; /* CMODE_* (binary) channel modes */
|
||||
int32 limit; /* 0 if none */
|
||||
char *key; /* NULL if none */
|
||||
char *link; /* +L (Unreal, trircd) */
|
||||
char *flood; /* +f (Unreal, etc.) */
|
||||
int32 joindelay; /* +J (trircd) */
|
||||
int32 joinrate1, joinrate2; /* +j (Bahamut) */
|
||||
|
||||
char **bans;
|
||||
int32 bans_count;
|
||||
char **excepts;
|
||||
int32 excepts_count;
|
||||
char **invites;
|
||||
int32 invites_count;
|
||||
|
||||
struct c_userlist {
|
||||
struct c_userlist *next, *prev;
|
||||
User *user;
|
||||
int32 mode; /* CUMODE_* modes (chanop, voice) */
|
||||
int16 flags; /* CUFLAG_* flags (below) */
|
||||
} *users;
|
||||
|
||||
time_t server_modetime; /* Time of last server MODE */
|
||||
time_t chanserv_modetime; /* Time of last check_modes() */
|
||||
int16 server_modecount; /* Number of server MODEs this second*/
|
||||
int16 chanserv_modecount; /* Number of check_mode()'s this sec */
|
||||
int16 bouncy_modes; /* Did we fail to set modes here? */
|
||||
};
|
||||
|
||||
/* Set by ChanServ if it deops a user on joining a channel */
|
||||
#define CUFLAG_DEOPPED 0x0001
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
242
commands.c
Normal file
242
commands.c
Normal file
@ -0,0 +1,242 @@
|
||||
/* Routines for looking up commands in a *Serv command list.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "modules.h"
|
||||
#include "commands.h"
|
||||
#include "language.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
typedef struct commandlist_ CommandList;
|
||||
typedef struct commandarray_ CommandArray;
|
||||
|
||||
struct commandlist_ {
|
||||
CommandList *next, *prev;
|
||||
Module *id;
|
||||
CommandArray *first_array;
|
||||
};
|
||||
|
||||
struct commandarray_ {
|
||||
CommandArray *next, *prev;
|
||||
Command *commands;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* List of all commands registered. */
|
||||
static CommandList *cmdlist;
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set up a new command list using the given module pointer as an ID value.
|
||||
* Fails if a command list associated with `id' already exists.
|
||||
*/
|
||||
|
||||
int new_commandlist(Module *id)
|
||||
{
|
||||
CommandList *cl;
|
||||
|
||||
LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
|
||||
if (cl)
|
||||
return 0;
|
||||
cl = smalloc(sizeof(*cl));
|
||||
cl->id = id;
|
||||
cl->first_array = NULL;
|
||||
LIST_INSERT(cl, cmdlist);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Register a command array under the given ID. Fails if there is no
|
||||
* command list associated with `id', `array' is NULL, `array' has already
|
||||
* been added to the list, or there are multiple command entries in `array'
|
||||
* with the same name (case-insensitive). If an entry in `array' has the
|
||||
* same name as a previously-registered entry, the entry in `array' will
|
||||
* take precendence, and a pointer to the previous entry will be stored in
|
||||
* the `next' field of the entry.
|
||||
*/
|
||||
|
||||
int register_commands(Module *id, Command *array)
|
||||
{
|
||||
CommandList *cl;
|
||||
CommandArray *ca;
|
||||
Command *c, *c2;
|
||||
|
||||
/* Basic sanity checking */
|
||||
if (!array)
|
||||
return 0;
|
||||
LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
|
||||
if (!cl)
|
||||
return 0;
|
||||
LIST_SEARCH_SCALAR(cl->first_array, commands, array, ca);
|
||||
if (ca)
|
||||
return 0;
|
||||
/* Check for duplicate commands and set `next' pointers */
|
||||
for (c = array; c->name; c++) {
|
||||
for (c2 = c+1; c2->name; c2++) {
|
||||
if (stricmp(c->name, c2->name) == 0)
|
||||
return 0;
|
||||
}
|
||||
c->next = lookup_cmd(id, c->name);
|
||||
}
|
||||
/* All's well, insert it in the list and return success */
|
||||
ca = smalloc(sizeof(*ca));
|
||||
ca->commands = array;
|
||||
LIST_INSERT(ca, cl->first_array);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Unregister a command array from the given ID. Fails if there is no
|
||||
* command list associated with `id' or `array' was not in the list in the
|
||||
* first place.
|
||||
*/
|
||||
|
||||
int unregister_commands(Module *id, Command *array)
|
||||
{
|
||||
CommandList *cl;
|
||||
CommandArray *ca;
|
||||
|
||||
LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
|
||||
if (!cl)
|
||||
return 0;
|
||||
LIST_SEARCH_SCALAR(cl->first_array, commands, array, ca);
|
||||
if (!ca)
|
||||
return 0;
|
||||
LIST_REMOVE(ca, cl->first_array);
|
||||
free(ca);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Delete the command list associated with the given ID. Fails if there is
|
||||
* no command list associated with `id' or the command list is not empty.
|
||||
*/
|
||||
|
||||
int del_commandlist(Module *id)
|
||||
{
|
||||
CommandList *cl;
|
||||
|
||||
LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
|
||||
if (!cl || cl->first_array)
|
||||
return 0;
|
||||
LIST_REMOVE(cl, cmdlist);
|
||||
free(cl);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Returns the Command structure associated with the given command for the
|
||||
* given command list (`id'), or NULL if no such command exists.
|
||||
*/
|
||||
|
||||
Command *lookup_cmd(Module *id, const char *name)
|
||||
{
|
||||
CommandList *cl;
|
||||
CommandArray *ca;
|
||||
Command *c;
|
||||
|
||||
LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
|
||||
if (!cl)
|
||||
return NULL;
|
||||
LIST_FOREACH (ca, cl->first_array) {
|
||||
for (c = ca->commands; c->name; c++) {
|
||||
if (stricmp(c->name, name) == 0)
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Run the routine for the given command, if it exists and the user has
|
||||
* privilege to do so; if not, print an appropriate error message.
|
||||
*/
|
||||
|
||||
void run_cmd(const char *service, User *u, Module *id, const char *cmd)
|
||||
{
|
||||
Command *c = lookup_cmd(id, cmd);
|
||||
|
||||
if (c && c->routine) {
|
||||
if ((c->has_priv == NULL) || c->has_priv(u))
|
||||
c->routine(u);
|
||||
else
|
||||
notice_lang(service, u, ACCESS_DENIED);
|
||||
} else {
|
||||
notice_lang(service, u, UNKNOWN_COMMAND_HELP, cmd, service);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Print a help message for the given command. Multiple spaces between
|
||||
* words are compressed to a single space before looking up the command
|
||||
* (this will cause `cmd' to be modified).
|
||||
*/
|
||||
|
||||
void help_cmd(const char *service, User *u, Module *id, char *cmd)
|
||||
{
|
||||
Command *c;
|
||||
char *s;
|
||||
|
||||
s = cmd-1;
|
||||
while ((s = strpbrk(s+1, " \t")) != NULL) {
|
||||
char *t = s + strspn(s, " \t");
|
||||
if (t > s+1)
|
||||
memmove(s+1, t, strlen(t)+1);
|
||||
}
|
||||
|
||||
c = lookup_cmd(id, cmd);
|
||||
if (c) {
|
||||
const char *p1 = c->help_param1,
|
||||
*p2 = c->help_param2,
|
||||
*p3 = c->help_param3,
|
||||
*p4 = c->help_param4;
|
||||
|
||||
if (c->helpmsg_all >= 0)
|
||||
notice_help(service, u, c->helpmsg_all, p1, p2, p3, p4);
|
||||
|
||||
if (is_oper(u)) {
|
||||
if (c->helpmsg_oper >= 0)
|
||||
notice_help(service, u, c->helpmsg_oper, p1, p2, p3, p4);
|
||||
else if (c->helpmsg_all < 0)
|
||||
notice_lang(service, u, NO_HELP_AVAILABLE, cmd);
|
||||
} else {
|
||||
if (c->helpmsg_reg >= 0)
|
||||
notice_help(service, u, c->helpmsg_reg, p1, p2, p3, p4);
|
||||
else if (c->helpmsg_all < 0)
|
||||
notice_lang(service, u, NO_HELP_AVAILABLE, cmd);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
notice_lang(service, u, NO_HELP_AVAILABLE, cmd);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
99
commands.h
Normal file
99
commands.h
Normal file
@ -0,0 +1,99 @@
|
||||
/* Declarations for command data.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef COMMANDS_H
|
||||
#define COMMANDS_H
|
||||
|
||||
/* Note that modules.h MUST be included before this file (for the Module
|
||||
* type). */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Structure for information about a *Serv command. */
|
||||
|
||||
typedef struct command_ Command;
|
||||
struct command_ {
|
||||
const char *name;
|
||||
void (*routine)(User *u);
|
||||
int (*has_priv)(const User *u); /* Returns 1 if user may use cmd, else 0 */
|
||||
int helpmsg_all; /* Displayed to all users; -1 = no message */
|
||||
int helpmsg_reg; /* Displayed to regular users only */
|
||||
int helpmsg_oper; /* Displayed to IRC operators only */
|
||||
const char *help_param1;
|
||||
const char *help_param2;
|
||||
const char *help_param3;
|
||||
const char *help_param4;
|
||||
Command *next; /* Next command with this name */
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Commands must be registered with Services to be usable; an array of
|
||||
* Command structures (terminated with name == NULL) can be registered and
|
||||
* unregistered with the following routines. All routines return 1 on
|
||||
* success, 0 on failure. ("Failure" occurs only when parameters are
|
||||
* invalid.)
|
||||
*/
|
||||
|
||||
/* Set up a new command list using the given module pointer as an ID value.
|
||||
* Fails if a command list associated with `id' already exists. */
|
||||
extern int new_commandlist(Module *id);
|
||||
|
||||
/* Register a command array under the given ID. Fails if there is no
|
||||
* command list associated with `id', `array' is NULL, `array' has already
|
||||
* been added to the list, or there are multiple command entries in `array'
|
||||
* with the same name (case-insensitive). If an entry in `array' has the
|
||||
* same name as a previously-registered entry, the entry in `array' will
|
||||
* take precendence, and a pointer to the previous entry will be stored in
|
||||
* the `next' field of the entry. */
|
||||
extern int register_commands(Module *id, Command *array);
|
||||
|
||||
/* Unregister a command array from the given ID. Fails if there is no
|
||||
* command list associated with `id' or `array' was not in the list in the
|
||||
* first place. */
|
||||
extern int unregister_commands(Module *id, Command *array);
|
||||
|
||||
/* Delete the command list associated with the given ID. Fails if there is
|
||||
* no command list associated with `id' or the command list is not empty. */
|
||||
extern int del_commandlist(Module *id);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Routines for looking up and doing other things with commands. */
|
||||
|
||||
/* Returns the Command structure associated with the given command for the
|
||||
* given command list (`id'), or NULL if no such command exists. */
|
||||
extern Command *lookup_cmd(Module *id, const char *name);
|
||||
|
||||
/* Runs the routine associated with the given command, sending a help
|
||||
* message if there is no such command or the user does not have privileges
|
||||
* to use the command. Equivalent to
|
||||
* lookup_cmd(id,name)->routine(u)
|
||||
* with privilege and error checking. */
|
||||
extern void run_cmd(const char *service, User *u, Module *id, const char *cmd);
|
||||
|
||||
/* Sends the help message associated with the given command, or a generic
|
||||
* "command not found" message if there is no such command. Multiple
|
||||
* spaces in `cmd' will be compressed to a single space (thus modifying the
|
||||
* string). */
|
||||
extern void help_cmd(const char *service, User *u, Module *id, char *cmd);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* COMMANDS_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
248
compat.c
Normal file
248
compat.c
Normal file
@ -0,0 +1,248 @@
|
||||
/* Compatibility routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
|
||||
#if !HAVE_HSTRERROR
|
||||
# include <netdb.h>
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_HSTRERROR
|
||||
|
||||
/* hstrerror: return an error message for the given h_errno code. */
|
||||
|
||||
const char *hstrerror(int h_errnum)
|
||||
{
|
||||
switch (h_errnum) {
|
||||
case HOST_NOT_FOUND: return "Host not found";
|
||||
case TRY_AGAIN: return "Host not found, try again";
|
||||
case NO_RECOVERY: return "Nameserver error";
|
||||
case NO_DATA: return "No data of requested type";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* !HAVE_HSTRERROR */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_SNPRINTF
|
||||
|
||||
/* [v]snprintf: Like [v]sprintf, but don't write more than len bytes
|
||||
* (including null terminator). Return the number of bytes
|
||||
* written.
|
||||
*/
|
||||
|
||||
#if BAD_SNPRINTF
|
||||
int vsnprintf(char *buf, size_t len, const char *fmt, va_list args)
|
||||
{
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
*buf = 0;
|
||||
#undef vsnprintf
|
||||
vsnprintf(buf, len, fmt, args);
|
||||
#define vsnprintf my_vsnprintf
|
||||
buf[len-1] = 0;
|
||||
return strlen(buf);
|
||||
}
|
||||
#endif /* BAD_SNPRINTF */
|
||||
|
||||
int snprintf(char *buf, size_t len, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
va_start(args, fmt);
|
||||
ret = vsnprintf(buf, len, fmt, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* !HAVE_SNPRINTF */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_STRTOK
|
||||
|
||||
/* glibc 2.2 (RedHat 7.0) has a broken strtok--it dies if called with a
|
||||
* NULL parameter after returning NULL once. glibc and possibly other
|
||||
* libraries also return non-NULL for strtok(NULL, "") even after
|
||||
* returning NULL for strtok(NULL, " ").
|
||||
*/
|
||||
|
||||
char *strtok(char *str, const char *delim)
|
||||
{
|
||||
static char *current = NULL;
|
||||
char *ret;
|
||||
|
||||
if (str)
|
||||
current = str;
|
||||
if (!current)
|
||||
return NULL;
|
||||
current += strspn(current, delim);
|
||||
ret = *current ? current : NULL;
|
||||
current += strcspn(current, delim);
|
||||
if (!*current)
|
||||
current = NULL;
|
||||
else
|
||||
*current++ = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* !HAVE_STRTOK */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_STRICMP && !HAVE_STRCASECMP
|
||||
|
||||
/* stricmp, strnicmp: Case-insensitive versions of strcmp() and
|
||||
* strncmp().
|
||||
*/
|
||||
|
||||
int stricmp(const char *s1, const char *s2)
|
||||
{
|
||||
register int c;
|
||||
|
||||
while ((c = tolower(*s1)) == tolower(*s2)) {
|
||||
if (c == 0)
|
||||
return 0;
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
if (c < tolower(*s2))
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int strnicmp(const char *s1, const char *s2, size_t len)
|
||||
{
|
||||
register int c;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
while ((c = tolower(*s1)) == tolower(*s2) && len > 0) {
|
||||
if (c == 0 || --len == 0)
|
||||
return 0;
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
if (c < tolower(*s2))
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_STRDUP && !MEMCHECKS
|
||||
char *strdup(const char *s)
|
||||
{
|
||||
char *new = malloc(strlen(s)+1);
|
||||
if (new)
|
||||
strcpy(new, s);
|
||||
return new;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_STRSPN
|
||||
|
||||
size_t strspn(const char *s, const char *accept)
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
while (*s && strchr(accept, *s))
|
||||
++i, ++s;
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t strcspn(const char *s, const char *reject)
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
while (*s && !strchr(reject, *s))
|
||||
++i, ++s;
|
||||
return i;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_STRERROR
|
||||
# if HAVE_SYS_ERRLIST
|
||||
extern char *sys_errlist[];
|
||||
# endif
|
||||
|
||||
char *strerror(int errnum)
|
||||
{
|
||||
# if HAVE_SYS_ERRLIST
|
||||
return sys_errlist[errnum];
|
||||
# else
|
||||
static char buf[20];
|
||||
snprintf(buf, sizeof(buf), "Error %d", errnum);
|
||||
return buf;
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if !HAVE_STRSIGNAL
|
||||
char *strsignal(int signum)
|
||||
{
|
||||
static char buf[32];
|
||||
switch (signum) {
|
||||
case SIGHUP: strbcpy(buf, "Hangup"); break;
|
||||
case SIGINT: strbcpy(buf, "Interrupt"); break;
|
||||
case SIGQUIT: strbcpy(buf, "Quit"); break;
|
||||
#ifdef SIGILL
|
||||
case SIGILL: strbcpy(buf, "Illegal instruction"); break;
|
||||
#endif
|
||||
#ifdef SIGABRT
|
||||
case SIGABRT: strbcpy(buf, "Abort"); break;
|
||||
#endif
|
||||
#if defined(SIGIOT) && (!defined(SIGABRT) || SIGIOT != SIGABRT)
|
||||
case SIGIOT: strbcpy(buf, "IOT trap"); break;
|
||||
#endif
|
||||
#ifdef SIGBUS
|
||||
case SIGBUS: strbcpy(buf, "Bus error"); break;
|
||||
#endif
|
||||
case SIGFPE: strbcpy(buf, "Floating point exception"); break;
|
||||
case SIGKILL: strbcpy(buf, "Killed"); break;
|
||||
case SIGUSR1: strbcpy(buf, "User signal 1"); break;
|
||||
case SIGSEGV: strbcpy(buf, "Segmentation fault"); break;
|
||||
case SIGUSR2: strbcpy(buf, "User signal 2"); break;
|
||||
case SIGPIPE: strbcpy(buf, "Broken pipe"); break;
|
||||
case SIGALRM: strbcpy(buf, "Alarm clock"); break;
|
||||
case SIGTERM: strbcpy(buf, "Terminated"); break;
|
||||
case SIGSTOP: strbcpy(buf, "Stopped (signal)"); break;
|
||||
case SIGTSTP: strbcpy(buf, "Stopped"); break;
|
||||
case SIGIO: strbcpy(buf, "I/O error"); break;
|
||||
default: snprintf(buf, sizeof(buf), "Signal %d\n", signum);
|
||||
break;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
670
conffile.c
Normal file
670
conffile.c
Normal file
@ -0,0 +1,670 @@
|
||||
/* Configuration file handling.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "conffile.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void do_all_directives(int action, ConfigDirective *directives);
|
||||
|
||||
static int read_config_file(const char *modulename,
|
||||
ConfigDirective *directives);
|
||||
|
||||
static int do_read_config_file(const char *modulename,
|
||||
ConfigDirective *directives, FILE *f,
|
||||
const char *filename, int recursion_level);
|
||||
|
||||
static int parse_config_line(const char *filename, int linenum, char *buf,
|
||||
ConfigDirective *directives);
|
||||
|
||||
|
||||
/* Actions for do_all_directives(): */
|
||||
|
||||
#define ACTION_COPYNEW 0 /* Copy `new' parameters to config variables */
|
||||
#define ACTION_RESTORESAVED 1 /* Restore saved values of config variables */
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set configuration options for the given module (if `modulename' is NULL,
|
||||
* set core configuration options). Returns nonzero on success, 0 on error
|
||||
* (an error message is logged, and printed to the terminal if applicable,
|
||||
* in this case). Returns successfully without doing anything if
|
||||
* `directives' is NULL.
|
||||
*
|
||||
* `action' is a bitmask of CONFIGURE_* values (services.h), specifying
|
||||
* what this function should do, as follows:
|
||||
* - CONFIGURE_READ: read new values from the configuration file
|
||||
* - CONFIGURE_SET: copy new values to configuration variables
|
||||
* If both CONFIGURE_READ and CONFIGURE_SET are specified, new values are
|
||||
* copied to the configuration variables only if all values are read in
|
||||
* successfully (i.e. if a configure(...,CONFIGURE_READ) call would have
|
||||
* returned success). CONFIGURE_SET alone will never fail.
|
||||
*/
|
||||
|
||||
int configure(const char *modulename, ConfigDirective *directives,
|
||||
int action)
|
||||
{
|
||||
/* If no directives were given, return success */
|
||||
if (!directives)
|
||||
return 1;
|
||||
|
||||
if (action & CONFIGURE_READ) {
|
||||
if (!read_config_file(modulename, directives))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action & CONFIGURE_SET)
|
||||
do_all_directives(ACTION_COPYNEW, directives);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Deconfigure given directive array (free any allocated storage and
|
||||
* restore original values). A no-op if `directives' is NULL.
|
||||
*/
|
||||
|
||||
void deconfigure(ConfigDirective *directives)
|
||||
{
|
||||
if (directives)
|
||||
do_all_directives(ACTION_RESTORESAVED, directives);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Print a warning or error message to the log (and the console, if open). */
|
||||
|
||||
void config_error(const char *filename, int linenum, const char *message, ...)
|
||||
{
|
||||
char buf[4096];
|
||||
va_list args;
|
||||
|
||||
va_start(args, message);
|
||||
vsnprintf(buf, sizeof(buf), message, args);
|
||||
va_end(args);
|
||||
if (linenum)
|
||||
log("%s:%d: %s", filename, linenum, buf);
|
||||
else
|
||||
log("%s: %s", filename, buf);
|
||||
if (!nofork && isatty(2)) {
|
||||
if (linenum)
|
||||
fprintf(stderr, "%s:%d: %s\n", filename, linenum, buf);
|
||||
else
|
||||
fprintf(stderr, "%s: %s\n", filename, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Perform an action for all directives in an array; the action is given
|
||||
* by ACTION_*, defined above.
|
||||
*/
|
||||
|
||||
static void do_all_directives(int action, ConfigDirective *directives)
|
||||
{
|
||||
int n, i;
|
||||
|
||||
for (n = 0; directives[n].name; n++) {
|
||||
ConfigDirective *d = &directives[n];
|
||||
for (i = 0; i < CONFIG_MAXPARAMS && d->params[i].type != CD_NONE; i++){
|
||||
CDValue val;
|
||||
|
||||
/* Select the appropriate value to copy */
|
||||
if (action == ACTION_COPYNEW)
|
||||
val = d->params[i].new;
|
||||
else
|
||||
val = d->params[i].prev;
|
||||
|
||||
/* In any case, we'll be rewriting the config variable, so free
|
||||
* the previous value if it was one we allocated */
|
||||
if (d->params[i].flags & CF_ALLOCED) {
|
||||
free(*(void **)d->params[i].ptr);
|
||||
d->params[i].flags &= ~CF_ALLOCED;
|
||||
}
|
||||
|
||||
/* Don't do anything if we're copying new values and this
|
||||
* directive/parameter wasn't seen, or if we're restoring saved
|
||||
* values and this parameter hasn't had its value saved (except
|
||||
* for function parameters) */
|
||||
if (action == ACTION_COPYNEW
|
||||
&& (!d->was_seen || !(d->params[i].flags & CF_WASSET)))
|
||||
continue;
|
||||
if (action == ACTION_RESTORESAVED
|
||||
&& d->params[i].type != CD_FUNC
|
||||
&& !(d->params[i].flags & CF_SAVED))
|
||||
continue;
|
||||
|
||||
/* Copy new value to configuration variable */
|
||||
switch (d->params[i].type) {
|
||||
case CD_SET:
|
||||
if (action == ACTION_COPYNEW)
|
||||
*(int *)d->params[i].ptr = (int)val.intval;
|
||||
break;
|
||||
case CD_TIME:
|
||||
*(time_t *)d->params[i].ptr = val.timeval;
|
||||
break;
|
||||
case CD_STRING:
|
||||
*(char **)d->params[i].ptr = val.ptrval;
|
||||
break;
|
||||
case CD_INT:
|
||||
case CD_POSINT:
|
||||
case CD_PORT:
|
||||
case CD_TIMEMSEC:
|
||||
*(int32 *)d->params[i].ptr = val.intval;
|
||||
break;
|
||||
case CD_FUNC: {
|
||||
int (*func)(const char *, int, char *)
|
||||
= (int (*)(const char *,int,char *))(d->params[i].ptr);
|
||||
if (action == ACTION_COPYNEW)
|
||||
func(NULL, CDFUNC_SET, NULL);
|
||||
else
|
||||
func(NULL, CDFUNC_DECONFIG, NULL);
|
||||
break;
|
||||
} /* case CD_FUNC */
|
||||
case CD_DEPRECATED:
|
||||
/* Nothing to do */
|
||||
break;
|
||||
default:
|
||||
log("conffile: do_all_directives BUG: don't know how to "
|
||||
" copy type %d (%s/%d)", d->params[i].type, d->name, i);
|
||||
break;
|
||||
} /* switch */
|
||||
|
||||
/* Fix up flags */
|
||||
if (action == ACTION_COPYNEW) {
|
||||
if (d->params[i].flags & CF_ALLOCED_NEW) {
|
||||
d->params[i].flags |= CF_ALLOCED;
|
||||
/* The value is still allocated, but it's now stored in
|
||||
* the configuration variable, so we don't want to free
|
||||
* it when clearing `new' */
|
||||
d->params[i].flags &= ~CF_ALLOCED_NEW;
|
||||
}
|
||||
} else {
|
||||
d->params[i].flags &= ~CF_SAVED;
|
||||
}
|
||||
} /* for each parameter */
|
||||
} /* for each directive */
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Read in configuration options, and return nonzero for success, zero for
|
||||
* failure. Performs the actions needed by configure(...,CONFIGURE_READ).
|
||||
*/
|
||||
|
||||
static int read_config_file(const char *modulename,
|
||||
ConfigDirective *directives)
|
||||
{
|
||||
const char *filename;
|
||||
FILE *f;
|
||||
int retval, i, n;
|
||||
|
||||
/* Open default file */
|
||||
filename = modulename==NULL ? IRCSERVICES_CONF : MODULES_CONF;
|
||||
f = fopen(filename, "r");
|
||||
if (!f) {
|
||||
log_perror("Unable to open %s", filename);
|
||||
if (!nofork && isatty(2))
|
||||
fprintf(stderr, "Unable to open %s: %s\n", filename,
|
||||
strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Clear `was_set' flag and `new' value for all directives */
|
||||
for (n = 0; directives[n].name != NULL; n++) {
|
||||
directives[n].was_seen = 0;
|
||||
for (i = 0; i < CONFIG_MAXPARAMS; i++) {
|
||||
if (directives[n].params[i].flags & CF_ALLOCED_NEW)
|
||||
free(directives[n].params[i].new.ptrval);
|
||||
directives[n].params[i].flags &= ~(CF_WASSET | CF_ALLOCED_NEW);
|
||||
memset(&directives[n].params[i].new, 0,
|
||||
sizeof(directives[n].params[i].new));
|
||||
if (directives[n].params[i].type == CD_FUNC) {
|
||||
int (*func)(const char *, int, char *)
|
||||
= (int (*)(const char *, int, char *))
|
||||
(directives[n].params[i].ptr);
|
||||
func(NULL, CDFUNC_INIT, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Actually do the work */
|
||||
retval = do_read_config_file(modulename, directives, f, filename, 0);
|
||||
|
||||
fclose(f);
|
||||
|
||||
/* Make sure all required directives were seen */
|
||||
for (n = 0; directives[n].name != NULL; n++) {
|
||||
if (!directives[n].was_seen
|
||||
&& (directives[n].params[0].flags & CF_DIRREQ)
|
||||
) {
|
||||
config_error(filename, 0, "Required directive `%s' missing",
|
||||
directives[n].name);
|
||||
retval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Read in a single configuration file, recursively processing IncludeFile
|
||||
* directives.
|
||||
*/
|
||||
|
||||
static int do_read_config_file(const char *modulename,
|
||||
ConfigDirective *directives, FILE *f,
|
||||
const char *filename, int recursion_level)
|
||||
{
|
||||
char *current_module = NULL; /* Current module in modules.conf */
|
||||
int retval = 1; /* Return value */
|
||||
int linenum = 0;
|
||||
char buf[4096], tmpbuf[4096], *s;
|
||||
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
/* Check for pathologically long files */
|
||||
if (linenum+1 < linenum) {
|
||||
config_error(filename, linenum, "File too long");
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
linenum++;
|
||||
/* Check for pathologically long lines */
|
||||
if (strlen(buf) == sizeof(buf)-1 && buf[sizeof(buf)-1] != '\n') {
|
||||
/* Report the maximum size as sizeof(buf)-3 to allow \r\n as
|
||||
* well as \n to fit */
|
||||
config_error(filename, linenum, "Line too long (%d bytes maximum)",
|
||||
sizeof(buf)-3);
|
||||
/* Skip everything else until an EOL (or EOF) is seen */
|
||||
while (fgets(buf, sizeof(buf), f) && buf[strlen(buf)-1] != '\n')
|
||||
/*nothing*/;
|
||||
retval = 0;
|
||||
}
|
||||
/* Strip out comments (but don't touch # inside of quotes) */
|
||||
s = buf;
|
||||
while (*s) {
|
||||
if (*s == '"') {
|
||||
if (!(s = strchr(s+1, '"')))
|
||||
break;
|
||||
} else if (*s == '#') {
|
||||
*s = 0;
|
||||
break;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
/* Check for IncludeFile directives (use tmpbuf to avoid damaging
|
||||
* the original copy of the line) */
|
||||
strbcpy(tmpbuf, buf);
|
||||
s = strtok(tmpbuf, " \t\r\n");
|
||||
if (s && stricmp(s, "IncludeFile") == 0) {
|
||||
FILE *f2;
|
||||
/* Check recursion level for infinite loops */
|
||||
if (recursion_level > 100) {
|
||||
config_error(filename, linenum,
|
||||
"IncludeFile recursion depth limit exceeded");
|
||||
retval = 0;
|
||||
continue;
|
||||
}
|
||||
/* Find the filename */
|
||||
s = strtok_remaining();
|
||||
if (s && *s == '"') {
|
||||
char *t = strchr(s+1, '"');
|
||||
if (!t) {
|
||||
config_error(filename, linenum,
|
||||
"Missing closing double quote");
|
||||
retval = 0;
|
||||
continue;
|
||||
}
|
||||
*t = 0;
|
||||
s++;
|
||||
} else {
|
||||
s = strtok(s, " \t\r\n");
|
||||
}
|
||||
if (!s || !*s) {
|
||||
config_error(filename, linenum,
|
||||
"Missing filename for IncludeFile");
|
||||
retval = 0;
|
||||
continue;
|
||||
}
|
||||
/* Valid filename string; try to open it */
|
||||
f2 = fopen(s, "r");
|
||||
if (!f2) {
|
||||
config_error(filename, linenum,
|
||||
"Unable to open %s: %s", s, strerror(errno));
|
||||
retval = 0;
|
||||
continue;
|
||||
}
|
||||
if (!do_read_config_file(modulename, directives, f2, s,
|
||||
recursion_level+1))
|
||||
retval = 0;
|
||||
fclose(f2);
|
||||
continue;
|
||||
}
|
||||
/* Handle Module/EndModule lines specially, and don't parse lines
|
||||
* belonging to other modules */
|
||||
if (modulename) {
|
||||
if (current_module) {
|
||||
/* Inside a Module/EndModule pair: discard lines belonging
|
||||
* to other modules, and handle EndModule directives. If
|
||||
* we reach EndModule for the module we're supposed to be
|
||||
* processing, exit the loop to avoid unneeded processing. */
|
||||
strbcpy(tmpbuf, buf);
|
||||
s = strtok(tmpbuf, " \t\r\n");
|
||||
if (s && stricmp(s, "EndModule") == 0) {
|
||||
int strcmp_result = strcmp(current_module, modulename);
|
||||
free(current_module);
|
||||
if (strcmp_result == 0)
|
||||
break; /* stop processing file, we're finished */
|
||||
else
|
||||
current_module = NULL;
|
||||
continue;
|
||||
} else if (strcmp(current_module, modulename) != 0) {
|
||||
continue;
|
||||
}
|
||||
} else { /* !current_module */
|
||||
/* Outside a Module/EndModule pair: handle Module
|
||||
* directives, and report errors for anything else */
|
||||
s = strtok(buf, " \t\r\n");
|
||||
if (!s)
|
||||
continue;
|
||||
if (stricmp(s, "Module") != 0) {
|
||||
config_error(filename, linenum,
|
||||
"Expected `Module' directive");
|
||||
retval = 0;
|
||||
} else {
|
||||
current_module = strtok(NULL, " \t\r\n");
|
||||
if (!current_module) {
|
||||
config_error(filename, linenum, "Module name missing");
|
||||
retval = 0;
|
||||
}
|
||||
current_module = strdup(current_module);
|
||||
if (!current_module) {
|
||||
config_error(filename, linenum, "Out of memory");
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} /* if (current_module) */
|
||||
} /* if (modulename) */
|
||||
/* Valid line--parse it */
|
||||
if (!parse_config_line(filename, linenum, buf, directives))
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Parse a configuration line. Return 1 on success; otherwise, print (and
|
||||
* log, if applicable) appropriate error message and return 0. Destroys
|
||||
* the buffer by side effect.
|
||||
*/
|
||||
|
||||
static int parse_config_line(const char *filename, int linenum, char *buf,
|
||||
ConfigDirective *directives)
|
||||
{
|
||||
char *s, *t, *directive;
|
||||
int i, n, optind;
|
||||
long longval;
|
||||
unsigned long ulongval;
|
||||
int retval = 1;
|
||||
int ac = 0;
|
||||
char *av[CONFIG_MAXPARAMS];
|
||||
|
||||
directive = strtok(buf, " \t\r\n");
|
||||
s = strtok(NULL, "");
|
||||
if (s) {
|
||||
while (isspace(*s))
|
||||
s++;
|
||||
while (*s) {
|
||||
if (ac >= CONFIG_MAXPARAMS) {
|
||||
config_error(filename, linenum,
|
||||
"Warning: too many parameters (%d max)",
|
||||
CONFIG_MAXPARAMS);
|
||||
break;
|
||||
}
|
||||
t = s;
|
||||
if (*s == '"') {
|
||||
t++;
|
||||
s++;
|
||||
while (*s && *s != '"') {
|
||||
if (*s == '\\' && s[1] != 0)
|
||||
strmove(s, s+1);
|
||||
s++;
|
||||
}
|
||||
if (!*s)
|
||||
config_error(filename, linenum,
|
||||
"Warning: unterminated double-quoted string");
|
||||
else
|
||||
*s++ = 0;
|
||||
} else {
|
||||
s += strcspn(s, " \t\r\n");
|
||||
if (*s)
|
||||
*s++ = 0;
|
||||
}
|
||||
av[ac++] = t;
|
||||
while (isspace(*s))
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!directive)
|
||||
return 1;
|
||||
|
||||
for (n = 0; directives[n].name; n++) {
|
||||
ConfigDirective *d = &directives[n];
|
||||
if (stricmp(directive, d->name) != 0)
|
||||
continue;
|
||||
d->was_seen = 1;
|
||||
optind = 0;
|
||||
for (i = 0; i < CONFIG_MAXPARAMS && d->params[i].type != CD_NONE; i++){
|
||||
if (d->params[i].type == CD_SET) {
|
||||
if (!(d->params[i].flags & CF_SAVED)) {
|
||||
d->params[i].prev.intval = *(int *)d->params[i].ptr;
|
||||
d->params[i].flags |= CF_SAVED;
|
||||
}
|
||||
d->params[i].new.intval = 1;
|
||||
d->params[i].flags |= CF_WASSET;
|
||||
continue;
|
||||
}
|
||||
if (d->params[i].type == CD_DEPRECATED) {
|
||||
config_error(filename, linenum,
|
||||
"Deprecated directive `%s' used", d->name);
|
||||
d->params[i].flags |= CF_WASSET;
|
||||
continue;
|
||||
}
|
||||
if (optind >= ac) {
|
||||
if (!(d->params[i].flags & CF_OPTIONAL)) {
|
||||
config_error(filename, linenum,
|
||||
"Not enough parameters for `%s'", d->name);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (d->params[i].type) {
|
||||
case CD_INT:
|
||||
if (!(d->params[i].flags & CF_SAVED)) {
|
||||
d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
|
||||
d->params[i].flags |= CF_SAVED;
|
||||
}
|
||||
longval = strtol(av[optind++], &s, 0);
|
||||
if (*s) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Expected an integer for parameter %d",
|
||||
d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
#if SIZEOF_LONG > 4
|
||||
if (longval < -0x80000000L || longval > 0x7FFFFFFFL) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Value out of range for parameter %d",
|
||||
d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
d->params[i].new.intval = (int32)longval;
|
||||
break;
|
||||
case CD_POSINT:
|
||||
if (!(d->params[i].flags & CF_SAVED)) {
|
||||
d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
|
||||
d->params[i].flags |= CF_SAVED;
|
||||
}
|
||||
ulongval = strtoul(av[optind++], &s, 0);
|
||||
if (*s || ulongval <= 0) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Expected a positive integer for"
|
||||
" parameter %d", d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
#if SIZEOF_LONG > 4
|
||||
if (ulongval > 0xFFFFFFFFL) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Value out of range for parameter %d",
|
||||
d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
d->params[i].new.intval = (int32)ulongval;
|
||||
break;
|
||||
case CD_PORT:
|
||||
if (!(d->params[i].flags & CF_SAVED)) {
|
||||
d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
|
||||
d->params[i].flags |= CF_SAVED;
|
||||
}
|
||||
longval = strtol(av[optind++], &s, 0);
|
||||
if (*s) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Expected a port number for parameter %d",
|
||||
d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
if (longval < 1 || longval > 65535) {
|
||||
config_error(filename, linenum,
|
||||
"Port numbers must be in the range 1..65535");
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
d->params[i].new.intval = (int32)longval;
|
||||
break;
|
||||
case CD_STRING:
|
||||
if (!(d->params[i].flags & CF_SAVED)) {
|
||||
d->params[i].prev.ptrval = *(char **)d->params[i].ptr;
|
||||
d->params[i].flags |= CF_SAVED;
|
||||
}
|
||||
d->params[i].new.ptrval = strdup(av[optind++]);
|
||||
if (!d->params[i].new.ptrval) {
|
||||
config_error(filename, linenum, "%s: Out of memory",
|
||||
d->name);
|
||||
return 0;
|
||||
}
|
||||
d->params[i].flags |= CF_ALLOCED_NEW;
|
||||
break;
|
||||
case CD_TIME:
|
||||
if (!(d->params[i].flags & CF_SAVED)) {
|
||||
d->params[i].prev.timeval = *(time_t *)d->params[i].ptr;
|
||||
d->params[i].flags |= CF_SAVED;
|
||||
}
|
||||
d->params[i].new.timeval = dotime(av[optind++]);
|
||||
if (d->params[i].new.timeval < 0) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Expected a time value for parameter %d",
|
||||
d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CD_TIMEMSEC:
|
||||
if (!(d->params[i].flags & CF_SAVED)) {
|
||||
d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
|
||||
d->params[i].flags |= CF_SAVED;
|
||||
}
|
||||
longval = strtol(av[optind++], &s, 10);
|
||||
if (longval < 0) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Expected a positive value for"
|
||||
" parameter %d", d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
} else if (longval > 1000000) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Value too large (maximum 1000000)",
|
||||
d->name);
|
||||
}
|
||||
longval *= 1000;
|
||||
if (*s == '.') {
|
||||
int decimal = 0;
|
||||
int count = 0;
|
||||
s++;
|
||||
while (count < 3 && isdigit(*s)) {
|
||||
decimal = decimal*10 + (*s++ - '0');
|
||||
count++;
|
||||
}
|
||||
while (count++ < 3)
|
||||
decimal *= 10;
|
||||
longval += decimal;
|
||||
while (isdigit(*s))
|
||||
s++;
|
||||
}
|
||||
if (*s) {
|
||||
config_error(filename, linenum,
|
||||
"%s: Expected a decimal number for"
|
||||
" parameter %d", d->name, optind);
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
d->params[i].new.intval = (int32)longval;
|
||||
break;
|
||||
case CD_FUNC: {
|
||||
int (*func)(const char *, int, char *)
|
||||
= (int (*)(const char *, int, char *))(d->params[i].ptr);
|
||||
if (!func(filename, linenum, av[optind++]))
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
config_error(filename, linenum, "%s: Unknown type %d for"
|
||||
" param %d", d->name, d->params[i].type, i+1);
|
||||
return 0; /* don't bother continuing--something's bizarre */
|
||||
} /* switch (d->params[i].type) */
|
||||
d->params[i].flags |= CF_WASSET;
|
||||
} /* for all parameters */
|
||||
break; /* because we found a match */
|
||||
} /* for all directives in array */
|
||||
|
||||
if (!directives[n].name) {
|
||||
config_error(filename, linenum, "Unknown directive `%s'", directive);
|
||||
return 1; /* don't cause abort */
|
||||
}
|
||||
|
||||
return retval;
|
||||
} /* parse_config_line() */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
108
conffile.h
Normal file
108
conffile.h
Normal file
@ -0,0 +1,108 @@
|
||||
/* Structures, constants, and external declarations for configuration file
|
||||
* handling.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef CONFFILE_H
|
||||
#define CONFFILE_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Configuration directives. Note that all numeric parameter types except
|
||||
* CD_TIME and CD_SET take an int32 value pointer (including CD_TIMEMSEC).
|
||||
*/
|
||||
|
||||
/* Information about a configuration parameter's value: */
|
||||
typedef union {
|
||||
void *ptrval;
|
||||
int32 intval;
|
||||
time_t timeval;
|
||||
} CDValue;
|
||||
|
||||
/* Information about a configuration directive: */
|
||||
typedef struct {
|
||||
const char *name;
|
||||
struct {
|
||||
int type; /* Parameter type (CD_* below) */
|
||||
int flags; /* Parameter flags (CF_* below) */
|
||||
void *ptr; /* Pointer to where to store the value */
|
||||
/* The following data is internal-use-only: */
|
||||
CDValue prev; /* Previous value (to restore when deconfigured) */
|
||||
CDValue new; /* New value (to set if conf-file successfully read) */
|
||||
} params[CONFIG_MAXPARAMS];
|
||||
/* Also internal use only: */
|
||||
int was_seen; /* Non-zero if directive was seen this time around */
|
||||
} ConfigDirective;
|
||||
|
||||
#define CD_NONE 0
|
||||
#define CD_INT 1
|
||||
#define CD_POSINT 2 /* Positive integer only */
|
||||
#define CD_PORT 3 /* 1..65535 only */
|
||||
#define CD_STRING 4
|
||||
#define CD_TIME 5 /* Type of `ptr' is `time_t *' */
|
||||
#define CD_TIMEMSEC 6 /* Variable is in milliseconds, parameter
|
||||
* in seconds (decimal allowed) */
|
||||
#define CD_FUNC 7 /* `ptr' is a function to call; see
|
||||
* init.c for examples */
|
||||
#define CD_SET -1 /* Not a real parameter; just set the
|
||||
* given `int' variable to 1 */
|
||||
#define CD_DEPRECATED -2 /* Set for deprecated directives; causes
|
||||
* a warning to be printed */
|
||||
|
||||
/* Flags: */
|
||||
#define CF_OPTIONAL 0x01 /* Parameter is optional (defaults to 0) */
|
||||
#define CF_DIRREQ 0x02 /* Directive is required (set on first param)*/
|
||||
/* Internal-use-only flags: */
|
||||
#define CF_SAVED 0x80 /* Original value saved in `prev' */
|
||||
#define CF_WASSET 0x40 /* Parameter set this time around */
|
||||
#define CF_ALLOCED 0x20 /* Current value is alloc'd by parser */
|
||||
#define CF_ALLOCED_NEW 0x10 /* Value of `new' is alloc'd by parser */
|
||||
|
||||
/* Values for `action' parameter to configure() (can be or'ed together): */
|
||||
#define CONFIGURE_READ 1 /* Read settings from configuration file */
|
||||
#define CONFIGURE_SET 2 /* Set configuration variables to new values */
|
||||
|
||||
/* Values passed in `linenum' parameter to configuration directive handlers
|
||||
* (CD_FUNC parameters) when `filename' is NULL: */
|
||||
#define CDFUNC_INIT 0 /* Prepare for reading data */
|
||||
#define CDFUNC_SET 1 /* Copy new data to real config variables */
|
||||
#define CDFUNC_DECONFIG 2 /* Delete any data in config variables */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Global functions: */
|
||||
|
||||
/* Set configuration options for the given module (if `modulename' is NULL,
|
||||
* set core configuration options). Returns nonzero on success, 0 on error
|
||||
* (an error message is logged, and printed to the terminal if applicable,
|
||||
* in this case). Returns successfully without doing anything if
|
||||
* `directives' is NULL. */
|
||||
extern int configure(const char *modulename, ConfigDirective *directives,
|
||||
int action);
|
||||
|
||||
/* Deconfigure given directive array (free any allocated storage and
|
||||
* restore original values). A no-op if `directives' is NULL. */
|
||||
extern void deconfigure(ConfigDirective *directives);
|
||||
|
||||
/* Print a warning or error message to the log (and the console, if open). */
|
||||
extern void config_error(const char *filename, int linenum,
|
||||
const char *message,...);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* CONFFILE_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
30
cp-recursive
Normal file
30
cp-recursive
Normal file
@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
if [ $1 = "-t" ] ; then
|
||||
shift
|
||||
fi
|
||||
if [ ! "$2" ] ; then
|
||||
echo >&2 "Usage: $0 <sourcedir> <targetdir>"
|
||||
exit 1
|
||||
fi
|
||||
if [ -d "$1" ] ; then
|
||||
dir="$1"
|
||||
else
|
||||
dir=`dirname "$1"`
|
||||
fi
|
||||
while [ "$2" ] ; do
|
||||
shift
|
||||
done
|
||||
dest=`echo "$1" | sed s,/*$,,`
|
||||
if [ -d "$dest" ] ; then
|
||||
dest="$dest/`basename $dir`"
|
||||
fi
|
||||
mkdir -p "$dest"
|
||||
tar Ccf $dir - . | tar Cxf $dest -
|
||||
|
||||
# Local variables:
|
||||
# indent-tabs-mode: nil
|
||||
# End:
|
||||
#
|
||||
# vim: expandtab shiftwidth=4:
|
327
databases.c
Normal file
327
databases.c
Normal file
@ -0,0 +1,327 @@
|
||||
/* Database core routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "databases.h"
|
||||
#include "encrypt.h"
|
||||
#include "modules.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* List of registered tables */
|
||||
typedef struct dbtablenode_ DBTableNode;
|
||||
struct dbtablenode_ {
|
||||
DBTableNode *next, *prev;
|
||||
DBTable *table;
|
||||
const Module *owner; /* Which module registered this table? */
|
||||
int loaded; /* Has this table been loaded? */
|
||||
};
|
||||
static DBTableNode *tables = NULL;
|
||||
|
||||
/* Currently active database module */
|
||||
static DBModule *dbmodule = NULL;
|
||||
|
||||
|
||||
/* Local routines: */
|
||||
static int do_unload_module(const Module *module);
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialization/cleanup routines. */
|
||||
|
||||
int database_init(int ac, char **av)
|
||||
{
|
||||
if (!add_callback(NULL, "unload module", do_unload_module)) {
|
||||
log("database_init: add_callback() failed");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/************************************/
|
||||
|
||||
void database_cleanup(void)
|
||||
{
|
||||
remove_callback(NULL, "unload module", do_unload_module);
|
||||
}
|
||||
|
||||
/************************************/
|
||||
|
||||
/* Check for tables that the module forgot to unload. */
|
||||
|
||||
static int do_unload_module(const Module *module)
|
||||
{
|
||||
DBTableNode *t, *t2;
|
||||
|
||||
LIST_FOREACH_SAFE (t, tables, t2) {
|
||||
if (t->owner == module) {
|
||||
log("database: Module `%s' forgot to unregister table `%s'",
|
||||
get_module_name(module), t->table->name);
|
||||
unregister_dbtable(t->table);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Register a new database table. Returns nonzero on success, zero on
|
||||
* error.
|
||||
*/
|
||||
|
||||
int _register_dbtable(DBTable *table, const Module *caller)
|
||||
{
|
||||
DBTableNode *t;
|
||||
|
||||
/* Sanity checks on parameter */
|
||||
if (!table) {
|
||||
log("BUG: register_dbtable() with NULL table!");
|
||||
return 0;
|
||||
}
|
||||
if (!table->name) {
|
||||
log("BUG: register_dbtable(): table->name is NULL!");
|
||||
return 0;
|
||||
}
|
||||
if (!table->fields || !table->newrec || !table->freerec
|
||||
|| !table->insert || !table->first || !table->next
|
||||
) {
|
||||
log("BUG: register_dbtable(%s): table->%s is NULL!", table->name,
|
||||
!table->fields ? "fields" :
|
||||
!table->newrec ? "newrec" :
|
||||
!table->freerec ? "freerec" :
|
||||
!table->insert ? "insert" :
|
||||
!table->first ? "first" :
|
||||
!table->next ? "next" :
|
||||
"???");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sanity check: make sure it's not already registered */
|
||||
LIST_FOREACH (t, tables) {
|
||||
if (t->table == table) {
|
||||
log("BUG: register_dbtable(%s): table already registered!",
|
||||
table->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate and append to list (make sure to preserve order, since
|
||||
* later tables may depend on earlier ones); if a database module is
|
||||
* available, load the table ammediately*/
|
||||
t = smalloc(sizeof(*t));
|
||||
t->table = table;
|
||||
t->owner = caller;
|
||||
t->loaded = 0;
|
||||
LIST_APPEND(t, tables);
|
||||
if (dbmodule)
|
||||
t->loaded = (*dbmodule->load_table)(t->table);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Unregister a database table. Does nothing if the table was not
|
||||
* registered in the first place.
|
||||
*/
|
||||
|
||||
void unregister_dbtable(DBTable *table)
|
||||
{
|
||||
DBTableNode *t;
|
||||
|
||||
if (!table) {
|
||||
log("BUG: unregister_dbtable() with NULL table!");
|
||||
return;
|
||||
}
|
||||
/* Sanity check: make sure it was registered first */
|
||||
LIST_FOREACH (t, tables) {
|
||||
if (t->table == table) {
|
||||
LIST_REMOVE(t, tables);
|
||||
free(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Save all registered database tables to permanent storage. Returns 1 if
|
||||
* all tables were successfully saved or no tables are registered, 0 if
|
||||
* some tables were successfully saved (but some were not), or -1 if no
|
||||
* tables were successfully saved.
|
||||
*/
|
||||
|
||||
int save_all_dbtables(void)
|
||||
{
|
||||
DBTableNode *t;
|
||||
int some_saved = 0;
|
||||
int some_failed = 0;
|
||||
|
||||
if (!tables)
|
||||
return 1;
|
||||
if (!dbmodule) {
|
||||
log("save_all_dbtables(): No database module registered!");
|
||||
return -1;
|
||||
}
|
||||
LIST_FOREACH (t, tables) {
|
||||
if ((*dbmodule->save_table)(t->table)) {
|
||||
some_saved = 1;
|
||||
} else {
|
||||
log("save_all_dbtables(): Failed to save table `%s'",
|
||||
t->table->name);
|
||||
some_failed = 1;
|
||||
}
|
||||
}
|
||||
return some_saved - some_failed;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Register a database module. Returns nonzero on success, zero on error.
|
||||
* On success, all registered tables which have not already been loaded
|
||||
* will be loaded from permanent storage. Only one database module can be
|
||||
* registered.
|
||||
*/
|
||||
|
||||
int register_dbmodule(DBModule *module)
|
||||
{
|
||||
DBTableNode *t;
|
||||
|
||||
if (!module) {
|
||||
log("BUG: register_dbmodule() with NULL module!");
|
||||
return 0;
|
||||
}
|
||||
if (!module->load_table || !module->save_table) {
|
||||
log("BUG: register_dbmodule(): module->%s is NULL!",
|
||||
!module->load_table ? "load_table" : "save_table");
|
||||
return 0;
|
||||
}
|
||||
if (dbmodule) {
|
||||
if (module == dbmodule)
|
||||
log("BUG: register_dbmodule(): attempt to re-register module!");
|
||||
else
|
||||
log("register_dbmodule(): a database module is already registered");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dbmodule = module;
|
||||
LIST_FOREACH (t, tables) {
|
||||
if (!t->loaded)
|
||||
t->loaded = (*dbmodule->load_table)(t->table);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Unregister a database module. Does nothing if the module was not
|
||||
* registered in the first place.
|
||||
*/
|
||||
|
||||
void unregister_dbmodule(DBModule *module)
|
||||
{
|
||||
if (!module) {
|
||||
log("BUG: unregister_dbmodule() with NULL module!");
|
||||
return;
|
||||
}
|
||||
if (dbmodule == module)
|
||||
dbmodule = NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Read a value from a database field. The value buffer is assumed to be
|
||||
* large enough to hold the retrieved value.
|
||||
*/
|
||||
|
||||
void get_dbfield(const void *record, const DBField *field, void *buffer)
|
||||
{
|
||||
int size;
|
||||
|
||||
if (!record || !field || !buffer) {
|
||||
log("BUG: get_dbfield(): %s is NULL!",
|
||||
!record ? "record" : !field ? "field" : "buffer");
|
||||
return;
|
||||
}
|
||||
if (field->get) {
|
||||
(*field->get)(record, buffer);
|
||||
return;
|
||||
}
|
||||
switch (field->type) {
|
||||
case DBTYPE_INT8: size = 1; break;
|
||||
case DBTYPE_UINT8: size = 1; break;
|
||||
case DBTYPE_INT16: size = 2; break;
|
||||
case DBTYPE_UINT16: size = 2; break;
|
||||
case DBTYPE_INT32: size = 4; break;
|
||||
case DBTYPE_UINT32: size = 4; break;
|
||||
case DBTYPE_TIME: size = sizeof(time_t); break;
|
||||
case DBTYPE_STRING: size = sizeof(char *); break;
|
||||
case DBTYPE_BUFFER: size = field->length; break;
|
||||
case DBTYPE_PASSWORD: size = sizeof(Password); break;
|
||||
default:
|
||||
log("BUG: bad field type %d in get_dbfield()", field->type);
|
||||
return;
|
||||
}
|
||||
if (!size) {
|
||||
return;
|
||||
}
|
||||
memcpy(buffer, (const uint8 *)record + field->offset, size);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Store a value to a database field. */
|
||||
|
||||
void put_dbfield(void *record, const DBField *field, const void *value)
|
||||
{
|
||||
int size;
|
||||
|
||||
if (!record || !field || !value) {
|
||||
log("BUG: get_dbfield(): %s is NULL!",
|
||||
!record ? "record" : !field ? "field" : "value");
|
||||
return;
|
||||
}
|
||||
if (field->put) {
|
||||
(*field->put)(record, value);
|
||||
return;
|
||||
}
|
||||
switch (field->type) {
|
||||
case DBTYPE_INT8: size = 1; break;
|
||||
case DBTYPE_UINT8: size = 1; break;
|
||||
case DBTYPE_INT16: size = 2; break;
|
||||
case DBTYPE_UINT16: size = 2; break;
|
||||
case DBTYPE_INT32: size = 4; break;
|
||||
case DBTYPE_UINT32: size = 4; break;
|
||||
case DBTYPE_TIME: size = sizeof(time_t); break;
|
||||
case DBTYPE_STRING: size = sizeof(char *); break;
|
||||
case DBTYPE_BUFFER: size = field->length; break;
|
||||
case DBTYPE_PASSWORD: size = sizeof(Password); break;
|
||||
default:
|
||||
log("BUG: bad field type %d in get_dbfield()", field->type);
|
||||
return;
|
||||
}
|
||||
if (!size) {
|
||||
return;
|
||||
}
|
||||
memcpy((uint8 *)record + field->offset, value, size);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
139
databases.h
Normal file
139
databases.h
Normal file
@ -0,0 +1,139 @@
|
||||
/* Database structures and declarations.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef DATABASE_H
|
||||
#define DATABASE_H
|
||||
|
||||
#ifndef MODULES_H
|
||||
# include "modules.h"
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Type constants for DBField.type */
|
||||
typedef enum {
|
||||
DBTYPE_INT8,
|
||||
DBTYPE_UINT8,
|
||||
DBTYPE_INT16,
|
||||
DBTYPE_UINT16,
|
||||
DBTYPE_INT32,
|
||||
DBTYPE_UINT32,
|
||||
DBTYPE_TIME,
|
||||
DBTYPE_STRING,
|
||||
DBTYPE_BUFFER, /* Buffer size in DBField.length */
|
||||
DBTYPE_PASSWORD,
|
||||
} DBType;
|
||||
|
||||
/* Structure that describes a field of a database table */
|
||||
typedef struct dbfield_ {
|
||||
const char *name; /* Field name */
|
||||
DBType type; /* Field type */
|
||||
int offset; /* Offset to field from start of structure
|
||||
* (use standard `offsetof' macro) */
|
||||
int length; /* Length of DBTYPE_BUFFER fields */
|
||||
int load_only; /* If nonzero, field is not saved (use for reading
|
||||
* obsolete fields) */
|
||||
void (*get)(const void *record, void **value_ret);
|
||||
/* Function to retrieve the field's value when
|
||||
* saving; `value_ret' points to a variable of
|
||||
* the appropriate type (for DBTYPE_STRING, the
|
||||
* string should be malloc'd if not NULL) */
|
||||
void (*put)(void *record, const void *value);
|
||||
/* Function to set the field's value on load;
|
||||
* `value' points to a variable of the
|
||||
* appropriate type */
|
||||
} DBField;
|
||||
|
||||
/* Structure that describes a database table */
|
||||
typedef struct dbtable_ {
|
||||
const char *name; /* Table name */
|
||||
DBField *fields; /* Array of fields, terminated with name==NULL */
|
||||
void *(*newrec)(void);
|
||||
/* Routine to allocate a new record */
|
||||
void (*freerec)(void *record);
|
||||
/* Routine to free an allocated record (that has
|
||||
* not been inserted into the table) */
|
||||
void (*insert)(void *record);
|
||||
/* Routine to insert a record (used when loading,
|
||||
* returns nonzero for success or 0 for failure) */
|
||||
void *(*first)(void), *(*next)(void);
|
||||
/* Routines to iterate through all records (used
|
||||
* when saving) */
|
||||
int (*postload)(void);
|
||||
/* Routine called after all records have been loaded;
|
||||
* returns nonzero for success or 0 for failure */
|
||||
} DBTable;
|
||||
|
||||
/* Container for module-implemented database functions */
|
||||
typedef struct dbmodule_ {
|
||||
/* Load the given table from permanent storage, returning nonzero for
|
||||
* success, zero for failure. */
|
||||
int (*load_table)(DBTable *table);
|
||||
/* Save the given table to permanent storage, returning nonzero for
|
||||
* success, zero for failure */
|
||||
int (*save_table)(DBTable *table);
|
||||
} DBModule;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Macro to return a pointer to a field in a record */
|
||||
#define DB_FIELDPTR(record,field) ((int8 *)(record) + (field)->offset)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialization/cleanup routines. */
|
||||
extern int database_init(int ac, char **av);
|
||||
extern void database_cleanup(void);
|
||||
|
||||
/* Register a new database table. Returns nonzero on success, zero on
|
||||
* error. */
|
||||
#define register_dbtable(table) _register_dbtable((table), THIS_MODULE)
|
||||
extern int _register_dbtable(DBTable *table, const Module *caller);
|
||||
|
||||
/* Unregister a database table. Does nothing if the table was not
|
||||
* registered in the first place. */
|
||||
extern void unregister_dbtable(DBTable *table);
|
||||
|
||||
/* Save all registered database tables to permanent storage. Returns 1 if
|
||||
* all tables were successfully saved or no tables are registered, 0 if
|
||||
* some tables were successfully saved (but some were not), or -1 if no
|
||||
* tables were successfully saved. */
|
||||
extern int save_all_dbtables(void);
|
||||
|
||||
/* Register a database module. Returns nonzero on success, zero on error.
|
||||
* On success, all registered tables which have not already been loaded
|
||||
* will be loaded from permanent storage. Only one database module can be
|
||||
* registered. */
|
||||
extern int register_dbmodule(DBModule *module);
|
||||
|
||||
/* Unregister a database module. Does nothing if the module was not
|
||||
* registered in the first place. */
|
||||
extern void unregister_dbmodule(DBModule *module);
|
||||
|
||||
/* Read a value from a database field. The value buffer is assumed to be
|
||||
* large enough to hold the retrieved value. */
|
||||
extern void get_dbfield(const void *record, const DBField *field,
|
||||
void *buffer);
|
||||
|
||||
/* Store a value to a database field. */
|
||||
extern void put_dbfield(void *record, const DBField *field, const void *value);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* DATABASE_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
279
defs.h
Normal file
279
defs.h
Normal file
@ -0,0 +1,279 @@
|
||||
/* Basic constants, macros and prototypes.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef DEFS_H
|
||||
#define DEFS_H
|
||||
|
||||
/*************************************************************************/
|
||||
/****************** START OF USER-CONFIGURABLE SECTION *******************/
|
||||
/*************************************************************************/
|
||||
|
||||
/******* General configuration *******/
|
||||
|
||||
/* Name of configuration file (in Services directory) */
|
||||
#define IRCSERVICES_CONF PROGRAM ".conf"
|
||||
|
||||
/* Name of module configuration file (in Services directory) */
|
||||
#define MODULES_CONF "modules.conf"
|
||||
|
||||
/* Maximum number of parameters for a configuration directive */
|
||||
#define CONFIG_MAXPARAMS 8
|
||||
|
||||
/* Maximum number of channels to buffer modes for (for MergeChannelModes) */
|
||||
#define MERGE_CHANMODES_MAX 3
|
||||
|
||||
|
||||
/******* NickServ configuration *******/
|
||||
|
||||
/* Default language for newly registered nicks; see language.h for
|
||||
* available languages (LANG_* constants). Unless you're running a
|
||||
* regional network, you should probably leave this at LANG_EN_US. */
|
||||
#define DEF_LANGUAGE LANG_EN_US
|
||||
|
||||
|
||||
/******* OperServ configuration *******/
|
||||
|
||||
/* Define this to enable OperServ's debugging commands (Services root
|
||||
* only). These commands are undocumented; "use the source, Luke!" */
|
||||
/* #define DEBUG_COMMANDS */
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/******************* END OF USER-CONFIGURABLE SECTION ********************/
|
||||
/*************************************************************************/
|
||||
|
||||
|
||||
/* Various buffer sizes */
|
||||
|
||||
/* Size of input buffer (note: this is different from BUFSIZ)
|
||||
* This MUST be big enough to hold at least one full IRC message, or Bad
|
||||
* Things will happen. */
|
||||
#define BUFSIZE 1024
|
||||
|
||||
/* Maximum length of a configuration file line */
|
||||
#define CONFIG_LINEMAX 4096
|
||||
|
||||
/* Size of memory-based log buffer (only used with SHOWALLOCS) */
|
||||
#define LOGMEMSIZE 65536
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* The following constants define the sizes of channel name, nickname, and
|
||||
* password buffers used in Services. These should only be adjusted if
|
||||
* your IRC network allows longer nicknames or channel names _and_ you wish
|
||||
* to allow such names to be used with Services. If your IRC network has
|
||||
* smaller limits, you do not need to change these values; Services will
|
||||
* still work fine, albeit with a tiny amount of wasted memory for each
|
||||
* nickname and channel.
|
||||
*
|
||||
* WARNING: If you change these, you MUST back up your data to an XML file
|
||||
* before making the change, and re-import the data afterwards. Database
|
||||
* files created with different calues of CHANMAX/NICKMAX/PASSMAX are not
|
||||
* compatible!
|
||||
*/
|
||||
|
||||
/* Maximum length of a channel name, including the trailing null. Any
|
||||
* channels with a length longer than CHANMAX-1 (including the leading #)
|
||||
* will not be usable with ChanServ. */
|
||||
#define CHANMAX 64
|
||||
|
||||
/* Maximum length of a nickname, including the trailing null. This MUST be
|
||||
* at least one greater than the maximum allowable nickname length on your
|
||||
* network, or people will run into problems using Services! The default
|
||||
* (32) should work for all current servers. */
|
||||
#define NICKMAX 32
|
||||
|
||||
/* Maximum length of an unencrypted password, including the trailing null. */
|
||||
#define PASSMAX 32
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* For convert-db, we redefine the above values to be large enough for all
|
||||
* potential strings. Do not modify these or convert-db will explode in
|
||||
* your face, painfully. */
|
||||
|
||||
#ifdef CONVERT_DB
|
||||
# undef NICKMAX
|
||||
# undef CHANMAX
|
||||
# undef PASSMAX
|
||||
# define NICKMAX 256
|
||||
# define CHANMAX 512
|
||||
# define PASSMAX 256
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* ---- There should be no need to modify anything below this line. ---- */
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Common includes/prototypes. */
|
||||
|
||||
|
||||
/* glibc seems to require this for the prototype of strsignal(). */
|
||||
#define _GNU_SOURCE
|
||||
|
||||
/* Some AIX boxes define int16 and int32 on their own. Blarph. */
|
||||
#if INTTYPE_WORKAROUND
|
||||
# define int16 builtin_int16
|
||||
# define int32 builtin_int32
|
||||
#endif
|
||||
|
||||
|
||||
/* We have our own encrypt(). */
|
||||
#define encrypt encrypt_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#undef DEFS_H /* kludge to work around Cygwin string.h kludge to work
|
||||
* around problem compiling in gdb... this is stupid */
|
||||
#include <string.h>
|
||||
#define DEFS_H
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#undef encrypt
|
||||
|
||||
#if HAVE_STDINT_H
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_STRINGS_H
|
||||
# include <strings.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_SYS_SELECT_H
|
||||
/* FreeBSD (4.4-STABLE) defines LIST_REMOVE and LIST_FOREACH in
|
||||
* <sys/queue.h>, which is included by <sys/select.h>, so make sure we
|
||||
* include it here before list-array.h defines our own versions of those. */
|
||||
# include <sys/select.h>
|
||||
#endif
|
||||
|
||||
#ifdef _AIX
|
||||
/* Some AIX boxes seem to have bogus includes that don't have these
|
||||
* prototypes. */
|
||||
extern int strcasecmp(const char *, const char *);
|
||||
extern int strncasecmp(const char *, const char *, size_t);
|
||||
# if 0 /* These break on some AIX boxes (4.3.1 reported). */
|
||||
extern int gettimeofday(struct timeval *, struct timezone *);
|
||||
extern int socket(int, int, int);
|
||||
extern int bind(int, struct sockaddr *, int);
|
||||
extern int connect(int, struct sockaddr *, int);
|
||||
extern int shutdown(int, int);
|
||||
# endif
|
||||
# undef FD_ZERO
|
||||
# define FD_ZERO(p) memset((p), 0, sizeof(*(p)))
|
||||
#endif /* _AIX */
|
||||
|
||||
/* Alias stricmp/strnicmp to strcasecmp/strncasecmp if we have the latter
|
||||
* but not the former. */
|
||||
#if !HAVE_STRICMP && HAVE_STRCASECMP
|
||||
# define stricmp strcasecmp
|
||||
# define strnicmp strncasecmp
|
||||
#endif
|
||||
|
||||
/* socklen_t for systems without it. */
|
||||
#if !HAVE_SOCKLEN_T
|
||||
typedef int socklen_t;
|
||||
#endif
|
||||
|
||||
|
||||
#if INTTYPE_WORKAROUND
|
||||
# undef int16
|
||||
# undef int32
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* System/compiler sanity checks. */
|
||||
|
||||
|
||||
/* Filename and pathname maximum lengths: (these are usually defined in
|
||||
* limits.h, but check just in case) */
|
||||
#ifndef NAME_MAX
|
||||
# define NAME_MAX 255
|
||||
#endif
|
||||
#ifndef PATH_MAX
|
||||
# define PATH_MAX 1023
|
||||
#endif
|
||||
|
||||
/* Number of signals available: */
|
||||
#ifndef NSIG
|
||||
# define NSIG 32
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Various generally useful macros. */
|
||||
|
||||
|
||||
/* Make sizeof() return an int regardless of compiler (avoids printf
|
||||
* argument type warnings). */
|
||||
#define sizeof(v) ((int)sizeof(v))
|
||||
|
||||
/* Length of an array: */
|
||||
#define lenof(a) (sizeof(a) / sizeof(*(a)))
|
||||
|
||||
/* Sign of a number: (-1, 0, or 1) */
|
||||
#define sgn(n) ((n)<0 ? -1 : ((n)>0))
|
||||
|
||||
/* Telling compilers about printf()-like functions: */
|
||||
#ifdef __GNUC__
|
||||
# define FORMAT(type,fmt,start) __attribute__((format(type,fmt,start)))
|
||||
#else
|
||||
# define FORMAT(type,fmt,start)
|
||||
#endif
|
||||
|
||||
/* Macros to define a function pointer (E_FUNCPTR declares it extern).
|
||||
* This is needed because GCC doesn't seem to like defining a pointer to a
|
||||
* function with __attribute__s in a single statement. */
|
||||
#ifdef __GNUC__
|
||||
# define FUNCPTR(type,name,rest) \
|
||||
type _##name##_t rest; \
|
||||
typeof(_##name##_t) *name
|
||||
# define E_FUNCPTR(type,name,rest) \
|
||||
type _##name##_t rest; \
|
||||
extern typeof(_##name##_t) *name
|
||||
#else
|
||||
# define FUNCPTR(type,name,rest) type (*name) rest
|
||||
# define E_FUNCPTR(type,name,rest) extern type (*name) rest
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Generic "invalid" pointer value. For use when an "invalid" value is
|
||||
* needed and NULL cannot be used. */
|
||||
|
||||
#define PTR_INVALID ((const char *)-1)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* DEFS_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
324
encrypt.c
Normal file
324
encrypt.c
Normal file
@ -0,0 +1,324 @@
|
||||
/* High-level encryption routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "modules.h"
|
||||
#include "encrypt.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* List of available ciphers. */
|
||||
static CipherInfo *cipherlist;
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Default (no encryption) routines. Used when no encryption is selected. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* encrypt(): Encrypt `src' of length `len' into `dest' of size `size'.
|
||||
* Returns:
|
||||
* 0 on success
|
||||
* +N if the destination buffer is too small; N is the minimum size
|
||||
* buffer required to hold the encrypted text
|
||||
* -1 on other error
|
||||
*/
|
||||
|
||||
static int default_encrypt(const char *src, int len, char *dest, int size)
|
||||
{
|
||||
if (size < PASSMAX) {
|
||||
return PASSMAX;
|
||||
}
|
||||
memset(dest, 0, size);
|
||||
memcpy(dest, src, (len > size) ? size : len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Decrypt `src' into buffer `dest' of size `size'. Returns:
|
||||
* 0 on success
|
||||
* +N if the destination buffer is too small; N is the minimum size
|
||||
* buffer required to hold the decrypted text
|
||||
* -2 if the encryption algorithm does not allow decryption
|
||||
* -1 on other error
|
||||
*/
|
||||
|
||||
static int default_decrypt(const char *src, char *dest, int size)
|
||||
{
|
||||
int passlen;
|
||||
for (passlen = 0; passlen < PASSMAX; passlen++) {
|
||||
if (!src[passlen]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (size < passlen+1) {
|
||||
return passlen+1 - size;
|
||||
}
|
||||
memset(dest, 0, size);
|
||||
memcpy(dest, src, passlen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Check an input password `plaintext' against a stored, encrypted password
|
||||
* `password'. Return value is:
|
||||
* 1 if the password matches
|
||||
* 0 if the password does not match
|
||||
* -1 if an error occurred while checking
|
||||
*/
|
||||
|
||||
static int default_check_password(const char *plaintext, const char *password)
|
||||
{
|
||||
if (strncmp(plaintext, password, PASSMAX) == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* High-level password encryption routines. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Allocate and return a new, empty Password structure. Always succeeds
|
||||
* (smalloc() will throw a signal if memory cannot be allocated).
|
||||
*/
|
||||
|
||||
Password *new_password(void)
|
||||
{
|
||||
Password *password = smalloc(sizeof(*password));
|
||||
init_password(password);
|
||||
return password;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialize a preallocated Password structure. Identical in behavior to
|
||||
* new_password(), except that the passed-in structure is used instead of
|
||||
* allocating a new one, and the structure pointer is not returned.
|
||||
*/
|
||||
|
||||
void init_password(Password *password)
|
||||
{
|
||||
memset(password->password, 0, sizeof(password->password));
|
||||
password->cipher = NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set the contents of a Password structure to the given values. If
|
||||
* cipher is not NULL, a copy of it is made, so the original string may be
|
||||
* disposed of after calling set_password().
|
||||
*/
|
||||
|
||||
void set_password(Password *password,
|
||||
const char password_buffer[PASSMAX],
|
||||
const char *cipher)
|
||||
{
|
||||
memcpy(password->password, password_buffer, PASSMAX);
|
||||
if (cipher) {
|
||||
password->cipher = sstrdup(cipher);
|
||||
} else {
|
||||
password->cipher = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Copy the contents of a Password structure to another Password structure.
|
||||
* The destination password comes first, a la memcpy().
|
||||
*/
|
||||
|
||||
void copy_password(Password *to, const Password *from)
|
||||
{
|
||||
clear_password(to);
|
||||
memcpy(to->password, from->password, sizeof(to->password));
|
||||
if (from->cipher) {
|
||||
to->cipher = sstrdup(from->cipher);
|
||||
} else {
|
||||
to->cipher = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Clear and free memory used by the contents of a Password structure,
|
||||
* without freeing the structure itself. Similar to init_password(), but
|
||||
* assumes that the contents of the Password structure are valid (in
|
||||
* particular, assumes that password->cipher needs to be freed if it is
|
||||
* not NULL).
|
||||
*/
|
||||
|
||||
void clear_password(Password *password)
|
||||
{
|
||||
memset(password->password, 0, sizeof(password->password));
|
||||
free((char *)password->cipher);
|
||||
password->cipher = NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Free a Password structure allocated with new_password(). Does nothing
|
||||
* if NULL is given.
|
||||
*/
|
||||
|
||||
void free_password(Password *password)
|
||||
{
|
||||
if (password) {
|
||||
clear_password(password);
|
||||
free(password);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Encrypt string `plaintext' of length `len', placing the result in
|
||||
* `password'. Returns:
|
||||
* 0 on success
|
||||
* -2 if the encrypted password is too long to fit in the buffer
|
||||
* -1 on other error
|
||||
*/
|
||||
|
||||
int encrypt_password(const char *plaintext, int len, Password *password)
|
||||
{
|
||||
encrypt_func_t low_encrypt = default_encrypt;
|
||||
int res;
|
||||
|
||||
if (EncryptionType) {
|
||||
CipherInfo *ci;
|
||||
LIST_SEARCH(cipherlist, name, EncryptionType, strcmp, ci);
|
||||
if (!ci) {
|
||||
log("encrypt_password(): cipher `%s' not available!",
|
||||
EncryptionType);
|
||||
return -1;
|
||||
}
|
||||
low_encrypt = ci->encrypt;
|
||||
}
|
||||
clear_password(password);
|
||||
res = (*low_encrypt)(plaintext, len, password->password,
|
||||
sizeof(password->password));
|
||||
if (res == 0) {
|
||||
if (EncryptionType) {
|
||||
password->cipher = strdup(EncryptionType);
|
||||
if (!password->cipher) {
|
||||
module_log_perror("strdup() failed in encrypt_password()");
|
||||
clear_password(password);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
clear_password(password);
|
||||
if (res > 0) { /* buffer too small */
|
||||
return -2;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Decrypt `password' into buffer `dest' of length `size'. Returns:
|
||||
* 0 on success
|
||||
* +N if the destination buffer is too small; N is the minimum size
|
||||
* buffer required to hold the decrypted password
|
||||
* -2 if the encryption algorithm does not allow decryption
|
||||
* -1 on other error
|
||||
*/
|
||||
|
||||
int decrypt_password(const Password *password, char *dest, int size)
|
||||
{
|
||||
decrypt_func_t low_decrypt = default_decrypt;
|
||||
|
||||
if (password->cipher) {
|
||||
CipherInfo *ci;
|
||||
LIST_SEARCH(cipherlist, name, password->cipher, strcmp, ci);
|
||||
if (!ci) {
|
||||
log("decrypt_password(): cipher `%s' not available!",
|
||||
password->cipher);
|
||||
return -1;
|
||||
}
|
||||
low_decrypt = ci->decrypt;
|
||||
}
|
||||
return (*low_decrypt)(password->password, dest, size);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Check an input password `plaintext' against a stored, encrypted password
|
||||
* `password'. Return value is:
|
||||
* 1 if the password matches
|
||||
* 0 if the password does not match
|
||||
* -1 if an error occurred while checking
|
||||
*/
|
||||
|
||||
int check_password(const char *plaintext, const Password *password)
|
||||
{
|
||||
check_password_func_t low_check_password = default_check_password;
|
||||
|
||||
if (password->cipher) {
|
||||
CipherInfo *ci;
|
||||
LIST_SEARCH(cipherlist, name, password->cipher, strcmp, ci);
|
||||
if (!ci) {
|
||||
log("check_password(): cipher `%s' not available!",
|
||||
password->cipher);
|
||||
return -1;
|
||||
}
|
||||
low_check_password = ci->check_password;
|
||||
}
|
||||
return (*low_check_password)(plaintext, password->password);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Cipher registration/unregistration. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Register a new cipher. */
|
||||
|
||||
void register_cipher(CipherInfo *ci)
|
||||
{
|
||||
LIST_INSERT(ci, cipherlist);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Unregister a cipher. Does nothing if the cipher was not registered. */
|
||||
|
||||
void unregister_cipher(CipherInfo *ci)
|
||||
{
|
||||
CipherInfo *ci2;
|
||||
LIST_FOREACH (ci2, cipherlist) {
|
||||
if (ci2 == ci) {
|
||||
LIST_REMOVE(ci, cipherlist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
143
encrypt.h
Normal file
143
encrypt.h
Normal file
@ -0,0 +1,143 @@
|
||||
/* Include file for encryption routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef ENCRYPT_H
|
||||
#define ENCRYPT_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Structure encapsulating a password and the type of encryption used to
|
||||
* encrypt it. */
|
||||
|
||||
typedef struct {
|
||||
char password[PASSMAX]; /* The password itself, possibly encrypted */
|
||||
const char *cipher; /* Encryption cipher name, or NULL for none */
|
||||
} Password;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* High-level password manipulation functions. */
|
||||
|
||||
|
||||
/* Allocate and return a new, empty Password structure. Always succeeds
|
||||
* (smalloc() will throw a signal if memory cannot be allocated). */
|
||||
extern Password *new_password(void);
|
||||
|
||||
/* Initialize a preallocated Password structure. Identical in behavior to
|
||||
* new_password(), except that the passed-in structure is used instead of
|
||||
* allocating a new one, and the structure pointer is not returned. */
|
||||
extern void init_password(Password *password);
|
||||
|
||||
/* Set the contents of a Password structure to the given values. If
|
||||
* cipher is not NULL, a copy of it is made, so the original string may be
|
||||
* disposed of after calling set_password(). */
|
||||
extern void set_password(Password *password,
|
||||
const char password_buffer[PASSMAX],
|
||||
const char *cipher);
|
||||
|
||||
/* Copy the contents of a Password structure to another Password structure.
|
||||
* The destination password comes first, a la memcpy(). */
|
||||
extern void copy_password(Password *to, const Password *from);
|
||||
|
||||
/* Clear and free memory used by the contents of a Password structure,
|
||||
* without freeing the structure itself. Similar to init_password(), but
|
||||
* assumes that the contents of the Password structure are valid (in
|
||||
* particular, assumes that password->cipher needs to be freed if it is not
|
||||
* NULL). */
|
||||
extern void clear_password(Password *password);
|
||||
|
||||
/* Free a Password structure allocated with new_password(). Does nothing
|
||||
* if NULL is given. */
|
||||
extern void free_password(Password *password);
|
||||
|
||||
/* Encrypt string `plaintext' of length `len', placing the result in
|
||||
* `password'. Returns:
|
||||
* 0 on success
|
||||
* -2 if the encrypted password is too long to fit in the buffer
|
||||
* -1 on other error */
|
||||
extern int encrypt_password(const char *plaintext, int len,
|
||||
Password *password);
|
||||
|
||||
/* Decrypt `password' into buffer `dest' of length `size'. Returns:
|
||||
* 0 on success
|
||||
* +N if the destination buffer is too small; N is the minimum size
|
||||
* buffer required to hold the decrypted password
|
||||
* -2 if the encryption algorithm does not allow decryption
|
||||
* -1 on other error */
|
||||
extern int decrypt_password(const Password *password, char *dest, int size);
|
||||
|
||||
/* Check an input password `plaintext' against a stored, encrypted password
|
||||
* `password'. Return value is:
|
||||
* 1 if the password matches
|
||||
* 0 if the password does not match
|
||||
* -1 if an error occurred while checking */
|
||||
extern int check_password(const char *plaintext, const Password *password);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Low-level encryption/decryption functions. Each encryption module must
|
||||
* implement all of these functions. */
|
||||
|
||||
|
||||
/* encrypt(): Encrypt `src' of length `len' into `dest' of size `size'.
|
||||
* Returns:
|
||||
* 0 on success
|
||||
* +N if the destination buffer is too small; N is the minimum size
|
||||
* buffer required to hold the encrypted text
|
||||
* -1 on other error */
|
||||
typedef int (*encrypt_func_t)(const char *src, int len, char *dest, int size);
|
||||
|
||||
/* Decrypt `src' into buffer `dest' of size `size'. Returns:
|
||||
* 0 on success
|
||||
* +N if the destination buffer is too small; N is the minimum size
|
||||
* buffer required to hold the decrypted text
|
||||
* -2 if the encryption algorithm does not allow decryption
|
||||
* -1 on other error */
|
||||
typedef int (*decrypt_func_t)(const char *src, char *dest, int size);
|
||||
|
||||
/* Check an input password `plaintext' against a stored, encrypted password
|
||||
* `password'. Return value is:
|
||||
* 1 if the password matches
|
||||
* 0 if the password does not match
|
||||
* -1 if an error occurred while checking */
|
||||
typedef int (*check_password_func_t)(const char *plaintext,
|
||||
const char *password);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Registration and de-registration of ciphers (encryption modules). */
|
||||
|
||||
typedef struct cipherinfo_ CipherInfo;
|
||||
struct cipherinfo_ {
|
||||
CipherInfo *next, *prev; /* Internal use only */
|
||||
const char *name; /* Cipher name (use the module name) */
|
||||
encrypt_func_t encrypt; /* Cipher functions */
|
||||
decrypt_func_t decrypt;
|
||||
check_password_func_t check_password;
|
||||
};
|
||||
|
||||
/* Register a new cipher. */
|
||||
extern void register_cipher(CipherInfo *ci);
|
||||
|
||||
/* Unregister a cipher. Does nothing if the cipher was not registered. */
|
||||
extern void unregister_cipher(CipherInfo *ci);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* ENCRYPT_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
329
extern.h
Normal file
329
extern.h
Normal file
@ -0,0 +1,329 @@
|
||||
/* General prototypes and external variable declarations.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef EXTERN_H
|
||||
#define EXTERN_H
|
||||
|
||||
|
||||
#define E extern /* This is used in other files as well */
|
||||
|
||||
|
||||
/**** actions.c ****/
|
||||
|
||||
E int actions_init(int ac, char **av);
|
||||
E void actions_cleanup(void);
|
||||
E int bad_password(const char *service, User *u, const char *what);
|
||||
E void clear_channel(Channel *chan, int what, const void *param);
|
||||
E const char *set_clear_channel_sender(const char *newsender);
|
||||
E void kill_user(const char *source, const char *user, const char *reason);
|
||||
E void set_topic(const char *source, Channel *c, const char *topic,
|
||||
const char *setter, time_t time);
|
||||
E void set_cmode(const char *sender, Channel *channel, ...);
|
||||
|
||||
|
||||
/**** channels.c ****/
|
||||
|
||||
E Channel *get_channel(const char *chan);
|
||||
E Channel *first_channel(void);
|
||||
E Channel *next_channel(void);
|
||||
|
||||
E int channel_init(int ac, char **av);
|
||||
E void channel_cleanup(void);
|
||||
E void get_channel_stats(long *nrec, long *memuse);
|
||||
|
||||
E Channel *chan_adduser(User *user, const char *chan, int32 modes);
|
||||
E void chan_deluser(User *user, Channel *c);
|
||||
E int chan_has_ban(const char *chan, const char *ban);
|
||||
|
||||
E void do_cmode(const char *source, int ac, char **av);
|
||||
E void do_topic(const char *source, int ac, char **av);
|
||||
|
||||
|
||||
/**** compat.c ****/
|
||||
|
||||
#if !HAVE_HSTRERROR
|
||||
# undef hstrerror
|
||||
E const char *hstrerror(int h_errnum);
|
||||
#endif
|
||||
#if !HAVE_SNPRINTF
|
||||
# if BAD_SNPRINTF
|
||||
# define snprintf my_snprintf
|
||||
# endif
|
||||
# define vsnprintf my_vsnprintf
|
||||
E int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
|
||||
E int snprintf(char *buf, size_t size, const char *fmt, ...);
|
||||
#endif
|
||||
#if !HAVE_STRTOK
|
||||
# undef strtok
|
||||
E char *strtok(char *str, const char *delim);
|
||||
#endif
|
||||
#if !HAVE_STRICMP && !HAVE_STRCASECMP
|
||||
# undef stricmp
|
||||
# undef strnicmp
|
||||
E int stricmp(const char *s1, const char *s2);
|
||||
E int strnicmp(const char *s1, const char *s2, size_t len);
|
||||
#endif
|
||||
#if !HAVE_STRDUP && !MEMCHECKS
|
||||
# undef strdup
|
||||
E char *strdup(const char *s);
|
||||
#endif
|
||||
#if !HAVE_STRSPN
|
||||
# undef strspn
|
||||
# undef strcspn
|
||||
E size_t strspn(const char *s, const char *accept);
|
||||
E size_t strcspn(const char *s, const char *reject);
|
||||
#endif
|
||||
#if !HAVE_STRERROR
|
||||
# undef strerror
|
||||
E char *strerror(int errnum);
|
||||
#endif
|
||||
#if !HAVE_STRSIGNAL
|
||||
# undef strsignal
|
||||
E char *strsignal(int signum);
|
||||
#endif
|
||||
|
||||
|
||||
/**** init.c ****/
|
||||
|
||||
E char **LoadModules;
|
||||
E int LoadModules_count;
|
||||
E char **LoadLanguageText;
|
||||
E int LoadLanguageText_count;
|
||||
|
||||
E char * RemoteServer;
|
||||
E int32 RemotePort;
|
||||
E char * RemotePassword;
|
||||
E char * LocalHost;
|
||||
E int32 LocalPort;
|
||||
|
||||
E char * ServerName;
|
||||
E char * ServerDesc;
|
||||
E char * ServiceUser;
|
||||
E char * ServiceHost;
|
||||
|
||||
E char * LogFilename;
|
||||
E char PIDFilename[PATH_MAX+1];
|
||||
E char * MOTDFilename;
|
||||
E char * LockFilename;
|
||||
|
||||
E int16 DefTimeZone;
|
||||
|
||||
E int NoBouncyModes;
|
||||
E int NoSplitRecovery;
|
||||
E int StrictPasswords;
|
||||
E int NoAdminPasswordCheck;
|
||||
E int32 BadPassLimit;
|
||||
E time_t BadPassTimeout;
|
||||
E int32 BadPassWarning;
|
||||
E int32 IgnoreDecay;
|
||||
E double IgnoreThreshold;
|
||||
E time_t UpdateTimeout;
|
||||
E time_t WarningTimeout;
|
||||
E int32 ReadTimeout;
|
||||
E int32 TimeoutCheck;
|
||||
E time_t PingFrequency;
|
||||
E int32 MergeChannelModes;
|
||||
E int32 TotalNetBufferSize;
|
||||
E int32 NetBufferSize;
|
||||
E int32 NetBufferLimitInactive;
|
||||
E int32 NetBufferLimitIgnore;
|
||||
|
||||
E char * EncryptionType;
|
||||
E char * GuestNickPrefix;
|
||||
E char **RejectEmail;
|
||||
E int RejectEmail_count;
|
||||
E int32 ListMax;
|
||||
E int LogMaxUsers;
|
||||
E int EnableGetpass;
|
||||
E int WallAdminPrivs;
|
||||
|
||||
E int introduce_user(const char *user);
|
||||
E int init(int ac, char **av);
|
||||
E int reconfigure(void);
|
||||
E void cleanup(void);
|
||||
|
||||
|
||||
/**** ignore.c ****/
|
||||
|
||||
E void ignore_init(User *u);
|
||||
E void ignore_update(User *u, uint32 msec);
|
||||
|
||||
|
||||
/**** main.c ****/
|
||||
|
||||
E const char *services_dir;
|
||||
E int debug;
|
||||
E int readonly;
|
||||
E int nofork;
|
||||
E int noexpire;
|
||||
E int noakill;
|
||||
E int forceload;
|
||||
E int encrypt_all;
|
||||
|
||||
E int linked;
|
||||
E int quitting;
|
||||
E int delayed_quit;
|
||||
E int restart;
|
||||
E char quitmsg[BUFSIZE];
|
||||
E char inbuf[BUFSIZE];
|
||||
E Socket *servsock;
|
||||
E int save_data;
|
||||
E time_t start_time;
|
||||
E int openlog_failed, openlog_errno;
|
||||
E int cb_connect;
|
||||
E int cb_save_complete;
|
||||
|
||||
E void connect_callback(Socket *s, void *param_unused);
|
||||
E void disconnect_callback(Socket *s, void *param);
|
||||
E void readfirstline_callback(Socket *s, void *param_unused);
|
||||
E void readline_callback(Socket *s, void *param_unused);
|
||||
|
||||
E int lock_data(void);
|
||||
E int is_data_locked(void);
|
||||
E int unlock_data(void);
|
||||
E void save_data_now(void);
|
||||
|
||||
|
||||
/**** messages.c ****/
|
||||
|
||||
E int allow_ignore;
|
||||
/* rest is in messages.h */
|
||||
|
||||
|
||||
/**** misc.c ****/
|
||||
|
||||
E unsigned char irc_lowertable[256];
|
||||
E unsigned char irc_tolower(char c);
|
||||
#define irc_tolower(c) (irc_lowertable[(uint8)(c)]) /* for speed */
|
||||
E int irc_stricmp(const char *s1, const char *s2);
|
||||
E int irc_strnicmp(const char *s1, const char *s2, int max);
|
||||
E char *strscpy(char *d, const char *s, size_t len);
|
||||
E char *strmove(char *d, const char *s);
|
||||
E char *stristr(const char *s1, const char *s2);
|
||||
E char *strupper(char *s);
|
||||
E char *strlower(char *s);
|
||||
E char *strnrepl(char *s, int32 size, const char *old, const char *new);
|
||||
E char *strtok_remaining(void);
|
||||
/* strbcpy(): strscpy() for a char array destination; uses sizeof(d) as
|
||||
* the size to copy */
|
||||
#define strbcpy(d,s) strscpy((d), (s), sizeof(d))
|
||||
|
||||
E char *merge_args(int argc, char **argv);
|
||||
|
||||
E int match_wild(const char *pattern, const char *str);
|
||||
E int match_wild_nocase(const char *pattern, const char *str);
|
||||
|
||||
E unsigned char valid_nick_table[0x10000];
|
||||
E unsigned char valid_chan_table[0x10000];
|
||||
E int valid_nick(const char *str);
|
||||
E int valid_chan(const char *str);
|
||||
E int valid_domain(const char *str);
|
||||
E int valid_email(const char *str);
|
||||
E int valid_url(const char *str);
|
||||
E int rejected_email(const char *email);
|
||||
|
||||
E uint32 time_msec(void);
|
||||
E time_t strtotime(const char *str, char **endptr);
|
||||
E int dotime(const char *s);
|
||||
|
||||
E uint8 *pack_ip(const char *ipaddr);
|
||||
E char *unpack_ip(const uint8 *ip);
|
||||
E uint8 *pack_ip6(const char *ipaddr);
|
||||
E char *unpack_ip6(const uint8 *ip);
|
||||
|
||||
E int encode_base64(const void *in, int insize, char *out, int outsize);
|
||||
E int decode_base64(const char *in, void *out, int outsize);
|
||||
|
||||
typedef int (*range_callback_t)(int num, va_list args);
|
||||
E int process_numlist(const char *numstr, int *count_ret,
|
||||
range_callback_t callback, ...);
|
||||
|
||||
E long atolsafe(const char *s, long min, long max);
|
||||
|
||||
|
||||
/**** process.c ****/
|
||||
|
||||
E int split_buf(char *buf, char ***argv, int colon_special);
|
||||
E int process_init(int ac, char **av);
|
||||
E void process_cleanup(void);
|
||||
E void process(void);
|
||||
|
||||
|
||||
/**** servers.c ****/
|
||||
|
||||
E Server *get_server(const char *servername);
|
||||
E Server *first_server(void);
|
||||
E Server *next_server(void);
|
||||
|
||||
E int server_init(int ac, char **av);
|
||||
E void server_cleanup(void);
|
||||
E void get_server_stats(long *nservers, long *memuse);
|
||||
E void do_server(const char *source, int ac, char **av);
|
||||
E void do_squit(const char *source, int ac, char **av);
|
||||
|
||||
|
||||
/**** signals.c ****/
|
||||
|
||||
E void init_signals(void);
|
||||
E void do_sigsetjmp(void *bufptr); /* actually a sigjmp_buf * */
|
||||
E void enable_signals(void);
|
||||
E void disable_signals(void);
|
||||
|
||||
|
||||
/**** users.c ****/
|
||||
|
||||
E int32 usercnt, opcnt;
|
||||
|
||||
E User *get_user(const char *nick);
|
||||
E User *first_user(void);
|
||||
E User *next_user(void);
|
||||
|
||||
E void quit_user(User *user, const char *quitmsg, int is_kill);
|
||||
E int user_init(int ac, char **av);
|
||||
E void user_cleanup(void);
|
||||
E void get_user_stats(long *nusers, long *memuse);
|
||||
|
||||
E int do_nick(const char *source, int ac, char **av);
|
||||
E void do_join(const char *source, int ac, char **av);
|
||||
E void do_part(const char *source, int ac, char **av);
|
||||
E void do_kick(const char *source, int ac, char **av);
|
||||
E void do_umode(const char *source, int ac, char **av);
|
||||
E void do_quit(const char *source, int ac, char **av);
|
||||
E void do_kill(const char *source, int ac, char **av);
|
||||
|
||||
E Channel *join_channel(User *user, const char *channel, int32 modes);
|
||||
E int part_channel(User *user, const char *channel, int callback,
|
||||
const char *param, const char *source);
|
||||
E void part_all_channels(User *user);
|
||||
|
||||
E int is_oper(const User *user);
|
||||
E Channel *is_on_chan(const User *user, const char *chan);
|
||||
E int is_chanop(const User *user, const char *chan);
|
||||
E int is_voiced(const User *user, const char *chan);
|
||||
|
||||
E int match_usermask(const char *mask, const User *user);
|
||||
E void split_usermask(const char *mask, char **nick, char **user, char **host);
|
||||
E char *create_mask(const User *user, int use_fakehost);
|
||||
|
||||
E char *make_guest_nick(void);
|
||||
E int is_guest_nick(const char *nick);
|
||||
|
||||
|
||||
|
||||
#endif /* EXTERN_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
322
hash.h
Normal file
322
hash.h
Normal file
@ -0,0 +1,322 @@
|
||||
/* Declarations for hash tables.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
/* This file allows code to define a simple hash table and associated
|
||||
* functions using a simple macro. After including this file, hash tables
|
||||
* can be defined with DEFINE_HASH() or DEFINE_HASH_SCALAR(); the former is
|
||||
* for string (char *) keys, the latter for scalar (int, etc.) keys. The
|
||||
* format for these macros is:
|
||||
* DEFINE_HASH[_SCALAR](name, type, keyfield [, keytype])
|
||||
* where:
|
||||
* `name' is the name to be used in the hash table functions,
|
||||
* `type' is the type of the elements to be stored in the hash table
|
||||
* (e.g. `struct mystruct' or `MyType'); this must be a
|
||||
* structured type,
|
||||
* `keyfield' is the field in `type' containing the key to be used to
|
||||
* index the element, and
|
||||
* `keytype' (only for DEFINE_HASH_SCALAR) is the type of `keyfield'.
|
||||
*
|
||||
* The default hash function:
|
||||
* - if HASH_SORTED is defined to a nonzero value, converts the first
|
||||
* two bytes of the value of `keyfield' to a pair of five-bit values,
|
||||
* for a total of 1024 slots, with the conversion controlled by the
|
||||
* `hashlookup' array (defined static const by this file);
|
||||
* - if HASH_SORTED is not defined or is defined to 0, treats `keyfield'
|
||||
* as a string and generates a hash key from the entire string,
|
||||
* hashing into a table of 65537 slots.
|
||||
* If you want a different hash function, redefine HASHFUNC and HASHSIZE
|
||||
* (see below) before defining the hash table; in particular, scalar keys
|
||||
* cannot be used with the default hash function, and require a different
|
||||
* hash function to be defined.
|
||||
*
|
||||
* The functions defined by this file are as follows (the strings `<name>',
|
||||
* `<type>', and `<keytype>' refer to the corresponding parameters given to
|
||||
* the DEFINE_HASH[_SCALAR] macro):
|
||||
*
|
||||
* void add_<name>(<type> *node)
|
||||
* Adds the given node to the hash table.
|
||||
*
|
||||
* void del_<name>(<type> *node)
|
||||
* Removes the given node from the hash table.
|
||||
*
|
||||
* <type> *get_<name>(const char *key) (for string keys)
|
||||
* <type> *get_<name>(<keytype> key) (for scalar keys)
|
||||
* If an element with the given key is stored in the hash table,
|
||||
* returns a pointer to that element; otherwise, returns NULL.
|
||||
*
|
||||
* <type> *first_<name>(void)
|
||||
* <type> *next_<name>(void)
|
||||
* These functions iterate over all elements in the hash table.
|
||||
* For hashes with string keys, elements are returned in lexical
|
||||
* order by key if HASH_SORTED is defined (see below).
|
||||
* first_<name>() initializes the iterator to the first element in
|
||||
* the hash table and returns it; next_<name>() returns subsequent
|
||||
* elements, one at a time, until all elements have been returned, at
|
||||
* which point it returns NULL until first_<name>() is called again.
|
||||
* If there are no elements in the hash table, first_<name>() will
|
||||
* return NULL (as will next_<name>()). It is safe to delete
|
||||
* elements, including the current element, while iterating. If an
|
||||
* element is added while iterating, it is undefined whether that
|
||||
* element will be returned by next_<name>() before the end of the
|
||||
* hash table is reached.
|
||||
*
|
||||
* This file and the hash tables it generates are controlled by the
|
||||
* following #define's:
|
||||
* EXPIRE_CHECK(node): Define to an expression used to check whether a
|
||||
* given entry has expired. Initially defined to
|
||||
* `0' (no expiration) if not otherwise defined when
|
||||
* this file is included.
|
||||
* HASH_STATIC: Define to either `static' or nothing, to control whether
|
||||
* hash functions are declared static or not. Defaults to
|
||||
* nothing (functions are not static). The hash table
|
||||
* structures themselves are always static.
|
||||
* HASHFUNC/ Define these if you want your own hash function. Defaults
|
||||
* HASHSIZE: to the standard hash function. If you redefine these once,
|
||||
* you can restore them to the defaults by defining them to
|
||||
* DEFAULT_HASHFUNC(key) and DEFAULT_HASHSIZE.
|
||||
* HASH_SORTED: Define (to a nonzero value) or undefine _before_
|
||||
* including this file_ to select the type of default hash
|
||||
* function (see above).
|
||||
*
|
||||
* Note that the above defines (except HASH_SORTED) take effect when hash
|
||||
* tables are actually defined, not when this file is included, so the
|
||||
* defines can be changed at will for different hashes.
|
||||
*/
|
||||
|
||||
#ifndef HASH_H
|
||||
#define HASH_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef EXPIRE_CHECK
|
||||
# define EXPIRE_CHECK(node) 0
|
||||
#endif
|
||||
|
||||
#ifndef HASH_STATIC
|
||||
# define HASH_STATIC /*nothing*/
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Generic hash function and associated lookup table. */
|
||||
|
||||
#if HASH_SORTED
|
||||
|
||||
# define DEFAULT_HASHFUNC(key) \
|
||||
(__hashlookup[(uint8)(*(key))]<<5 \
|
||||
| (*(key) ? __hashlookup[(uint8)((key)[1])] : 0))
|
||||
# define DEFAULT_HASHSIZE 1024
|
||||
static const uint8 __hashlookup[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
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,26,27,28,29, 0, 0,
|
||||
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,26,27,28,29,30,30,
|
||||
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
|
||||
};
|
||||
|
||||
#else /* !HASH_SORTED */
|
||||
|
||||
# define DEFAULT_HASHFUNC(key) (__default_hashfunc(key))
|
||||
# define DEFAULT_HASHSIZE 65537
|
||||
static const uint8 __hashlookup_unsorted[256] = {
|
||||
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
|
||||
0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
|
||||
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
|
||||
0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
|
||||
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
|
||||
0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
|
||||
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
|
||||
0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
|
||||
|
||||
0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
|
||||
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
|
||||
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
|
||||
0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x5E,0x5F,
|
||||
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
|
||||
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
|
||||
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
|
||||
0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
|
||||
|
||||
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
|
||||
0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
|
||||
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
|
||||
0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
|
||||
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,
|
||||
0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
|
||||
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,
|
||||
0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
|
||||
|
||||
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,
|
||||
0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
|
||||
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,
|
||||
0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
|
||||
0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,
|
||||
0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
|
||||
0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,
|
||||
0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
|
||||
};
|
||||
static inline int __default_hashfunc(const char *key_) {
|
||||
const uint8 *key = (const uint8 *)key_;
|
||||
int hash = 0;
|
||||
while (*key) {
|
||||
hash = (hash*31 + __hashlookup_unsorted[*key]) % DEFAULT_HASHSIZE;
|
||||
key++;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
#endif /* HASH_SORTED */
|
||||
|
||||
|
||||
#ifndef HASHFUNC
|
||||
# define HASHFUNC(key) DEFAULT_HASHFUNC(key)
|
||||
#endif
|
||||
|
||||
#ifndef HASHSIZE
|
||||
# define HASHSIZE DEFAULT_HASHSIZE
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Templates for creating hash table definitions and functions.
|
||||
* HASH_STATIC should be defined to either `static' or nothing beforehand.
|
||||
*/
|
||||
|
||||
#undef DEFINE_HASHTABLE
|
||||
#define DEFINE_HASHTABLE(name, type) \
|
||||
static type *hashtable_##name[HASHSIZE];
|
||||
|
||||
#undef DEFINE_HASH_ITER
|
||||
#define DEFINE_HASH_ITER(name,type) \
|
||||
static int hashpos_##name; \
|
||||
static type *hashiter_##name; \
|
||||
static void _next_##name(void) \
|
||||
{ \
|
||||
if (hashiter_##name) \
|
||||
hashiter_##name = hashiter_##name->next; \
|
||||
while (hashiter_##name == NULL && ++hashpos_##name < HASHSIZE) \
|
||||
hashiter_##name = hashtable_##name[hashpos_##name]; \
|
||||
} \
|
||||
HASH_STATIC type *next_##name(void) \
|
||||
{ \
|
||||
type *retval; \
|
||||
do { \
|
||||
retval = hashiter_##name; \
|
||||
if (retval == NULL) \
|
||||
return NULL; \
|
||||
_next_##name(); \
|
||||
} while (!noexpire && EXPIRE_CHECK(retval)); \
|
||||
return retval; \
|
||||
} \
|
||||
HASH_STATIC type *first_##name(void) \
|
||||
{ \
|
||||
hashpos_##name = -1; \
|
||||
hashiter_##name = NULL; \
|
||||
_next_##name(); \
|
||||
return next_##name(); \
|
||||
}
|
||||
|
||||
#undef DEFINE_HASH_ADD
|
||||
#define DEFINE_HASH_ADD(name,type,keyfield) \
|
||||
HASH_STATIC void add_##name(type *node) \
|
||||
{ \
|
||||
type **listptr = &hashtable_##name[HASHFUNC(node->keyfield)]; \
|
||||
LIST_INSERT_ORDERED(node, *listptr, irc_stricmp, keyfield); \
|
||||
}
|
||||
|
||||
#undef DEFINE_HASH_ADD_SCALAR
|
||||
#define DEFINE_HASH_ADD_SCALAR(name,type,keyfield) \
|
||||
HASH_STATIC void add_##name(type *node) \
|
||||
{ \
|
||||
type **listptr = &hashtable_##name[HASHFUNC(node->keyfield)]; \
|
||||
LIST_INSERT(node, *listptr); \
|
||||
}
|
||||
|
||||
#undef DEFINE_HASH_DEL
|
||||
#define DEFINE_HASH_DEL(name,type,keyfield) \
|
||||
HASH_STATIC void del_##name(type *node) \
|
||||
{ \
|
||||
if (node == hashiter_##name) \
|
||||
_next_##name(); \
|
||||
LIST_REMOVE(node, hashtable_##name[HASHFUNC(node->keyfield)]); \
|
||||
}
|
||||
|
||||
#undef DEFINE_HASH_GET
|
||||
#define DEFINE_HASH_GET(name,type,keyfield) \
|
||||
HASH_STATIC type *get_##name(const char *what) \
|
||||
{ \
|
||||
type *result; \
|
||||
LIST_SEARCH_ORDERED(hashtable_##name[HASHFUNC(what)], \
|
||||
keyfield, what, irc_stricmp, result); \
|
||||
if (result && !noexpire && EXPIRE_CHECK(result)) \
|
||||
result = NULL; \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#undef DEFINE_HASH_GET_SCALAR
|
||||
#define DEFINE_HASH_GET_SCALAR(name,type,keyfield,keytype) \
|
||||
HASH_STATIC type *get_##name(keytype what) \
|
||||
{ \
|
||||
type *result; \
|
||||
LIST_SEARCH_SCALAR(hashtable_##name[HASHFUNC(what)], \
|
||||
keyfield, what, result); \
|
||||
if (result && !noexpire && EXPIRE_CHECK(result)) \
|
||||
result = NULL; \
|
||||
return result; \
|
||||
}
|
||||
|
||||
/* Macros to create everything at once for a given type.
|
||||
* name: Name to use in the hash functions (the XXX in add_XXX, etc.)
|
||||
* type: Type of the hash (NickInfo, etc.)
|
||||
* keyfield: Key field in given type
|
||||
*/
|
||||
|
||||
#undef DEFINE_HASH
|
||||
#define DEFINE_HASH(name,type,keyfield) \
|
||||
DEFINE_HASHTABLE(name, type) \
|
||||
DEFINE_HASH_ITER(name, type) \
|
||||
DEFINE_HASH_ADD(name, type, keyfield) \
|
||||
DEFINE_HASH_DEL(name, type, keyfield) \
|
||||
DEFINE_HASH_GET(name, type, keyfield)
|
||||
|
||||
#undef DEFINE_HASH_SCALAR
|
||||
#define DEFINE_HASH_SCALAR(name,type,keyfield,keytype) \
|
||||
DEFINE_HASHTABLE(name, type) \
|
||||
DEFINE_HASH_ITER(name, type) \
|
||||
DEFINE_HASH_ADD_SCALAR(name, type, keyfield) \
|
||||
DEFINE_HASH_DEL(name, type, keyfield) \
|
||||
DEFINE_HASH_GET_SCALAR(name, type, keyfield, keytype)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* HASH_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
86
ignore.c
Normal file
86
ignore.c
Normal file
@ -0,0 +1,86 @@
|
||||
/* Ignore handling.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* ignore_init: Initialize ignore-related fields of a new User structure. */
|
||||
|
||||
void ignore_init(User *u)
|
||||
{
|
||||
u->ignore = 0;
|
||||
u->lastcmd = time_msec();
|
||||
u->lastcmd_s = time(NULL);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* ignore_update: Update the user's ignore level. The "ignore level" is a
|
||||
* measure of how much time Services has spent processing
|
||||
* this user's commands, and is calculated as a decaying
|
||||
* average of the fraction of time spent on the user's
|
||||
* commands over the total time Services has been running--
|
||||
* more specifically, the average over time of a function
|
||||
* whose value is 1 when Services is executing a command
|
||||
* from the user and 0 at all other times. The rate of
|
||||
* decay of the average, and hence the rate of response to
|
||||
* new commands, is dependent on the IgnoreDecay
|
||||
* configuration setting: the average decays by half every
|
||||
* IgnoreDecay milliseconds (note that the value is given as
|
||||
* seconds in the configuration file).
|
||||
* The `msec' parameter to this function is the length
|
||||
* of time taken by the most recent command (which caused
|
||||
* this update). `msec' may be specified as zero to update
|
||||
* the user's ignore level at any time.
|
||||
*/
|
||||
|
||||
void ignore_update(User *u, uint32 msec)
|
||||
{
|
||||
time_t now;
|
||||
uint32 now_msec;
|
||||
|
||||
if (!IgnoreDecay) {
|
||||
/* Ignore code disabled, just return */
|
||||
return;
|
||||
}
|
||||
now = time(NULL);
|
||||
now_msec = time_msec();
|
||||
if (now - u->lastcmd_s > 1000000) {
|
||||
/* Millisecond counter may have overflowed, just reset to 0 */
|
||||
u->ignore = 0;
|
||||
} else {
|
||||
double zerolen = (now_msec - msec) - u->lastcmd;
|
||||
if (zerolen > 0)
|
||||
u->ignore *= pow(2, -(zerolen / IgnoreDecay));
|
||||
}
|
||||
if (msec) {
|
||||
double factor;
|
||||
while (msec > IgnoreDecay) {
|
||||
u->ignore = u->ignore*0.5 + 0.5;
|
||||
msec -= IgnoreDecay;
|
||||
}
|
||||
factor = pow(2, -((double)msec / IgnoreDecay));
|
||||
u->ignore = u->ignore*factor + (1-factor);
|
||||
}
|
||||
u->lastcmd = now_msec;
|
||||
u->lastcmd_s = now;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
41
install-script
Normal file
41
install-script
Normal file
@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
|
||||
SRC=""; DEST=""; MODE=""; USER=""; GROUP=""; ISDIR=""
|
||||
export SRC DEST MODE USER GROUP ISDIR
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-m) MODE=$2; shift; shift;;
|
||||
-u) USER=$2; shift; shift;;
|
||||
-g) GROUP=$2; shift; shift;;
|
||||
-d) ISDIR=1; shift;;
|
||||
-c) shift;;
|
||||
*) SRC="$DEST"; DEST="$1"; shift;;
|
||||
esac
|
||||
done
|
||||
if [ ! "$DEST" ]; then
|
||||
echo >&2 "Usage: $0 [-c] [-m mode] [-u user] [-g group] source dest"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$ISDIR" ]; then
|
||||
mkdir -p "$DEST"
|
||||
else
|
||||
if [ -d "$DEST" ]; then
|
||||
DEST="$DEST/$SRC"
|
||||
fi
|
||||
/bin/cp -p "$SRC" "$DEST"
|
||||
fi
|
||||
if [ "$USER" ]; then
|
||||
/bin/chown "$USER" "$DEST"
|
||||
fi
|
||||
if [ "$GROUP" ]; then
|
||||
/bin/chgrp "$GROUP" "$DEST"
|
||||
fi
|
||||
if [ "$MODE" ]; then
|
||||
/bin/chmod "$MODE" "$DEST"
|
||||
fi
|
||||
|
||||
# Local variables:
|
||||
# indent-tabs-mode: nil
|
||||
# End:
|
||||
#
|
||||
# vim: expandtab shiftwidth=4:
|
960
language.c
Normal file
960
language.c
Normal file
@ -0,0 +1,960 @@
|
||||
/* Multi-language support.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#define LANGSTR_ARRAY /* define langstrs[] in langstrs.h, via language.h */
|
||||
|
||||
#include "services.h"
|
||||
#include "language.h"
|
||||
|
||||
/* Needed for NickGroupInfo structure definition (used by getstring() and
|
||||
* strftime_lang()) */
|
||||
#include "modules/nickserv/nickserv.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Indexes of available languages (exported), terminated by -1: */
|
||||
int langlist[NUM_LANGS+1];
|
||||
|
||||
/* List and count of available strings: */
|
||||
static char **langstrs;
|
||||
int num_strings;
|
||||
|
||||
/* The list of lists of messages. */
|
||||
static char **langtexts[NUM_LANGS];
|
||||
/* Pointers to the original data, in case external files are loaded later. */
|
||||
static char **origtexts[NUM_LANGS];
|
||||
|
||||
/* Order in which languages should be displayed: (alphabetical in English) */
|
||||
static int langorder[] = {
|
||||
LANG_EN_US, /* English (US) */
|
||||
LANG_NL, /* Dutch */
|
||||
LANG_FR, /* French */
|
||||
LANG_DE, /* German */
|
||||
LANG_HU, /* Hungarian */
|
||||
/* LANG_IT,*/ /* Italian */
|
||||
LANG_JA_EUC, /* Japanese (EUC encoding) */
|
||||
LANG_JA_SJIS, /* Japanese (SJIS encoding) */
|
||||
/* LANG_PT,*/ /* Portugese */
|
||||
LANG_RU, /* Russian */
|
||||
LANG_ES, /* Spanish */
|
||||
LANG_TR, /* Turkish */
|
||||
};
|
||||
|
||||
/* Filenames for language files: */
|
||||
static struct {
|
||||
int num;
|
||||
const char *filename;
|
||||
} filenames[] = {
|
||||
{ LANG_EN_US, "en_us" },
|
||||
{ LANG_NL, "nl" },
|
||||
{ LANG_FR, "fr" },
|
||||
{ LANG_DE, "de" },
|
||||
{ LANG_HU, "hu" },
|
||||
{ LANG_IT, "it" },
|
||||
{ LANG_JA_EUC, "ja_euc" },
|
||||
{ LANG_JA_SJIS, "ja_sjis" },
|
||||
{ LANG_PT, "pt" },
|
||||
{ LANG_RU, "ru" },
|
||||
{ LANG_ES, "es" },
|
||||
{ LANG_TR, "tr" },
|
||||
{ -1, NULL }
|
||||
};
|
||||
|
||||
/* Mapping of language strings (to allow on-the-fly replacement of strings) */
|
||||
static int *langmap;
|
||||
|
||||
/* Array indicating which languages were actually loaded (needed since NULL
|
||||
* langtexts[] pointers are redirected to DEF_LANGUAGE) */
|
||||
static int is_loaded[NUM_LANGS];
|
||||
|
||||
/* Index of the first extra (non-base) string */
|
||||
#define FIRST_EXTRA_STRING (NUM_BASE_STRINGS)
|
||||
/* Is the given string index a base string index? */
|
||||
#define IS_BASE_STRING(n) ((n) < FIRST_EXTRA_STRING)
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Helper functions. */
|
||||
|
||||
static inline int read_int32(int32 *ptr, FILE *f)
|
||||
{
|
||||
int a = fgetc(f);
|
||||
int b = fgetc(f);
|
||||
int c = fgetc(f);
|
||||
int d = fgetc(f);
|
||||
if (a == EOF || b == EOF || c == EOF || d == EOF)
|
||||
return -1;
|
||||
*ptr = a<<24 | b<<16 | c<<8 | d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int read_uint32(uint32 *ptr, FILE *f)
|
||||
{
|
||||
return read_int32((int32 *)ptr, f);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Load a language file, storing the data in origtexts[index]. */
|
||||
|
||||
static void load_lang(int index, const char *filename)
|
||||
{
|
||||
char buf[256];
|
||||
FILE *f;
|
||||
uint32 num, size, i;
|
||||
char *data = NULL;
|
||||
|
||||
log_debug(1, "Loading language %d from file `languages/%s'",
|
||||
index, filename);
|
||||
snprintf(buf, sizeof(buf), "languages/%s", filename);
|
||||
if (!(f = fopen(buf, "r"))) {
|
||||
log_perror("Failed to load language %d (%s)", index, filename);
|
||||
return;
|
||||
} else if (read_uint32(&num, f) < 0) {
|
||||
log("Failed to read number of strings for language %d (%s)",
|
||||
index, filename);
|
||||
return;
|
||||
} else if (read_uint32(&size, f) < 0) {
|
||||
log("Failed to read data size for language %d (%s)",
|
||||
index, filename);
|
||||
return;
|
||||
} else if (num != NUM_BASE_STRINGS) {
|
||||
log("Warning: Bad number of strings (%d, wanted %d) "
|
||||
"for language %d (%s)", num, NUM_BASE_STRINGS, index, filename);
|
||||
}
|
||||
origtexts[index] = scalloc(sizeof(char *), NUM_BASE_STRINGS+1);
|
||||
if (num > NUM_BASE_STRINGS)
|
||||
num = NUM_BASE_STRINGS;
|
||||
origtexts[index][0] = data = smalloc(size+4);
|
||||
*((uint32 *)data) = size;
|
||||
data += 4;
|
||||
if (fread(data, size, 1, f) != 1) {
|
||||
log("Failed to read language data for language %d (%s)",
|
||||
index, filename);
|
||||
goto fail;
|
||||
}
|
||||
for (i = 0; i < num; i++) {
|
||||
int32 pos;
|
||||
if (read_int32(&pos, f) < 0) {
|
||||
log("Failed to read entry %d in language %d (%s) TOC",
|
||||
i, index, filename);
|
||||
goto fail;
|
||||
}
|
||||
if (pos == -1) {
|
||||
origtexts[index][i+1] = NULL;
|
||||
} else {
|
||||
origtexts[index][i+1] = data + pos;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
is_loaded[index] = 1;
|
||||
return;
|
||||
|
||||
fail:
|
||||
free(data);
|
||||
free(origtexts[index]);
|
||||
origtexts[index] = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialize list of lists. */
|
||||
|
||||
int lang_init(void)
|
||||
{
|
||||
int i, j, n = 0;
|
||||
|
||||
/* Set up the string list */
|
||||
langstrs = malloc(sizeof(char *) * NUM_BASE_STRINGS);
|
||||
if (!langstrs) {
|
||||
log_perror("malloc(langstrs)");
|
||||
return 0;
|
||||
}
|
||||
memcpy(langstrs, base_langstrs, sizeof(char *) * NUM_BASE_STRINGS);
|
||||
num_strings = NUM_BASE_STRINGS;
|
||||
|
||||
/* Set up the string remap list */
|
||||
langmap = smalloc(sizeof(int) * num_strings);
|
||||
for (i = 0; i < num_strings; i++)
|
||||
langmap[i] = i;
|
||||
|
||||
/* Load language files */
|
||||
memset(is_loaded, 0, sizeof(is_loaded));
|
||||
for (i = 0; i < lenof(langorder); i++) {
|
||||
for (j = 0; filenames[j].num >= 0; j++) {
|
||||
if (filenames[j].num == langorder[i])
|
||||
break;
|
||||
}
|
||||
if (filenames[j].num >= 0) {
|
||||
load_lang(langorder[i], filenames[j].filename);
|
||||
} else {
|
||||
log("BUG: lang_init(): no filename entry for language %d!",
|
||||
langorder[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure the default language has all strings available */
|
||||
if (!origtexts[DEF_LANGUAGE]) {
|
||||
log("Unable to load default language");
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < num_strings; i++) {
|
||||
if (!origtexts[DEF_LANGUAGE][i+1]) {
|
||||
if (is_loaded[LANG_EN_US] && origtexts[LANG_EN_US][i+1]) {
|
||||
uint32 oldsize = *((uint32 *)origtexts[DEF_LANGUAGE][0]);
|
||||
uint32 newsize = strlen(origtexts[LANG_EN_US][i+1]) + 1;
|
||||
origtexts[DEF_LANGUAGE][0] =
|
||||
realloc(origtexts[DEF_LANGUAGE][0], oldsize + newsize + 4);
|
||||
if (!origtexts[DEF_LANGUAGE][0]) {
|
||||
log("Out of memory while loading languages");
|
||||
return 0;
|
||||
}
|
||||
*((uint32 *)origtexts[DEF_LANGUAGE][0]) = oldsize + newsize;
|
||||
origtexts[DEF_LANGUAGE][i+1] =
|
||||
origtexts[DEF_LANGUAGE][0] + 4 + oldsize;
|
||||
strcpy(origtexts[DEF_LANGUAGE][i+1],
|
||||
origtexts[LANG_EN_US][i+1]);
|
||||
} else {
|
||||
log("String %s missing from default language", langstrs[i]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set up the list of available languages in langlist[] */
|
||||
for (i = 0; i < lenof(langorder); i++) {
|
||||
if (is_loaded[langorder[i]])
|
||||
langlist[n++] = langorder[i];
|
||||
}
|
||||
while (n < lenof(langlist))
|
||||
langlist[n++] = -1;
|
||||
|
||||
/* Initialize the active string tables in langtexts[] */
|
||||
reset_ext_lang();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Clean up language data. */
|
||||
|
||||
void lang_cleanup(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_LANGS; i++) {
|
||||
if (langtexts[i]) {
|
||||
free(langtexts[i][0]);
|
||||
free(langtexts[i]);
|
||||
langtexts[i] = NULL;
|
||||
}
|
||||
if (origtexts[i]) {
|
||||
free(origtexts[i][0]);
|
||||
free(origtexts[i]);
|
||||
langtexts[i] = NULL;
|
||||
}
|
||||
}
|
||||
free(langmap);
|
||||
free(langstrs);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Load an external language file. External language files are formatted
|
||||
* the same way as the standard language files, except that a language name
|
||||
* follows the string name; for example:
|
||||
*
|
||||
* UNKNOWN_COMMAND<lwsp+>en_us<lwsp*><nl>
|
||||
* <tab>The command %s is not recognized.<nl>
|
||||
*
|
||||
* where <lwsp> is "linear white space" (space or tab); <nl> is either LF
|
||||
* or CRLF; and <tab> is the tab character. Empty lines and lines
|
||||
* beginning with '#' are ignored. Note that any lines longer than
|
||||
* BUFSIZE-1 characters (including the trailing <nl>) will be truncated.
|
||||
*
|
||||
* Returns 0 on failure, nonzero on success.
|
||||
*/
|
||||
|
||||
int load_ext_lang(const char *filename)
|
||||
{
|
||||
FILE *f;
|
||||
char buf[BUFSIZE], *s;
|
||||
char **newtexts[NUM_LANGS];
|
||||
uint32 newsizes[NUM_LANGS];
|
||||
int i, curstr, curlang, line;
|
||||
int retval = 1, firstline = 1;
|
||||
|
||||
memset(newtexts, 0, sizeof(newtexts));
|
||||
memset(newsizes, 0, sizeof(newsizes));
|
||||
f = fopen(filename, "r");
|
||||
if (!f) {
|
||||
log_perror("load_ext_lang(): Unable to open file %s", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
curstr = curlang = -1;
|
||||
line = 0;
|
||||
while (fgets(buf, sizeof(buf), f) && *buf) {
|
||||
line++;
|
||||
s = buf + strlen(buf) - 1;
|
||||
if (*s == '\r') { /* in case we get half a CRLF */
|
||||
*s = fgetc(f);
|
||||
if (*s != '\n') {
|
||||
ungetc(*s, f);
|
||||
*s = '\r';
|
||||
}
|
||||
}
|
||||
if (*s == '\n') {
|
||||
*s = 0;
|
||||
if (s > buf && s[-1] == '\r')
|
||||
s[-1] = 0;
|
||||
} else {
|
||||
char buf2[BUFSIZE];
|
||||
log("load_ext_lang(): %s:%d: Line too long (maximum %d"
|
||||
" characters)", filename, line, sizeof(buf)-2);
|
||||
retval = 0;
|
||||
while (fgets(buf2, sizeof(buf2), f)
|
||||
&& *buf2 && buf2[strlen(buf2)-1] != '\n')
|
||||
;
|
||||
}
|
||||
if (*buf == '#')
|
||||
continue;
|
||||
if (*buf != '\t') {
|
||||
curstr = curlang = -1;
|
||||
s = strtok(buf, " \t\r\n");
|
||||
if (!s)
|
||||
continue; /* empty line */
|
||||
if ((curstr = lookup_string(s)) < 0) {
|
||||
log("load_ext_lang: %s:%d: Unknown string ID `%s'",
|
||||
filename, line, s);
|
||||
retval = 0;
|
||||
} else {
|
||||
s = strtok(NULL, " \t\r\n");
|
||||
if (!s) {
|
||||
log("load_ext_lang: %s:%d: Missing language ID\n",
|
||||
filename, line);
|
||||
retval = 0;
|
||||
curstr = -1;
|
||||
} else if ((curlang = lookup_language(s)) < 0) {
|
||||
log("load_ext_lang: %s:%d: Unknown language ID `%s'",
|
||||
filename, line, s);
|
||||
retval = 0;
|
||||
curstr = -1;
|
||||
} else if (!is_loaded[curlang]) {
|
||||
log("load_ext_lang: %s:%d: Language `%s' is not"
|
||||
" available", filename, line, s);
|
||||
retval = 0;
|
||||
curstr = -1;
|
||||
}
|
||||
}
|
||||
if (curstr >= 0 && curlang >= 0) {
|
||||
/* Valid string/language ID--set up storage space if not
|
||||
* yet done */
|
||||
if (!newtexts[curlang]) {
|
||||
newtexts[curlang] = calloc(num_strings+1, sizeof(char *));
|
||||
if (!newtexts[curlang]) {
|
||||
log_perror("load_ext_lang: %s:%d", filename, line);
|
||||
goto fail;
|
||||
}
|
||||
newsizes[curlang] = 0;
|
||||
}
|
||||
/* Point to location of string in data buffer; we add 1
|
||||
* to the offset to differentiate the value from 0 (not
|
||||
* present) */
|
||||
newtexts[curlang][curstr+1] = (char *)newsizes[curlang] + 1;
|
||||
/* Set first-line flag (signals whether to insert a
|
||||
* newline) */
|
||||
firstline = 1;
|
||||
}
|
||||
} else { /* line begins with tab -> text line */
|
||||
if (curstr >= 0 && curlang >= 0) {
|
||||
int oldsize = newsizes[curlang];
|
||||
if (!firstline)
|
||||
oldsize--; // overwrite the trailing \0 with a newline
|
||||
newsizes[curlang] += strlen(buf+1)+1; // skip initial tab
|
||||
newtexts[curlang][0] =
|
||||
realloc(newtexts[curlang][0], newsizes[curlang]);
|
||||
sprintf(newtexts[curlang][0]+oldsize, "%s%s",
|
||||
firstline ? "" : "\n", buf+1);
|
||||
firstline = 0;
|
||||
}
|
||||
}
|
||||
} /* for each line */
|
||||
fclose(f);
|
||||
|
||||
if (retval) {
|
||||
/* Success, install new strings. First set up new string arrays in
|
||||
* newtexts[], then, when all succeeds (i.e. no ENOMEM), move the
|
||||
* new data to langtexts[]. */
|
||||
for (curlang = 0; curlang < NUM_LANGS; curlang++) {
|
||||
char *newbuf;
|
||||
uint32 oldlen, newlen;
|
||||
if (!newtexts[curlang])
|
||||
continue;
|
||||
oldlen = *((uint32 *)langtexts[curlang][0]);
|
||||
newlen = newsizes[curlang];
|
||||
newbuf = malloc(4 + oldlen + newlen);
|
||||
if (!newbuf) {
|
||||
log_perror("load_ext_lang: %s:%d", filename, line);
|
||||
goto fail;
|
||||
}
|
||||
*((uint32 *)newbuf) = oldlen + newlen;
|
||||
memcpy(newbuf+4, langtexts[curlang][0]+4, oldlen);
|
||||
memcpy(newbuf+4+oldlen, newtexts[curlang][0], newlen);
|
||||
free(newtexts[curlang][0]);
|
||||
newtexts[curlang][0] = newbuf;
|
||||
for (i = 0; i < num_strings; i++) {
|
||||
if (newtexts[curlang][i+1]) {
|
||||
int ofs = (int)newtexts[curlang][i+1] - 1;
|
||||
newtexts[curlang][i+1] = newbuf+4 + oldlen + ofs;
|
||||
} else if (langtexts[curlang][i+1]) {
|
||||
int ofs = langtexts[curlang][i+1]
|
||||
- langtexts[curlang][0];
|
||||
newtexts[curlang][i+1] = newbuf + ofs;
|
||||
} else {
|
||||
newtexts[curlang][i+1] = NULL;
|
||||
}
|
||||
}
|
||||
} /* for each language */
|
||||
/* All okay, actually install the data */
|
||||
for (curlang = 0; curlang < NUM_LANGS; curlang++) {
|
||||
if (!newtexts[curlang])
|
||||
continue;
|
||||
free(langtexts[curlang][0]);
|
||||
free(langtexts[curlang]);
|
||||
langtexts[curlang] = newtexts[curlang];
|
||||
}
|
||||
} /* if (retval) */
|
||||
|
||||
return retval;
|
||||
|
||||
fail:
|
||||
for (curlang = 0; curlang < NUM_LANGS; curlang++) {
|
||||
if (newtexts[curlang]) {
|
||||
free(newtexts[curlang][0]);
|
||||
free(newtexts[curlang]);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Clear all data loaded from external language files or set with
|
||||
* setstring(), restoring to the default data sets. Strings added with
|
||||
* addstring() are retained but reset to empty.
|
||||
*/
|
||||
|
||||
void reset_ext_lang(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < NUM_LANGS; i++) {
|
||||
if (is_loaded[i]) {
|
||||
uint32 datasize = *((uint32 *)origtexts[i][0]);
|
||||
if (langtexts[i]) {
|
||||
free(langtexts[i][0]);
|
||||
free(langtexts[i]);
|
||||
}
|
||||
langtexts[i] = smalloc(sizeof(char *) * (num_strings+1));
|
||||
langtexts[i][0] = smalloc(datasize+4);
|
||||
memcpy(langtexts[i][0], origtexts[i][0], datasize+4);
|
||||
for (j = 0; j < num_strings; j++) {
|
||||
if (IS_BASE_STRING(j)) {
|
||||
if (origtexts[i][j+1]) {
|
||||
langtexts[i][j+1] = origtexts[i][j+1]
|
||||
- origtexts[i][0]
|
||||
+ langtexts[i][0];
|
||||
} else {
|
||||
langtexts[i][j+1] = NULL;
|
||||
}
|
||||
} else if (i == DEF_LANGUAGE) {
|
||||
langtexts[i][j+1] = langtexts[i][0] + datasize - 1;
|
||||
} else {
|
||||
langtexts[i][j+1] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the language number for the given language name. If the language
|
||||
* is not found, returns -1.
|
||||
*/
|
||||
|
||||
int lookup_language(const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; filenames[i].num >= 0; i++) {
|
||||
if (stricmp(filenames[i].filename, name) == 0)
|
||||
return filenames[i].num;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return true if the given language is loaded, false otherwise. */
|
||||
|
||||
int have_language(int language)
|
||||
{
|
||||
return language >= 0 && language < NUM_LANGS && is_loaded[language];
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the index of the given string name. If the name is not found,
|
||||
* returns -1. Note that string names are case sensitive.
|
||||
*/
|
||||
|
||||
int lookup_string(const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_strings; i++) {
|
||||
if (strcmp(langstrs[i], name) == 0)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Retrieve a message text using the language selected for the given
|
||||
* NickGroupInfo (if NickGroupInfo is NULL, use DEF_LANGUAGE).
|
||||
*/
|
||||
|
||||
const char *getstring(const NickGroupInfo *ngi, int index)
|
||||
{
|
||||
int language;
|
||||
const char *text;
|
||||
|
||||
if (index < 0 || index >= num_strings) {
|
||||
log("getstring(): BUG: index (%d) out of range!", index);
|
||||
return NULL;
|
||||
}
|
||||
language = (ngi && ngi != NICKGROUPINFO_INVALID
|
||||
&& ngi->language != LANG_DEFAULT)
|
||||
? ngi->language
|
||||
: DEF_LANGUAGE;
|
||||
text = langtexts[language][langmap[index]+1];
|
||||
if (!text)
|
||||
text = langtexts[DEF_LANGUAGE][langmap[index]+1];
|
||||
return text;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Retrieve a message text using the given language. */
|
||||
|
||||
const char *getstring_lang(int language, int index)
|
||||
{
|
||||
const char *text;
|
||||
|
||||
if (language < 0 || language >= NUM_LANGS) {
|
||||
log("getstring_lang(): BUG: language (%d) out of range!", language);
|
||||
language = DEF_LANGUAGE;
|
||||
} else if (index < 0 || index >= num_strings) {
|
||||
log("getstring_lang(): BUG: index (%d) out of range!", index);
|
||||
return NULL;
|
||||
}
|
||||
text = langtexts[language][langmap[index]+1];
|
||||
if (!text)
|
||||
text = langtexts[DEF_LANGUAGE][langmap[index]+1];
|
||||
return text;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set the contents of the given string in the given language. Returns
|
||||
* nonzero on success, zero on error (invalid parameters or language not
|
||||
* available). The text is copied to a separate location, so does not need
|
||||
* to be saved by the caller.
|
||||
*/
|
||||
|
||||
int setstring(int language, int index, const char *text)
|
||||
{
|
||||
uint32 oldlen, newlen;
|
||||
char *oldtext, *newtext;
|
||||
int i;
|
||||
|
||||
if (language < 0 || language >= NUM_LANGS) {
|
||||
log("setstring(): language (%d) out of range!", language);
|
||||
return 0;
|
||||
} else if (index < 0 || index >= num_strings) {
|
||||
log("setstring(): index (%d) out of range!", index);
|
||||
return 0;
|
||||
} else if (IS_BASE_STRING(index)) {
|
||||
log("setstring(): index (%d) is a base string and cannot be set",
|
||||
index);
|
||||
return 0;
|
||||
} else if (!text) {
|
||||
log("setstring(): text is NULL!");
|
||||
return 0;
|
||||
} else if (!is_loaded[language]) {
|
||||
log_debug(1, "setstring(): language %d not available", language);
|
||||
return 0;
|
||||
}
|
||||
oldtext = langtexts[language][0];
|
||||
oldlen = *((uint32 *)oldtext);
|
||||
newlen = oldlen + strlen(text) + 1;
|
||||
newtext = malloc(newlen + 4);
|
||||
if (!newtext) {
|
||||
log("setstring(): out of memory");
|
||||
return 0;
|
||||
}
|
||||
*((uint32 *)newtext) = newlen;
|
||||
memcpy(newtext+4, oldtext+4, oldlen);
|
||||
strcpy(newtext+4+oldlen, text);
|
||||
for (i = 0; i < num_strings; i++) {
|
||||
if (i == index) {
|
||||
langtexts[language][i+1] = newtext+4 + oldlen;
|
||||
} else {
|
||||
int ofs = langtexts[language][i+1] - oldtext;
|
||||
langtexts[language][i+1] = newtext + ofs;
|
||||
}
|
||||
}
|
||||
langtexts[language][0] = newtext;
|
||||
free(oldtext);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Map string number `old' to number `new' and return the number of the
|
||||
* message that used to be stored there. Returns -1 on error (invalid
|
||||
* parameter).
|
||||
*/
|
||||
|
||||
int mapstring(int old, int new)
|
||||
{
|
||||
int prev;
|
||||
|
||||
if (old < 0 || old >= num_strings) {
|
||||
log("mapstring(): old string index (%d) is out of range!", old);
|
||||
return -1;
|
||||
} else if (new < 0 || new >= num_strings) {
|
||||
log("mapstring(): new string index (%d) is out of range!", new);
|
||||
return -1;
|
||||
}
|
||||
prev = langmap[old];
|
||||
langmap[old] = langmap[new];
|
||||
return prev;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Add a new string to the global list with the given (non-empty) name.
|
||||
* The string is initially empty in all languages. If the string has
|
||||
* already been added, clears the string's text but does not add a new copy
|
||||
* of the string. Returns the string index on success, -1 on failure
|
||||
* (invalid parameters or attempting to re-add a base string).
|
||||
*/
|
||||
|
||||
int addstring(const char *name)
|
||||
{
|
||||
int index, i;
|
||||
char **new_langstrs, **new_langtexts[NUM_LANGS];
|
||||
int *new_langmap;
|
||||
|
||||
if (!name) {
|
||||
log("addstring(): BUG: name is NULL!");
|
||||
return -1;
|
||||
}
|
||||
if (!*name) {
|
||||
log("addstring(): name is the empty string!");
|
||||
return -1;
|
||||
}
|
||||
if ((index = lookup_string(name)) >= 0) {
|
||||
if (IS_BASE_STRING(index)) {
|
||||
log("addstring(): attempt to re-add base string `%s'!", name);
|
||||
return -1;
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
new_langstrs = malloc(sizeof(char *) * (num_strings+1));
|
||||
new_langmap = malloc(sizeof(int) * (num_strings+1));
|
||||
if (!new_langstrs || !new_langmap) {
|
||||
log("addstring(): Out of memory!");
|
||||
free(new_langmap);
|
||||
free(new_langstrs);
|
||||
return -1;
|
||||
}
|
||||
memcpy(new_langstrs, langstrs, sizeof(char *) * num_strings);
|
||||
memcpy(new_langmap, langmap, sizeof(int) * num_strings);
|
||||
new_langstrs[num_strings] = strdup(name);
|
||||
if (!new_langstrs[num_strings]) {
|
||||
log("addstring(): Out of memory!");
|
||||
free(new_langmap);
|
||||
free(new_langstrs);
|
||||
return -1;
|
||||
}
|
||||
new_langmap[num_strings] = num_strings;
|
||||
for (i = 0; i < NUM_LANGS; i++) {
|
||||
if (!is_loaded[i])
|
||||
continue;
|
||||
new_langtexts[i] = malloc(sizeof(char *) * (num_strings+2));
|
||||
if (!new_langtexts[i]) {
|
||||
log("addstring(): Out of memory!");
|
||||
while (--i >= 0)
|
||||
free(new_langtexts[i]);
|
||||
free(new_langstrs[num_strings]);
|
||||
free(new_langmap);
|
||||
free(new_langstrs);
|
||||
return -1;
|
||||
}
|
||||
memcpy(new_langtexts[i], langtexts[i],
|
||||
sizeof(char *) * (num_strings+1));
|
||||
if (i == DEF_LANGUAGE) {
|
||||
/* Point at the \0 ending the last string in the text buffer */
|
||||
new_langtexts[i][num_strings+1] =
|
||||
new_langtexts[i][0]+4 + *((uint32 *)new_langtexts[i][0]) - 1;
|
||||
} else {
|
||||
new_langtexts[i][num_strings+1] = NULL;
|
||||
}
|
||||
}
|
||||
free(langstrs);
|
||||
langstrs = new_langstrs;
|
||||
free(langmap);
|
||||
langmap = new_langmap;
|
||||
for (i = 0; i < NUM_LANGS; i++) {
|
||||
if (is_loaded[i]) {
|
||||
free(langtexts[i]);
|
||||
langtexts[i] = new_langtexts[i];
|
||||
}
|
||||
}
|
||||
return num_strings++;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Format a string in a strftime()-like way, but heed the nick group's
|
||||
* language setting for month and day names and adjust the time for the
|
||||
* nick group's time zone setting. The string stored in the buffer will
|
||||
* always be null-terminated, even if the actual string was longer than the
|
||||
* buffer size. Also note that the format parameter is a message number
|
||||
* rather than a literal string, and the time parameter is a time_t, not a
|
||||
* struct tm *.
|
||||
* Assumption: No month or day name has a length (including trailing null)
|
||||
* greater than BUFSIZE or contains the '%' character.
|
||||
*/
|
||||
|
||||
int strftime_lang(char *buf, int size, const NickGroupInfo *ngi,
|
||||
int format, time_t time)
|
||||
{
|
||||
int ngi_is_valid = (ngi && ngi != NICKGROUPINFO_INVALID);
|
||||
int language = (ngi_is_valid && ngi->language != LANG_DEFAULT)
|
||||
? ngi->language
|
||||
: DEF_LANGUAGE;
|
||||
char tmpbuf[BUFSIZE], buf2[BUFSIZE];
|
||||
char *s;
|
||||
int i, ret;
|
||||
struct tm *tm;
|
||||
|
||||
strbcpy(tmpbuf, getstring_lang(language,format));
|
||||
if (ngi_is_valid && ngi->timezone != TIMEZONE_DEFAULT) {
|
||||
time += ngi->timezone*60;
|
||||
tm = gmtime(&time);
|
||||
/* Remove "%Z" (timezone) specifiers */
|
||||
while ((s = strstr(tmpbuf, "%Z")) != NULL) {
|
||||
char *end = s+2;
|
||||
while (s > tmpbuf && s[-1] == ' ')
|
||||
s--;
|
||||
strmove(s, end);
|
||||
}
|
||||
} else {
|
||||
tm = localtime(&time);
|
||||
}
|
||||
if ((s = langtexts[language][STRFTIME_DAYS_SHORT+1]) != NULL) {
|
||||
for (i = 0; i < tm->tm_wday; i++)
|
||||
s += strcspn(s, "\n")+1;
|
||||
i = strcspn(s, "\n");
|
||||
strncpy(buf2, s, i);
|
||||
buf2[i] = 0;
|
||||
strnrepl(tmpbuf, sizeof(tmpbuf), "%a", buf2);
|
||||
}
|
||||
if ((s = langtexts[language][STRFTIME_DAYS_LONG+1]) != NULL) {
|
||||
for (i = 0; i < tm->tm_wday; i++)
|
||||
s += strcspn(s, "\n")+1;
|
||||
i = strcspn(s, "\n");
|
||||
strncpy(buf2, s, i);
|
||||
buf2[i] = 0;
|
||||
strnrepl(tmpbuf, sizeof(tmpbuf), "%A", buf2);
|
||||
}
|
||||
if ((s = langtexts[language][STRFTIME_MONTHS_SHORT+1]) != NULL) {
|
||||
for (i = 0; i < tm->tm_mon; i++)
|
||||
s += strcspn(s, "\n")+1;
|
||||
i = strcspn(s, "\n");
|
||||
strncpy(buf2, s, i);
|
||||
buf2[i] = 0;
|
||||
strnrepl(tmpbuf, sizeof(tmpbuf), "%b", buf2);
|
||||
}
|
||||
if ((s = langtexts[language][STRFTIME_MONTHS_LONG+1]) != NULL) {
|
||||
for (i = 0; i < tm->tm_mon; i++)
|
||||
s += strcspn(s, "\n")+1;
|
||||
i = strcspn(s, "\n");
|
||||
strncpy(buf2, s, i);
|
||||
buf2[i] = 0;
|
||||
strnrepl(tmpbuf, sizeof(tmpbuf), "%B", buf2);
|
||||
}
|
||||
ret = strftime(buf, size, tmpbuf, tm);
|
||||
if (ret >= size) // buffer overflow, should be impossible
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Generates a string describing the given length of time to one unit
|
||||
* (e.g. "3 days" or "10 hours"), or two units (e.g. "5 hours 25 minutes")
|
||||
* if the MT_DUALUNIT flag is specified. The minimum resolution is one
|
||||
* minute, unless the MT_SECONDS flag is specified; the returned time is
|
||||
* rounded up if in the minimum unit, else rounded to the nearest integer.
|
||||
* The returned buffer is a static buffer which will be overwritten on the
|
||||
* next call to this routine.
|
||||
*
|
||||
* The MT_* flags (passed in the `flags' parameter) are defined in
|
||||
* language.h.
|
||||
*/
|
||||
|
||||
char *maketime(const NickGroupInfo *ngi, time_t time, int flags)
|
||||
{
|
||||
static char buf[BUFSIZE];
|
||||
int unit;
|
||||
|
||||
if (time < 1) /* Enforce a minimum of one second */
|
||||
time = 1;
|
||||
|
||||
if ((flags & MT_SECONDS) && time <= 59) {
|
||||
|
||||
unit = (time==1 ? STR_SECOND : STR_SECONDS);
|
||||
snprintf(buf, sizeof(buf), "%ld%s", (long)time, getstring(ngi,unit));
|
||||
|
||||
} else if (!(flags & MT_SECONDS) && time <= 59*60) {
|
||||
|
||||
time = (time+59) / 60;
|
||||
unit = (time==1 ? STR_MINUTE : STR_MINUTES);
|
||||
snprintf(buf, sizeof(buf), "%ld%s", (long)time, getstring(ngi,unit));
|
||||
|
||||
|
||||
} else if (flags & MT_DUALUNIT) {
|
||||
|
||||
time_t time2;
|
||||
int unit2;
|
||||
|
||||
if (time <= 59*60+59) { /* 59 minutes, 59 seconds */
|
||||
time2 = time % 60;
|
||||
unit2 = (time2==1 ? STR_SECOND : STR_SECONDS);
|
||||
time = time / 60;
|
||||
unit = (time==1 ? STR_MINUTE : STR_MINUTES);
|
||||
} else if (time <= (23*60+59)*60+30) { /* 23 hours, 59.5 minutes */
|
||||
time = (time+30) / 60;
|
||||
time2 = time % 60;
|
||||
unit2 = (time2==1 ? STR_MINUTE : STR_MINUTES);
|
||||
time = time / 60;
|
||||
unit = (time==1 ? STR_HOUR : STR_HOURS);
|
||||
} else {
|
||||
time = (time+(30*60)) / (60*60);
|
||||
time2 = time % 24;
|
||||
unit2 = (time2==1 ? STR_HOUR : STR_HOURS);
|
||||
time = time / 24;
|
||||
unit = (time==1 ? STR_DAY : STR_DAYS);
|
||||
}
|
||||
if (time2)
|
||||
snprintf(buf, sizeof(buf), "%ld%s%s%ld%s", (long)time,
|
||||
getstring(ngi,unit), getstring(ngi,STR_TIMESEP),
|
||||
(long)time2, getstring(ngi,unit2));
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "%ld%s", (long)time,
|
||||
getstring(ngi,unit));
|
||||
|
||||
} else { /* single unit */
|
||||
|
||||
if (time <= 59*60+30) { /* 59 min 30 sec; MT_SECONDS known true */
|
||||
time = (time+30) / 60;
|
||||
unit = (time==1 ? STR_MINUTE : STR_MINUTES);
|
||||
} else if (time <= (23*60+30)*60) { /* 23 hours, 30 minutes */
|
||||
time = (time+(30*60)) / (60*60);
|
||||
unit = (time==1 ? STR_HOUR : STR_HOURS);
|
||||
} else {
|
||||
time = (time+(12*60*60)) / (24*60*60);
|
||||
unit = (time==1 ? STR_DAY : STR_DAYS);
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%ld%s", (long)time, getstring(ngi,unit));
|
||||
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Generates a description for the given expiration time in the form of
|
||||
* days, hours, minutes, seconds and/or a combination thereof. May also
|
||||
* return "does not expire" or "already expired" messages if the expiration
|
||||
* time given is zero or earlier than the current time, respectively.
|
||||
* String is truncated if it would exceed `size' bytes (including trailing
|
||||
* null byte).
|
||||
*/
|
||||
|
||||
void expires_in_lang(char *buf, int size, const NickGroupInfo *ngi,
|
||||
time_t expires)
|
||||
{
|
||||
time_t seconds = expires - time(NULL);
|
||||
|
||||
if (expires == 0) {
|
||||
strscpy(buf, getstring(ngi,EXPIRES_NONE), size);
|
||||
} else if (seconds <= 0 && noexpire) {
|
||||
strscpy(buf, getstring(ngi,EXPIRES_NOW), size);
|
||||
} else {
|
||||
if (seconds <= 0) {
|
||||
/* Already expired--it will be cleared out by the next get() */
|
||||
seconds = 1;
|
||||
}
|
||||
snprintf(buf, size, getstring(ngi,EXPIRES_IN),
|
||||
maketime(ngi,seconds,MT_DUALUNIT));
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Send a syntax-error message to the user. */
|
||||
|
||||
void syntax_error(const char *service, const User *u, const char *command,
|
||||
int msgnum)
|
||||
{
|
||||
char buf[BUFSIZE];
|
||||
snprintf(buf, sizeof(buf), getstring(u->ngi, msgnum), command);
|
||||
notice_lang(service, u, SYNTAX_ERROR, buf);
|
||||
notice_lang(service, u, MORE_INFO, service, command);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
97
language.h
Normal file
97
language.h
Normal file
@ -0,0 +1,97 @@
|
||||
/* Include file for multi-language support.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef LANGUAGE_H
|
||||
#define LANGUAGE_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Languages. Never insert anything in (or delete anything from) the
|
||||
* middle of this list, or everybody will start getting the wrong language!
|
||||
* If you want to change the order the languages are displayed in for
|
||||
* NickServ HELP SET LANGUAGE, do it in language.c.
|
||||
*/
|
||||
#define LANG_EN_US 0 /* United States English */
|
||||
#define LANG_UNUSED1 1 /* Unused; was Japanese (JIS encoding) */
|
||||
#define LANG_JA_EUC 2 /* Japanese (EUC encoding) */
|
||||
#define LANG_JA_SJIS 3 /* Japanese (SJIS encoding) */
|
||||
#define LANG_ES 4 /* Spanish */
|
||||
#define LANG_PT 5 /* Portugese */
|
||||
#define LANG_FR 6 /* French */
|
||||
#define LANG_TR 7 /* Turkish */
|
||||
#define LANG_IT 8 /* Italian */
|
||||
#define LANG_DE 9 /* German */
|
||||
#define LANG_NL 10 /* Dutch */
|
||||
#define LANG_HU 11 /* Hungarian */
|
||||
#define LANG_RU 12 /* Russian */
|
||||
|
||||
#define NUM_LANGS 13 /* Number of languages */
|
||||
#define LANG_DEFAULT -1 /* "Use the default" setting */
|
||||
|
||||
/* Sanity-check on default language value */
|
||||
#if DEF_LANGUAGE < 0 || DEF_LANGUAGE >= NUM_LANGS
|
||||
# error Invalid value for DEF_LANGUAGE: must be >= 0 and < NUM_LANGS
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Flags for maketime() `flags' parameter. */
|
||||
|
||||
#define MT_DUALUNIT 0x0001 /* Allow two units (e.g. X hours Y mins) */
|
||||
#define MT_SECONDS 0x0002 /* Allow seconds (default minutes only) */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* External symbol declarations (see language.c for documentation). */
|
||||
|
||||
extern int langlist[NUM_LANGS+1];
|
||||
|
||||
extern int lang_init(void);
|
||||
extern void lang_cleanup(void);
|
||||
extern int load_ext_lang(const char *filename);
|
||||
extern void reset_ext_lang(void);
|
||||
|
||||
extern int lookup_language(const char *name);
|
||||
extern int have_language(int language);
|
||||
extern int lookup_string(const char *name);
|
||||
extern const char *getstring(const NickGroupInfo *ngi, int index);
|
||||
extern const char *getstring_lang(int language, int index);
|
||||
extern int setstring(int language, int index, const char *text);
|
||||
extern int mapstring(int old, int new);
|
||||
extern int addstring(const char *name);
|
||||
|
||||
extern int strftime_lang(char *buf, int size, const NickGroupInfo *ngi,
|
||||
int format, time_t time);
|
||||
extern char *maketime(const NickGroupInfo *ngi, time_t time, int flags);
|
||||
extern void expires_in_lang(char *buf, int size, const NickGroupInfo *ngi,
|
||||
time_t seconds);
|
||||
|
||||
extern void syntax_error(const char *service, const User *u,
|
||||
const char *command, int msgnum);
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Definitions of language string constants. */
|
||||
#include "langstrs.h"
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* LANGUAGE_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
440
list-array.h
Normal file
440
list-array.h
Normal file
@ -0,0 +1,440 @@
|
||||
/* Macros to handle insertion, deletion, iteration, and searching for
|
||||
* linked lists and arrays.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef LIST_ARRAY_H
|
||||
#define LIST_ARRAY_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Comments on the efficiency of lists vs. arrays:
|
||||
*
|
||||
* Lists are simple to insert into and remove from, but searching can be
|
||||
* inefficient, as the list must be walked through each time. Arrays can
|
||||
* be searched quickly, particularly if the list is sorted (note that the
|
||||
* macros below do not support sorted arrays), but insertion and removal
|
||||
* are expensive, requiring data to be moved around in memory if the
|
||||
* element to be inserted or removed is not last in the array or if
|
||||
* reallocation of the array results in a different array pointer. Lists
|
||||
* also have the advantage that the list can be easily modified (items
|
||||
* inserted or deleted) within a FOREACH loop, while arrays require extra
|
||||
* logic to modify the counter variable after an insert or delete.
|
||||
*
|
||||
* As far as searching goes, arrays do not show a significant improvement
|
||||
* until the data set size reaches around 15 elements, at which point the
|
||||
* reduced number of memory accesses (and reduced cache pressure,
|
||||
* particularly with large data sets) gives arrays a clear advantage. The
|
||||
* following results were obtained from running the test program below on a
|
||||
* Celeron 1.7GHz system (compiled with gcc-4.1 -O3):
|
||||
*
|
||||
* | Time/search (ns)
|
||||
* Size | List | Array
|
||||
* -----+-------------------
|
||||
* 3 | 4.1 | 3.7
|
||||
* 5 | 7.3 | 6.7
|
||||
* 7 | 9.7 | 9.3
|
||||
* 10 | 14.5 | 12.3
|
||||
* 15 | 23.6 | 18.2
|
||||
* 20 | 53.5 | 21.7
|
||||
* 30 | 73.2 | 44.8
|
||||
* 50 | 109 | 56.7
|
||||
* 100 | 200 | 101
|
||||
* 300 | 564 | 267
|
||||
* 1000 | 4530 | 864
|
||||
* 3000 | 13740 | 4440
|
||||
*
|
||||
* Test program (outputs average time per trial):
|
||||
*
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
int main(int ac, char **av)
|
||||
{
|
||||
int size = atoi(av[1]), runs = atoi(av[2]), trials = ac>3?atoi(av[3]):1;
|
||||
struct p_s {struct p_s *next; int a,b; } *p, *p2;
|
||||
struct { int a,b; } *q;
|
||||
int i, j, t;
|
||||
struct rusage ru1, ru2, ru3, ru4;
|
||||
int list = 0, array = 0;
|
||||
|
||||
p = p2 = malloc(sizeof(*p));
|
||||
q = malloc(sizeof(*q) * size);
|
||||
for (i = 0; i < size; i++) {
|
||||
p2->b = q[i].b = i;
|
||||
p2 = (p2->next = malloc(sizeof(*p2)));
|
||||
}
|
||||
for (t = 0; t < trials+1; t++) {
|
||||
for (p2 = p; p2->b != size-1; p2 = p2->next)
|
||||
;
|
||||
getrusage(RUSAGE_SELF, &ru1);
|
||||
for (i = 0; i < runs; i++)
|
||||
for (p2 = p; p2->b != size-1; p2 = p2->next)
|
||||
;
|
||||
getrusage(RUSAGE_SELF, &ru2);
|
||||
for (j = 0; q[j].b != size-1; j++)
|
||||
;
|
||||
getrusage(RUSAGE_SELF, &ru3);
|
||||
for (i = 0; i < runs; i++)
|
||||
for (j = 0; q[j].b != size-1; j++)
|
||||
;
|
||||
getrusage(RUSAGE_SELF, &ru4);
|
||||
#define TIME(ru) ((ru).ru_utime.tv_sec*1000 + (ru).ru_utime.tv_usec/1000)
|
||||
if (t > 0) { // skip first trial for cache loading
|
||||
list += TIME(ru2) - TIME(ru1);
|
||||
array += TIME(ru4) - TIME(ru3);
|
||||
}
|
||||
}
|
||||
printf("List : %d ms\nArray: %d ms\n",
|
||||
(list*2+trials)/(trials*2), (array*2+trials)/(trials*2));
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove anything defined by system headers. */
|
||||
|
||||
#undef LIST_INSERT
|
||||
#undef LIST_INSERT_ORDERED
|
||||
#undef LIST_REMOVE
|
||||
#undef LIST_FOREACH
|
||||
#undef LIST_FOREACH_SAFE
|
||||
#undef LIST_SEARCH
|
||||
#undef LIST_SEARCH_SCALAR
|
||||
#undef LIST_SEARCH_ORDERED
|
||||
#undef LIST_SEARCH_ORDERED_SCALAR
|
||||
#undef ARRAY2_EXTEND
|
||||
#undef ARRAY2_INSERT
|
||||
#undef ARRAY2_REMOVE
|
||||
#undef ARRAY2_FOREACH
|
||||
#undef ARRAY2_SEARCH
|
||||
#undef ARRAY2_SEARCH_SCALAR
|
||||
#undef ARRAY2_SEARCH_PLAIN_SCALAR
|
||||
#undef ARRAY_EXTEND
|
||||
#undef ARRAY_INSERT
|
||||
#undef ARRAY_REMOVE
|
||||
#undef ARRAY_FOREACH
|
||||
#undef ARRAY_SEARCH
|
||||
#undef ARRAY_SEARCH_SCALAR
|
||||
#undef ARRAY_SEARCH_PLAIN_SCALAR
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* List macros. In these macros, `list' must be an lvalue (with no side
|
||||
* effects) of the same type as the nodes in the list. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Insert `node' into the beginning of `list'. Insertion is performed in
|
||||
* constant time. */
|
||||
|
||||
#define LIST_INSERT(node_,list) \
|
||||
do { \
|
||||
typeof(list) _node = (node_); \
|
||||
_node->next = (list); \
|
||||
_node->prev = NULL; \
|
||||
if (list) \
|
||||
(list)->prev = _node; \
|
||||
(list) = _node; \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Append `node' to the end of `list'. Insertion is performed in linear
|
||||
* time with the length of the list. */
|
||||
|
||||
#define LIST_APPEND(node_,list) \
|
||||
do { \
|
||||
typeof(list) _node = (node_); \
|
||||
typeof(_node) *_nextptr = &(list); \
|
||||
typeof(_node) _prev = NULL; \
|
||||
while (*_nextptr) { \
|
||||
_prev = *_nextptr; \
|
||||
_nextptr = &((*_nextptr)->next); \
|
||||
} \
|
||||
*_nextptr = _node; \
|
||||
_node->prev = _prev; \
|
||||
_node->next = NULL; \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Insert `node' into `list' so that `list' maintains its order as
|
||||
* determined by the function `compare' called on the field `field' of each
|
||||
* node. `field' must be a field of `node', and `compare' must be a
|
||||
* function that takes two `field' values and returns -1, 0, or 1
|
||||
* indicating whether the first argument is ordered before, equal to, or
|
||||
* after the second (strcmp(), for example). If an equal node is found,
|
||||
* `node' is inserted after it. Insertion is performed in linear time
|
||||
* with the length of the list. */
|
||||
|
||||
#define LIST_INSERT_ORDERED(node_,list,compare,field) \
|
||||
do { \
|
||||
typeof(list) _node = (node_); \
|
||||
typeof(_node) _ptr, _prev; \
|
||||
for (_ptr = (list), _prev = NULL; _ptr; \
|
||||
_prev = _ptr, _ptr = _ptr->next \
|
||||
) { \
|
||||
if (compare(_node->field, _ptr->field) < 0) \
|
||||
break; \
|
||||
} \
|
||||
_node->next = _ptr; \
|
||||
_node->prev = _prev; \
|
||||
if (_ptr) \
|
||||
_ptr->prev = _node; \
|
||||
if (_prev) \
|
||||
_prev->next = _node; \
|
||||
else \
|
||||
(list) = _node; \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove `node' from `list'. Removal is performed in constant time. */
|
||||
|
||||
#define LIST_REMOVE(node_,list) \
|
||||
do { \
|
||||
typeof(list) _node = (node_); \
|
||||
if (_node->next) \
|
||||
_node->next->prev = _node->prev; \
|
||||
if (_node->prev) \
|
||||
_node->prev->next = _node->next; \
|
||||
else \
|
||||
(list) = _node->next; \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Iterate over every element in `list', using `iter' as the iterator. The
|
||||
* macro has the same properties as a for() loop. `iter' must be an
|
||||
* lvalue. */
|
||||
|
||||
#define LIST_FOREACH(iter,list) \
|
||||
for ((iter) = (list); (iter); (iter) = (iter)->next)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Iterate over `list' using an extra variable (`temp') to hold the next
|
||||
* element, ensuring proper operation even when the current element is
|
||||
* deleted. `iter' and `temp' must be lvalues. */
|
||||
|
||||
#define LIST_FOREACH_SAFE(iter,list,temp) \
|
||||
for ((iter) = (list); (iter) && (temp = (iter)->next, 1); (iter) = temp)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search `list' for a node with `field' equal to `target' (as evaluated by
|
||||
* `compare') and place a pointer to the node found, or NULL if no node is
|
||||
* found, in `result'. `field' must be a field of the nodes in `list';
|
||||
* `target' must be an expression of the type of `field' with no side
|
||||
* effects; `result' must be an lvalue; and `compare' must be a
|
||||
* strcmp()-like function (see LIST_INSERT_ORDERED). The search is
|
||||
* performed in linear time, disregarding the comparison function. */
|
||||
|
||||
#define LIST_SEARCH(list,field,target,compare,result) \
|
||||
do { \
|
||||
LIST_FOREACH ((result), (list)) { \
|
||||
if (compare((result)->field, (target)) == 0)\
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search `list' as LIST_SEARCH does, but for a scalar value. The search
|
||||
* is performed in linear time. */
|
||||
|
||||
#define LIST_SEARCH_SCALAR(list,field,target,result) \
|
||||
do { \
|
||||
LIST_FOREACH ((result), (list)) { \
|
||||
if ((result)->field == (target)) \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search `list' as LIST_SEARCH does, but for a list known to be ordered.
|
||||
* The search is performed in linear time, disregarding the comparison
|
||||
* function. */
|
||||
|
||||
#define LIST_SEARCH_ORDERED(list,field,target,compare,result) \
|
||||
do { \
|
||||
LIST_FOREACH ((result), (list)) { \
|
||||
int i = compare((result)->field, (target)); \
|
||||
if (i > 0) \
|
||||
(result) = NULL; \
|
||||
if (i >= 0) \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search `list' as LIST_SEARCH_ORDERED does, but for a scalar value. The
|
||||
* search is performed in linear time. */
|
||||
|
||||
#define LIST_SEARCH_ORDERED_SCALAR(list,field,target,result) \
|
||||
do { \
|
||||
LIST_FOREACH ((result), (list)) { \
|
||||
int i = (result)->field - (target); \
|
||||
if (i > 0) \
|
||||
(result) = NULL; \
|
||||
if (i >= 0) \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Array macros. In these macros, `array' and `size' must be lvalues with
|
||||
* no side effects; `array' is a pointer to the elements of the array, and
|
||||
* `size' is the current size (length) of the array. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Extend a variable-length array by one entry. Execution time is no
|
||||
* greater than linear with the length of the array. */
|
||||
|
||||
#define ARRAY2_EXTEND(array,size) \
|
||||
do { \
|
||||
(size)++; \
|
||||
(array) = srealloc((array), sizeof(*(array)) * (size)); \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Insert a slot at position `index' in a variable-length array.
|
||||
* Execution time is linear with the length of the array. */
|
||||
|
||||
#define ARRAY2_INSERT(array,size,index_) \
|
||||
do { \
|
||||
unsigned int _index = (index_); \
|
||||
(size)++; \
|
||||
(array) = srealloc((array), sizeof(*(array)) * (size)); \
|
||||
if (_index < (size)-1) \
|
||||
memmove(&(array)[_index+1], &(array)[_index], \
|
||||
sizeof(*(array)) * (((size)-1)-_index)); \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Delete entry number `index' from a variable-length array. Execution
|
||||
* time is linear with the length of the array. */
|
||||
|
||||
#define ARRAY2_REMOVE(array,size,index_) \
|
||||
do { \
|
||||
unsigned int _index = (index_); \
|
||||
(size)--; \
|
||||
if (_index < (size)) \
|
||||
memmove(&(array)[_index], &(array)[_index]+1, \
|
||||
sizeof(*(array)) * ((size)-_index)); \
|
||||
(array) = srealloc((array), sizeof(*(array)) * (size)); \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Iterate over every element in a variable-length array. */
|
||||
|
||||
#define ARRAY2_FOREACH(iter,array,size) \
|
||||
for ((iter) = 0; (iter) < (size); (iter)++)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search a variable-length array for a value. Operates like LIST_SEARCH.
|
||||
* `result' must be an integer lvalue. If nothing is found, `result' will
|
||||
* be set equal to `size'. The search is performed in linear time,
|
||||
* disregarding the comparison function. */
|
||||
|
||||
#define ARRAY2_SEARCH(array,size,field,target,compare,result) \
|
||||
do { \
|
||||
ARRAY2_FOREACH ((result), (array), (size)) { \
|
||||
if (compare((array)[(result)].field, (target)) == 0)\
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search a variable-length array for a value, when the array elements do
|
||||
* not have fields. The search is performed in linear time, disregarding
|
||||
* the comparison function. */
|
||||
|
||||
#define ARRAY2_SEARCH_PLAIN(array,size,target,compare,result) \
|
||||
do { \
|
||||
ARRAY2_FOREACH ((result), (array), (size)) { \
|
||||
if (compare((array)[(result)], (target)) == 0) \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search a variable-length array for a scalar value. The search is
|
||||
* performed in linear time. */
|
||||
|
||||
#define ARRAY2_SEARCH_SCALAR(array,size,field,target,result) \
|
||||
do { \
|
||||
ARRAY2_FOREACH ((result), (array), (size)) { \
|
||||
if ((array)[(result)].field == (target)) \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Search a variable-length array for a scalar value, when the array
|
||||
* elements do not have fields. The search is performed in linear time. */
|
||||
|
||||
#define ARRAY2_SEARCH_PLAIN_SCALAR(array,size,target,result) \
|
||||
do { \
|
||||
ARRAY2_FOREACH ((result), (array), (size)) { \
|
||||
if ((array)[(result)] == (target)) \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Perform the ARRAY2_* actions on an array `array' whose size is stored in
|
||||
* `array_count'. */
|
||||
|
||||
#define ARRAY_EXTEND(array) ARRAY2_EXTEND(array,array##_count)
|
||||
#define ARRAY_INSERT(array,index) ARRAY2_INSERT(array,array##_count,index)
|
||||
#define ARRAY_REMOVE(array,index) ARRAY2_REMOVE(array,array##_count,index)
|
||||
#define ARRAY_FOREACH(iter,array) ARRAY2_FOREACH(iter,array,array##_count)
|
||||
#define ARRAY_SEARCH(array,field,target,compare,result) \
|
||||
ARRAY2_SEARCH(array,array##_count,field,target,compare,result)
|
||||
#define ARRAY_SEARCH_PLAIN(array,target,compare,result) \
|
||||
ARRAY2_SEARCH_PLAIN(array,array##_count,target,compare,result)
|
||||
#define ARRAY_SEARCH_SCALAR(array,field,target,result) \
|
||||
ARRAY2_SEARCH_SCALAR(array,array##_count,field,target,result)
|
||||
#define ARRAY_SEARCH_PLAIN_SCALAR(array,target,result) \
|
||||
ARRAY2_SEARCH_PLAIN_SCALAR(array,array##_count,target,result)
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* LIST_ARRAY_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
396
log.c
Normal file
396
log.c
Normal file
@ -0,0 +1,396 @@
|
||||
/* Logging routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include <netdb.h> /* for hstrerror() */
|
||||
|
||||
/* Pattern for generating logfile names */
|
||||
static char logfile_pattern[PATH_MAX+1];
|
||||
|
||||
/* Currently open log file */
|
||||
static FILE *logfile;
|
||||
static char current_filename[PATH_MAX+1];
|
||||
|
||||
/* Memory log buffer (see open_memory_log()) */
|
||||
static char logmem[LOGMEMSIZE];
|
||||
static char *logmemptr = NULL;
|
||||
|
||||
/* Are we in fatal() or fatal_perror()? (This is used to avoid infinite
|
||||
* recursion if wallops() does a fatal().) */
|
||||
static int in_fatal = 0;
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Local routine to generate a filename from a filename pattern (possibly
|
||||
* containing %y/%m/%d for year/month/day). Result is returned in a static
|
||||
* buffer, and will not be longer than PATH_MAX characters.
|
||||
*/
|
||||
|
||||
static char *gen_log_filename(void)
|
||||
{
|
||||
static char result[PATH_MAX+1];
|
||||
const char *s, *from;
|
||||
char *to;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm *tm = localtime(&now);
|
||||
tm->tm_year += 1900;
|
||||
tm->tm_mon++;
|
||||
|
||||
from = LogFilename;
|
||||
if (!*from) {
|
||||
*result = 0;
|
||||
return result;
|
||||
}
|
||||
to = result;
|
||||
|
||||
while ((s = strchr(from, '%')) != NULL) {
|
||||
to += snprintf(to, sizeof(result)-(to-result), "%.*s", s-from, from);
|
||||
s++;
|
||||
switch (*s) {
|
||||
case 'y':
|
||||
to += snprintf(to, sizeof(result)-(to-result), "%d", tm->tm_year);
|
||||
break;
|
||||
case 'm':
|
||||
to += snprintf(to, sizeof(result)-(to-result), "%02d", tm->tm_mon);
|
||||
break;
|
||||
case 'd':
|
||||
to += snprintf(to, sizeof(result)-(to-result), "%02d",tm->tm_mday);
|
||||
break;
|
||||
default:
|
||||
if (to-result < sizeof(result)-1)
|
||||
*to++ = *s;
|
||||
break;
|
||||
}
|
||||
from = s+1;
|
||||
}
|
||||
to += snprintf(to, sizeof(result)-(to-result), "%s", from);
|
||||
|
||||
*to = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Local routine to check whether the log file needs to be rotated, and
|
||||
* rotate it if so. Assumes the log file is already open.
|
||||
*/
|
||||
|
||||
static void check_log_rotate(void)
|
||||
{
|
||||
char *newname = gen_log_filename();
|
||||
if (strlen(newname) > sizeof(current_filename)-1)
|
||||
newname[sizeof(current_filename)-1] = 0;
|
||||
if (strcmp(current_filename, gen_log_filename()) != 0) {
|
||||
if (!reopen_log())
|
||||
log("Warning: Unable to rotate log file: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Local routines to write text to the log file and/or stderr as needed. */
|
||||
|
||||
static void vlogprintf(const char *fmt, va_list args)
|
||||
{
|
||||
if (nofork) {
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
vfprintf(stderr, fmt, args_copy);
|
||||
va_end(args_copy);
|
||||
}
|
||||
if (logfile) {
|
||||
vfprintf(logfile, fmt, args);
|
||||
} else if (logmemptr) {
|
||||
char tmpbuf[BUFSIZE];
|
||||
int len = vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, args);
|
||||
if (len > LOGMEMSIZE - (logmemptr-logmem)) {
|
||||
int oldlen = len;
|
||||
len = LOGMEMSIZE - (logmemptr-logmem);
|
||||
if (len > 0) {
|
||||
if (tmpbuf[oldlen-1] == '\n') {
|
||||
tmpbuf[len-1] = '\n'; /* always end with a newline */
|
||||
} else {
|
||||
len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (len > 0) {
|
||||
memcpy(logmemptr, tmpbuf, len);
|
||||
logmemptr += len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void logprintf(const char *fmt, ...) FORMAT(printf,1,2);
|
||||
static void logprintf(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vlogprintf(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static void logputs(const char *str)
|
||||
{
|
||||
logprintf("%s", str);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Local routine to write the time of day to the log. */
|
||||
|
||||
static void write_time(void)
|
||||
{
|
||||
time_t t;
|
||||
struct tm tm;
|
||||
char buf[256];
|
||||
|
||||
time(&t);
|
||||
tm = *localtime(&t);
|
||||
#if HAVE_GETTIMEOFDAY
|
||||
if (debug) {
|
||||
char *s;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
strftime(buf, sizeof(buf)-1, "[%b %d %H:%M:%S", &tm);
|
||||
s = buf + strlen(buf);
|
||||
s += snprintf(s, sizeof(buf)-(s-buf), ".%06d", (int)tv.tv_usec);
|
||||
strftime(s, sizeof(buf)-(s-buf)-1, " %Y] ", &tm);
|
||||
} else {
|
||||
#endif
|
||||
strftime(buf, sizeof(buf)-1, "[%b %d %H:%M:%S %Y] ", &tm);
|
||||
#if HAVE_GETTIMEOFDAY
|
||||
}
|
||||
#endif
|
||||
logputs(buf);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set the filename pattern to use for generating the log filename. */
|
||||
|
||||
void set_logfile(const char *pattern)
|
||||
{
|
||||
if (pattern) { /* Just to be safe */
|
||||
strscpy(logfile_pattern, pattern, sizeof(logfile_pattern));
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Open the log file. Return zero if the log file could not be opened,
|
||||
* else return nonzero (success).
|
||||
*/
|
||||
|
||||
int open_log(void)
|
||||
{
|
||||
if (logfile)
|
||||
return 1;
|
||||
strbcpy(current_filename, gen_log_filename());
|
||||
logfile = fopen(current_filename, "a");
|
||||
if (logfile) {
|
||||
setbuf(logfile, NULL);
|
||||
if (logmemptr) {
|
||||
int res, errno_save;
|
||||
if (logmemptr > logmem) {
|
||||
res = fwrite(logmem, logmemptr-logmem, 1, logfile);
|
||||
errno_save = errno;
|
||||
} else {
|
||||
res = 1; /* i.e. no error */
|
||||
#if CLEAN_COMPILE
|
||||
errno_save = 0; /* warning killer for dumb compilers */
|
||||
#endif
|
||||
}
|
||||
logmemptr = NULL;
|
||||
if (res != 1) {
|
||||
/* make sure this is AFTER the memory log has been closed! */
|
||||
errno = errno_save;
|
||||
log_perror("open_log(): error writing memory log");
|
||||
}
|
||||
}
|
||||
}
|
||||
return logfile!=NULL ? 1 : 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Open a virtual log file in memory. The contents of the memory log file
|
||||
* will be written to a physical log file when open_log() is called and
|
||||
* executes successfully. If a physical log file is already open, does
|
||||
* nothing. Always succeeds (and returns nonzero).
|
||||
*/
|
||||
|
||||
int open_memory_log(void)
|
||||
{
|
||||
if (logfile || logmemptr)
|
||||
return 1;
|
||||
logmemptr = logmem;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Close the log file. */
|
||||
|
||||
void close_log(void)
|
||||
{
|
||||
if (logmemptr) {
|
||||
logmemptr = NULL;
|
||||
}
|
||||
if (logfile) {
|
||||
fclose(logfile);
|
||||
logfile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Reopen the log file with the current value of LogFilename (for use when
|
||||
* rehashing configuration files or rotating the log file). Return value
|
||||
* is like open_log().
|
||||
*/
|
||||
|
||||
int reopen_log(void)
|
||||
{
|
||||
char *newname;
|
||||
FILE *f;
|
||||
|
||||
newname = gen_log_filename();
|
||||
/* Make sure it will fit in current_filename later */
|
||||
if (strlen(newname) > sizeof(current_filename)-1)
|
||||
newname[sizeof(current_filename)-1] = 0;
|
||||
f = fopen(newname, "a");
|
||||
if (!f)
|
||||
return 0;
|
||||
setbuf(f, NULL);
|
||||
if (logfile)
|
||||
fclose(logfile);
|
||||
logfile = f;
|
||||
strcpy(current_filename, newname); /* safe b/c of length check above */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return nonzero if the log file is currently open, zero if closed. */
|
||||
|
||||
int log_is_open(void)
|
||||
{
|
||||
return logfile != NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Log stuff to the log file with a datestamp when debug >= debuglevel.
|
||||
* errno is preserved. If debuglevel is greater than zero, "debug: " is
|
||||
* prefixed to the message. If do_perror is nonzero, the message is
|
||||
* followed by a ": " and system error message, a la perror() (if errno<0,
|
||||
* it is treated as an herror value from hostname resolution). If
|
||||
* modulename is not NULL, it is used as a prefix to the error message.
|
||||
*
|
||||
* [module_]log[_perror][_debug]() are implemented as macros which call
|
||||
* this function.
|
||||
*/
|
||||
|
||||
void do_log(int debuglevel, int do_perror, const char *modulename,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
if (debug >= debuglevel) {
|
||||
va_list args;
|
||||
int errno_save = errno;
|
||||
|
||||
va_start(args, fmt);
|
||||
if (logfile)
|
||||
check_log_rotate();
|
||||
write_time();
|
||||
if (debuglevel > 0)
|
||||
logputs("debug: ");
|
||||
if (modulename)
|
||||
logprintf("(%s) ", modulename);
|
||||
vlogprintf(fmt, args);
|
||||
if (do_perror) {
|
||||
logprintf(": %s",
|
||||
(errno_save<0) ? hstrerror(-errno_save)
|
||||
: strerror(errno_save));
|
||||
}
|
||||
logputs("\n");
|
||||
va_end(args);
|
||||
errno = errno_save;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* We've hit something we can't recover from. Let people know what
|
||||
* happened, then go down.
|
||||
*/
|
||||
|
||||
void fatal(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[4096];
|
||||
|
||||
if (in_fatal)
|
||||
exit(2);
|
||||
in_fatal++;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
if (logfile)
|
||||
check_log_rotate();
|
||||
write_time();
|
||||
logprintf("FATAL: %s\n", buf);
|
||||
if (servsock && sock_isconn(servsock))
|
||||
wallops(NULL, "FATAL ERROR! %s", buf);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Same thing, but do it like perror(). */
|
||||
|
||||
void fatal_perror(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[4096];
|
||||
const char *errstr;
|
||||
|
||||
if (in_fatal)
|
||||
exit(2);
|
||||
in_fatal++;
|
||||
errstr = (errno<0) ? hstrerror(-errno) : strerror(errno);
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
if (logfile)
|
||||
check_log_rotate();
|
||||
write_time();
|
||||
logprintf("FATAL: %s: %s\n", buf, errstr);
|
||||
if (servsock && sock_isconn(servsock))
|
||||
wallops(NULL, "FATAL ERROR! %s: %s", buf, errstr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
56
log.h
Normal file
56
log.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* Definitions and prototypes related to logging.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
extern void set_logfile(const char *pattern);
|
||||
extern int open_log(void);
|
||||
extern int open_memory_log(void);
|
||||
extern void close_log(void);
|
||||
extern int reopen_log(void);
|
||||
extern int log_is_open(void);
|
||||
extern void do_log(int debuglevel, int do_perror, const char *modulename,
|
||||
const char *fmt, ...)
|
||||
FORMAT(printf,4,5);
|
||||
extern void fatal(const char *fmt, ...)
|
||||
FORMAT(printf,1,2);
|
||||
extern void fatal_perror(const char *fmt, ...)
|
||||
FORMAT(printf,1,2);
|
||||
|
||||
#undef log /* in case system includes #define it */
|
||||
#define log(fmt,...) \
|
||||
log_debug(0, fmt , ##__VA_ARGS__)
|
||||
#define log_perror(fmt,...) \
|
||||
log_perror_debug(0, fmt , ##__VA_ARGS__)
|
||||
#define log_debug(debuglevel,fmt,...) \
|
||||
do_log(debuglevel, 0, NULL, fmt, ##__VA_ARGS__)
|
||||
#define log_perror_debug(debuglevel,fmt,...) \
|
||||
do_log(debuglevel, 1, NULL, fmt, ##__VA_ARGS__)
|
||||
#define module_log(fmt,...) \
|
||||
module_log_debug(0, fmt , ##__VA_ARGS__)
|
||||
#define module_log_perror(fmt,...) \
|
||||
module_log_perror_debug(0, fmt , ##__VA_ARGS__)
|
||||
#define module_log_debug(debuglevel,fmt,...) \
|
||||
do_log(debuglevel, 0, MODULE_NAME, fmt, ##__VA_ARGS__)
|
||||
#define module_log_perror_debug(debuglevel,fmt,...) \
|
||||
do_log(debuglevel, 1, MODULE_NAME, fmt, ##__VA_ARGS__)
|
||||
|
||||
#endif /* LOG_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
379
main.c
Normal file
379
main.c
Normal file
@ -0,0 +1,379 @@
|
||||
/* IRC Services -- main source file.
|
||||
* Copyright (c) 1996-2009 Andrew Church <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#include "services.h"
|
||||
#include "databases.h"
|
||||
#include "modules.h"
|
||||
#include "timeout.h"
|
||||
#include <fcntl.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
|
||||
/* Hack for sigsetjmp(); since (at least with glibc, and it shouldn't hurt
|
||||
* anywhere else) sigsetjmp() only works if you don't leave the stack frame
|
||||
* it was called from, we have to call it before calling the signals.c
|
||||
* wrapper. */
|
||||
|
||||
#define DO_SIGSETJMP() do { \
|
||||
static sigjmp_buf buf; \
|
||||
if (!sigsetjmp(buf, 1)) \
|
||||
do_sigsetjmp(&buf); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/******** Global variables! ********/
|
||||
|
||||
/* Command-line options: (note that configuration variables are in init.c) */
|
||||
const char *services_dir = SERVICES_DIR;/* -dir=dirname */
|
||||
int debug = 0; /* -debug */
|
||||
int readonly = 0; /* -readonly */
|
||||
int nofork = 0; /* -nofork */
|
||||
int noexpire = 0; /* -noexpire */
|
||||
int noakill = 0; /* -noakill */
|
||||
int forceload = 0; /* -forceload */
|
||||
int encrypt_all = 0; /* -encrypt-all */
|
||||
|
||||
|
||||
/* Set to 1 while we are linked to the network */
|
||||
int linked = 0;
|
||||
|
||||
/* Set to 1 if we are to quit */
|
||||
int quitting = 0;
|
||||
|
||||
/* Set to 1 if we are to quit after saving databases */
|
||||
int delayed_quit = 0;
|
||||
|
||||
/* Set to 1 if we are to restart */
|
||||
int restart = 0;
|
||||
|
||||
/* Contains a message as to why services is terminating */
|
||||
char quitmsg[BUFSIZE] = "";
|
||||
|
||||
/* Input buffer - global, so we can dump it if something goes wrong */
|
||||
char inbuf[BUFSIZE];
|
||||
|
||||
/* Socket for talking to server */
|
||||
Socket *servsock = NULL;
|
||||
|
||||
/* Should we update the databases now? */
|
||||
int save_data = 0;
|
||||
|
||||
/* At what time were we started? */
|
||||
time_t start_time;
|
||||
|
||||
/* Were we unable to open the log? (and the error that occurred) */
|
||||
int openlog_failed, openlog_errno;
|
||||
|
||||
/* Module callbacks (global so init.c can set them): */
|
||||
int cb_connect = -1;
|
||||
int cb_save_complete = -1;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Callbacks for uplink IRC server socket. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Actions to perform when connection to server completes. */
|
||||
|
||||
void connect_callback(Socket *s, void *param_unused)
|
||||
{
|
||||
sock_set_blocking(s, 1);
|
||||
sock_setcb(s, SCB_READLINE, readfirstline_callback);
|
||||
send_server();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Actions to perform when connection to server is broken. */
|
||||
|
||||
void disconnect_callback(Socket *s, void *param)
|
||||
{
|
||||
/* We are no longer linked */
|
||||
linked = 0;
|
||||
|
||||
if (param == DISCONN_REMOTE || param == DISCONN_CONNFAIL) {
|
||||
int errno_save = errno;
|
||||
const char *msg = (param==DISCONN_REMOTE ? "Read error from server"
|
||||
: "Connection to server failed");
|
||||
snprintf(quitmsg, sizeof(quitmsg),
|
||||
"%s: %s", msg, strerror(errno_save));
|
||||
if (param == DISCONN_REMOTE) {
|
||||
/* If we were already connected, make sure any changed data is
|
||||
* updated before we terminate. */
|
||||
delayed_quit = 1;
|
||||
save_data = 1;
|
||||
} else {
|
||||
/* The connection was never made in the first place, so we
|
||||
* discard any changes (such as expirations) made on the
|
||||
* assumption that either a configuration problem or other
|
||||
* external problem exists. Such changes will be saved the
|
||||
* next time Services successfully connects to a server. */
|
||||
quitting = 1;
|
||||
}
|
||||
}
|
||||
sock_setcb(s, SCB_READLINE, NULL);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Actions to perform when first line is read from socket. */
|
||||
|
||||
void readfirstline_callback(Socket *s, void *param_unused)
|
||||
{
|
||||
sock_setcb(s, SCB_READLINE, readline_callback);
|
||||
|
||||
if (!sgets2(inbuf, sizeof(inbuf), s)) {
|
||||
/* This shouldn't happen, but just in case... */
|
||||
disconn(s);
|
||||
fatal("Unable to read greeting from server socket");
|
||||
}
|
||||
if (strnicmp(inbuf, "ERROR", 5) == 0) {
|
||||
/* Close server socket first to stop wallops, since the other
|
||||
* server doesn't want to listen to us anyway */
|
||||
disconn(s);
|
||||
fatal("Remote server returned: %s", inbuf);
|
||||
}
|
||||
|
||||
/* We're now linked to the network */
|
||||
linked = 1;
|
||||
|
||||
/* Announce a logfile error if there was one */
|
||||
if (openlog_failed) {
|
||||
wallops(NULL, "Warning: couldn't open logfile: %s",
|
||||
strerror(openlog_errno));
|
||||
openlog_failed = 0;
|
||||
}
|
||||
|
||||
/* Bring in our pseudo-clients */
|
||||
introduce_user(NULL);
|
||||
|
||||
/* Let modules do their startup stuff */
|
||||
call_callback(cb_connect);
|
||||
|
||||
/* Process the line we read in above */
|
||||
process();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Actions to perform when subsequent lines are read from socket. */
|
||||
|
||||
void readline_callback(Socket *s, void *param_unused)
|
||||
{
|
||||
if (sgets2(inbuf, sizeof(inbuf), s))
|
||||
process();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Lock the data directory if possible; return nonzero on success, zero on
|
||||
* failure (data directory already locked or cannot create lock file).
|
||||
* On failure, errno will be EEXIST if the directory was already locked or
|
||||
* a value other than EEXIST if an error occurred creating the lock file.
|
||||
*
|
||||
* This does not attempt to correct for NFS brokenness w.r.t. O_EXCL and
|
||||
* will contain a race condition when used on an NFS filesystem (or any
|
||||
* other filesystem which does not support O_EXCL properly).
|
||||
*/
|
||||
|
||||
int lock_data(void)
|
||||
{
|
||||
int fd;
|
||||
|
||||
errno = 0;
|
||||
fd = open(LockFilename, O_WRONLY | O_CREAT | O_EXCL, 0);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Check whether the data directory is locked without actually attempting
|
||||
* to lock it. Returns 1 if locked, 0 if not, or -1 if an error occurred
|
||||
* while trying to check (in which case errno will be set to an appropriate
|
||||
* value, i.e. whatever access() returned).
|
||||
*/
|
||||
|
||||
int is_data_locked(void)
|
||||
{
|
||||
errno = 0;
|
||||
if (access(LockFilename, F_OK) == 0)
|
||||
return 1;
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Unlock the data directory. Assumes we locked it in the first place.
|
||||
* Returns 1 on success, 0 on failure (unable to remove the lock file), or
|
||||
* -1 if the lock file didn't exist in the first place (possibly because it
|
||||
* was removed by another (misbehaving) program).
|
||||
*/
|
||||
|
||||
int unlock_data(void)
|
||||
{
|
||||
errno = 0;
|
||||
if (unlink(LockFilename) == 0)
|
||||
return 1;
|
||||
if (errno == ENOENT)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Subroutine to save databases. */
|
||||
|
||||
void save_data_now(void)
|
||||
{
|
||||
if (!lock_data()) {
|
||||
if (errno == EEXIST) {
|
||||
log("warning: databases are locked, not updating");
|
||||
wallops(NULL,
|
||||
"\2Warning:\2 Databases are locked, and cannot be updated."
|
||||
" Remove the `%s%s%s' file to allow database updates.",
|
||||
*LockFilename=='/' ? "" : services_dir,
|
||||
*LockFilename=='/' ? "" : "/", LockFilename);
|
||||
} else {
|
||||
log_perror("warning: unable to lock databases, not updating");
|
||||
wallops(NULL, "\2Warning:\2 Unable to lock databases; databases"
|
||||
" will not be updated.");
|
||||
}
|
||||
call_callback_1(cb_save_complete, 0);
|
||||
} else {
|
||||
log_debug(1, "Saving databases");
|
||||
save_all_dbtables();
|
||||
if (!unlock_data()) {
|
||||
log_perror("warning: unable to unlock databases");
|
||||
wallops(NULL,
|
||||
"\2Warning:\2 Unable to unlock databases; future database"
|
||||
" updates may fail until the `%s%s%s' file is removed.",
|
||||
*LockFilename=='/' ? "" : services_dir,
|
||||
*LockFilename=='/' ? "" : "/", LockFilename);
|
||||
}
|
||||
call_callback_1(cb_save_complete, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Main routine. (What does it look like? :-) ) */
|
||||
|
||||
int main(int ac, char **av, char **envp)
|
||||
{
|
||||
volatile time_t last_update; /* When did we last update the databases? */
|
||||
volatile uint32 last_check; /* When did we last check timeouts? */
|
||||
|
||||
|
||||
/*** Initialization stuff. ***/
|
||||
|
||||
if (init(ac, av) < 0) {
|
||||
fprintf(stderr, "Initialization failed, exiting.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Set up timers. */
|
||||
last_send = time(NULL);
|
||||
last_update = time(NULL);
|
||||
last_check = time(NULL);
|
||||
|
||||
/* The signal handler routine will drop back here with quitting != 0
|
||||
* if it gets called. */
|
||||
DO_SIGSETJMP();
|
||||
|
||||
|
||||
/*** Main loop. ***/
|
||||
|
||||
while (!quitting) {
|
||||
time_t now = time(NULL);
|
||||
int32 now_msec = time_msec();
|
||||
|
||||
log_debug(2, "Top of main loop");
|
||||
|
||||
if (!readonly && (save_data || now-last_update >= UpdateTimeout)) {
|
||||
save_data_now();
|
||||
save_data = 0;
|
||||
last_update = now;
|
||||
}
|
||||
if (delayed_quit)
|
||||
break;
|
||||
|
||||
if (sock_isconn(servsock)) {
|
||||
if (PingFrequency && now - last_send >= PingFrequency)
|
||||
send_cmd(NULL, "PING :%s", ServerName);
|
||||
}
|
||||
|
||||
if (now_msec - last_check >= TimeoutCheck) {
|
||||
check_timeouts();
|
||||
last_check = now_msec;
|
||||
}
|
||||
|
||||
check_sockets();
|
||||
|
||||
if (!MergeChannelModes)
|
||||
set_cmode(NULL, NULL); /* flush out any mode changes made */
|
||||
}
|
||||
|
||||
|
||||
/*** Cleanup stuff. ***/
|
||||
|
||||
cleanup();
|
||||
|
||||
/* Check for restart instead of exit */
|
||||
if (restart) {
|
||||
execve(SERVICES_BIN, av, envp);
|
||||
/* If we get here, the exec() failed; override readonly and write a
|
||||
* note to the log file */
|
||||
{
|
||||
int errno_save = errno;
|
||||
open_log();
|
||||
errno = errno_save;
|
||||
}
|
||||
log_perror("Restart failed");
|
||||
close_log();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Terminate program */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
331
memory.c
Normal file
331
memory.c
Normal file
@ -0,0 +1,331 @@
|
||||
/* Memory management routines.
|
||||
* Leak checking based on code by Kelmar (2001/1/13).
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#define NO_MEMREDEF
|
||||
#include "services.h"
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* smalloc, scalloc, srealloc, sstrdup:
|
||||
* Versions of the memory allocation functions which will cause the
|
||||
* program to terminate with an "Out of memory" error if the memory
|
||||
* cannot be allocated. (Hence, the return value from these functions
|
||||
* is never NULL, except for smalloc()/scalloc() called with a size of
|
||||
* zero.)
|
||||
*/
|
||||
|
||||
#if MEMCHECKS
|
||||
# define FILELINE , const char *file, int line
|
||||
# define xmalloc(a) MCmalloc(a,file,line)
|
||||
# define xsmalloc(a) smalloc(a,file,line)
|
||||
# define xcalloc(a,b) MCcalloc(a,b,file,line)
|
||||
# define xrealloc(a,b) MCrealloc(a,b,file,line)
|
||||
# define xfree(a) MCfree(a,file,line)
|
||||
#else
|
||||
# define FILELINE /*nothing*/
|
||||
# define xmalloc(a) malloc(a)
|
||||
# define xsmalloc(a) smalloc(a)
|
||||
# define xcalloc(a,b) calloc(a,b)
|
||||
# define xrealloc(a,b) realloc(a,b)
|
||||
# define xfree(a) free(a)
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void *smalloc(long size FILELINE)
|
||||
{
|
||||
void *buf;
|
||||
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
buf = xmalloc(size);
|
||||
if (buf == NULL)
|
||||
raise(SIGUSR1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void *scalloc(long els, long elsize FILELINE)
|
||||
{
|
||||
void *buf;
|
||||
|
||||
if (elsize == 0 || els == 0)
|
||||
return NULL;
|
||||
buf = xcalloc(elsize, els);
|
||||
if (buf == NULL)
|
||||
raise(SIGUSR1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void *srealloc(void *oldptr, long newsize FILELINE)
|
||||
{
|
||||
void *buf;
|
||||
|
||||
if (newsize == 0) {
|
||||
xfree(oldptr);
|
||||
return NULL;
|
||||
}
|
||||
buf = xrealloc(oldptr, newsize);
|
||||
if (buf == NULL)
|
||||
raise(SIGUSR1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
char *sstrdup(const char *s FILELINE)
|
||||
{
|
||||
char *t = xsmalloc(strlen(s) + 1);
|
||||
strcpy(t, s); /* safe, obviously */
|
||||
return t;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/************ Everything from here down is MEMCHECKS-related. ************/
|
||||
/*************************************************************************/
|
||||
|
||||
#if MEMCHECKS
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static long allocated = 0; /* Amount of memory currently allocated */
|
||||
static long runtime = 0; /* `allocated' value at init_memory() time */
|
||||
|
||||
# if SHOWALLOCS
|
||||
int showallocs = 1; /* Actually log allocations? */
|
||||
# endif
|
||||
|
||||
typedef struct _smemblock {
|
||||
long size; /* Size of this block */
|
||||
int32 sig; /* Signature word: 0x5AFEC0DE */
|
||||
} MemBlock;
|
||||
# define SIGNATURE 0x5AFEC0DE
|
||||
# define FREE_SIGNATURE 0xDEADBEEF /* Used for freed memory */
|
||||
# define NEW_FILL 0xD017D017 /* New allocs are filled with this */
|
||||
# define FREE_FILL 0xBEEF1E57 /* Freed memory is filled with this */
|
||||
|
||||
# define MEMBLOCK_TO_PTR(mb) ((void *)((char *)(mb) + sizeof(MemBlock)))
|
||||
# define PTR_TO_MEMBLOCK(ptr) ((MemBlock *)((char *)(ptr) - sizeof(MemBlock)))
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Leak-checking initialization and exit code. */
|
||||
|
||||
static void show_leaks(void)
|
||||
{
|
||||
if (runtime >= 0 && (allocated - runtime) > 0) {
|
||||
log("SAFEMEM: There were %ld bytes leaked on exit!",
|
||||
(allocated - runtime));
|
||||
} else {
|
||||
log("SAFEMEM: No memory leaks detected.");
|
||||
}
|
||||
}
|
||||
|
||||
void init_memory(void)
|
||||
{
|
||||
runtime = allocated;
|
||||
log("init_memory(): runtime = %ld", runtime);
|
||||
atexit(show_leaks);
|
||||
}
|
||||
|
||||
/* Used to avoid memory-leak message from the parent process */
|
||||
void uninit_memory(void)
|
||||
{
|
||||
runtime = -1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Helper to fill memory with a given 32-bit value. */
|
||||
|
||||
static inline void fill32(void *ptr, uint32 value, long size)
|
||||
{
|
||||
register uint32 *ptr32 = ptr;
|
||||
register uint32 v = value;
|
||||
while (size >= 4) {
|
||||
*ptr32++ = v;
|
||||
size -= 4;
|
||||
}
|
||||
if (size > 0)
|
||||
memcpy(ptr32, &value, size);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Substitutes for malloc() and friends. memory.h redefines malloc(), etc.
|
||||
* to these functions if MEMCHECKS is defined. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void *MCmalloc(long size, const char *file, int line)
|
||||
{
|
||||
MemBlock *mb;
|
||||
void *data;
|
||||
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
mb = malloc(size + sizeof(MemBlock));
|
||||
if (mb) {
|
||||
mb->size = size;
|
||||
mb->sig = SIGNATURE;
|
||||
data = MEMBLOCK_TO_PTR(mb);
|
||||
allocated += size;
|
||||
# if SHOWALLOCS
|
||||
if (showallocs) {
|
||||
log("smalloc(): Allocated %ld bytes at %p (%s:%d)",
|
||||
size, data, file, line);
|
||||
}
|
||||
# endif
|
||||
fill32(data, NEW_FILL, size);
|
||||
return data;
|
||||
} else {
|
||||
# if SHOWALLOCS
|
||||
if (showallocs) {
|
||||
log("mcalloc(): Unable to allocate %ld bytes (%s:%d)",
|
||||
size, file, line);
|
||||
}
|
||||
# endif
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void *MCcalloc(long els, long elsize, const char *file, int line)
|
||||
{
|
||||
MemBlock *mb;
|
||||
void *data;
|
||||
|
||||
if (elsize == 0 || els == 0)
|
||||
return NULL;
|
||||
mb = malloc(els*elsize + sizeof(MemBlock));
|
||||
if (mb) {
|
||||
mb->size = elsize * els;
|
||||
mb->sig = SIGNATURE;
|
||||
data = MEMBLOCK_TO_PTR(mb);
|
||||
memset(data, 0, els*elsize);
|
||||
allocated += mb->size;
|
||||
# if SHOWALLOCS
|
||||
if (showallocs) {
|
||||
log("scalloc(): Allocated %ld bytes at %p (%s:%d)",
|
||||
els*elsize, data, file, line);
|
||||
}
|
||||
# endif
|
||||
return data;
|
||||
} else {
|
||||
# if SHOWALLOCS
|
||||
if (showallocs) {
|
||||
log("scalloc(): Unable to allocate %ld bytes (%s:%d)",
|
||||
els*elsize, file, line);
|
||||
}
|
||||
# endif
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void *MCrealloc(void *oldptr, long newsize, const char *file, int line)
|
||||
{
|
||||
MemBlock *newb, *oldb;
|
||||
long oldsize;
|
||||
void *data;
|
||||
|
||||
if (newsize == 0) {
|
||||
MCfree(oldptr, file, line);
|
||||
return NULL;
|
||||
}
|
||||
if (oldptr == NULL)
|
||||
return MCmalloc(newsize, file, line);
|
||||
oldb = PTR_TO_MEMBLOCK(oldptr);
|
||||
if (oldb->sig != SIGNATURE) {
|
||||
fatal("Attempt to realloc() an invalid pointer (%p) (%s:%d)",
|
||||
oldptr, file, line);
|
||||
}
|
||||
oldsize = oldb->size;
|
||||
newb = realloc(oldb, newsize + sizeof(MemBlock));
|
||||
if (newb) {
|
||||
newb->size = newsize;
|
||||
newb->sig = SIGNATURE; /* should already be set... */
|
||||
data = MEMBLOCK_TO_PTR(newb);
|
||||
allocated += (newsize - oldsize);
|
||||
# if SHOWALLOCS
|
||||
if (showallocs) {
|
||||
log("srealloc(): Adjusted %ld bytes (%p) to %ld bytes (%p)"
|
||||
" (%s:%d)", oldsize, oldptr, newsize, data, file, line);
|
||||
}
|
||||
# endif
|
||||
return data;
|
||||
} else {
|
||||
# if SHOWALLOCS
|
||||
if (showallocs) {
|
||||
log("srealloc(): Unable to adjust %ld bytes (%p) to %ld bytes"
|
||||
" (%s:%d)", oldsize, oldptr, newsize, file, line);
|
||||
}
|
||||
# endif
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void MCfree(void *ptr, const char *file, int line)
|
||||
{
|
||||
MemBlock *mb;
|
||||
|
||||
if (ptr == NULL)
|
||||
return;
|
||||
mb = PTR_TO_MEMBLOCK(ptr);
|
||||
if (mb->sig != SIGNATURE) {
|
||||
fatal("Attempt to free() an invalid pointer (%p) (%s:%d)",
|
||||
ptr, file, line);
|
||||
}
|
||||
allocated -= mb->size;
|
||||
# if SHOWALLOCS
|
||||
if (showallocs) {
|
||||
log("sfree(): Released %ld bytes at %p (%s:%d)",
|
||||
mb->size, ptr, file, line);
|
||||
}
|
||||
# endif
|
||||
mb->size = FREE_FILL;
|
||||
mb->sig = FREE_SIGNATURE;
|
||||
fill32(ptr, FREE_FILL, mb->size);
|
||||
free(mb);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
char *MCstrdup(const char *s, const char *file, int line)
|
||||
{
|
||||
char *t = MCmalloc(strlen(s) + 1, file, line);
|
||||
strcpy(t, s); /* safe, obviously */
|
||||
return t;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* MEMCHECKS */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
81
memory.h
Normal file
81
memory.h
Normal file
@ -0,0 +1,81 @@
|
||||
/* Memory management include file.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef MEMORY_H
|
||||
#define MEMORY_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if MEMCHECKS
|
||||
# define FILELINE , const char *file, int line
|
||||
#else
|
||||
# define FILELINE /*nothing*/
|
||||
#endif
|
||||
extern void *smalloc(long size FILELINE);
|
||||
extern void *scalloc(long els, long elsize FILELINE);
|
||||
extern void *srealloc(void *oldptr, long newsize FILELINE);
|
||||
extern char *sstrdup(const char *s FILELINE);
|
||||
#undef FILELINE
|
||||
|
||||
#if MEMCHECKS
|
||||
|
||||
# if SHOWALLOCS
|
||||
extern int showallocs;
|
||||
# endif
|
||||
extern void init_memory(void);
|
||||
extern void uninit_memory(void);
|
||||
extern void *MCmalloc(long size, const char *file, int line);
|
||||
extern void *MCcalloc(long elsize, long els, const char *file, int line);
|
||||
extern void *MCrealloc(void *oldptr, long newsize, const char *file, int line);
|
||||
extern void MCfree(void *ptr, const char *file, int line);
|
||||
extern char *MCstrdup(const char *s, const char *file, int line);
|
||||
|
||||
#else /* !MEMCHECKS */
|
||||
|
||||
# define init_memory() /*nothing*/
|
||||
# define uninit_memory() /*nothing*/
|
||||
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#if MEMCHECKS && !defined(NO_MEMREDEF)
|
||||
|
||||
# undef malloc
|
||||
# undef calloc
|
||||
# undef realloc
|
||||
# undef free
|
||||
# undef strdup
|
||||
|
||||
# define malloc(size) MCmalloc(size,__FILE__,__LINE__)
|
||||
# define calloc(elsize,els) MCcalloc(elsize,els,__FILE__,__LINE__)
|
||||
# define realloc(ptr,size) MCrealloc(ptr,size,__FILE__,__LINE__)
|
||||
# define free(ptr) MCfree(ptr,__FILE__,__LINE__)
|
||||
# define strdup(str) MCstrdup(str,__FILE__,__LINE__)
|
||||
|
||||
# define smalloc(size) smalloc(size,__FILE__,__LINE__)
|
||||
# define scalloc(elsize,els) scalloc(elsize,els,__FILE__,__LINE__)
|
||||
# define srealloc(ptr,size) srealloc(ptr,size,__FILE__,__LINE__)
|
||||
# define sstrdup(str) sstrdup(str,__FILE__,__LINE__)
|
||||
|
||||
#endif /* MEMCHECKS && !NO_MEMREDEF */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* MEMORY_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
657
messages.c
Normal file
657
messages.c
Normal file
@ -0,0 +1,657 @@
|
||||
/* Definitions of IRC message functions and list of messages.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "messages.h"
|
||||
#include "language.h"
|
||||
#include "modules.h"
|
||||
#include "version.h"
|
||||
#include "modules/operserv/operserv.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Enable ignore code for PRIVMSGs? */
|
||||
int allow_ignore = 1;
|
||||
|
||||
/* Callbacks for various messages */
|
||||
static int cb_privmsg = -1;
|
||||
static int cb_whois = -1;
|
||||
|
||||
/*************************************************************************/
|
||||
/************************ Basic message handling *************************/
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_nickcoll(char *source, int ac, char **av)
|
||||
{
|
||||
if (ac < 1)
|
||||
return;
|
||||
if (!readonly)
|
||||
introduce_user(av[0]);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_ping(char *source, int ac, char **av)
|
||||
{
|
||||
if (ac < 1)
|
||||
return;
|
||||
send_cmd(ServerName, "PONG %s %s", ac>1 ? av[1] : ServerName, av[0]);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_info(char *source, int ac, char **av)
|
||||
{
|
||||
int i;
|
||||
struct tm *tm;
|
||||
char timebuf[64];
|
||||
|
||||
if (!*source) {
|
||||
log("Source missing from INFO message");
|
||||
return;
|
||||
}
|
||||
|
||||
tm = localtime(&start_time);
|
||||
strftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S %Y %Z", tm);
|
||||
|
||||
for (i = 0; info_text[i]; i++)
|
||||
send_cmd(ServerName, "371 %s :%s", source, info_text[i]);
|
||||
send_cmd(ServerName, "371 %s :Version %s (%s)", source,
|
||||
version_number, version_build);
|
||||
send_cmd(ServerName, "371 %s :On-line since %s", source, timebuf);
|
||||
send_cmd(ServerName, "374 %s :End of /INFO list.", source);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_join(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from JOIN message");
|
||||
return;
|
||||
} else if (ac < 1) {
|
||||
return;
|
||||
}
|
||||
do_join(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_kick(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from KICK message");
|
||||
return;
|
||||
} else if (ac != 3) {
|
||||
return;
|
||||
}
|
||||
do_kick(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_kill(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from KILL message");
|
||||
return;
|
||||
} else if (ac != 2) {
|
||||
return;
|
||||
}
|
||||
/* Recover if someone kills us. If introduce_user() returns 0, then
|
||||
* the user in question isn't a pseudoclient, so pass it on to the
|
||||
* user handling code. */
|
||||
if (!introduce_user(av[0]))
|
||||
do_kill(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_mode(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from MODE message");
|
||||
return;
|
||||
}
|
||||
|
||||
if (*av[0] == '#' || *av[0] == '&') {
|
||||
if (ac < 2)
|
||||
return;
|
||||
do_cmode(source, ac, av);
|
||||
} else {
|
||||
if (ac != 2) {
|
||||
return;
|
||||
} else if (irc_stricmp(source,av[0])!=0 && strchr(source,'.')==NULL) {
|
||||
log("user: MODE %s %s from different nick %s!", av[0], av[1],
|
||||
source);
|
||||
wallops(NULL, "%s attempted to change mode %s for %s",
|
||||
source, av[1], av[0]);
|
||||
return;
|
||||
}
|
||||
do_umode(source, ac, av);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_motd(char *source, int ac, char **av)
|
||||
{
|
||||
FILE *f;
|
||||
char buf[BUFSIZE];
|
||||
|
||||
if (!*source) {
|
||||
log("Source missing from MOTD message");
|
||||
return;
|
||||
}
|
||||
|
||||
f = fopen(MOTDFilename, "r");
|
||||
send_cmd(ServerName, "375 %s :- %s Message of the Day",
|
||||
source, ServerName);
|
||||
if (f) {
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
buf[strlen(buf)-1] = 0;
|
||||
send_cmd(ServerName, "372 %s :- %s", source, buf);
|
||||
}
|
||||
fclose(f);
|
||||
} else {
|
||||
send_cmd(ServerName, "372 %s :- MOTD file not found! Please "
|
||||
"contact your IRC administrator.", source);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_part(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from PART message");
|
||||
return;
|
||||
} else if (ac < 1 || ac > 2) {
|
||||
return;
|
||||
}
|
||||
do_part(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char msg_up_inactive[] =
|
||||
"Network buffer size exceeded inactive threshold (%d%%), not processing"
|
||||
" PRIVMSGs";
|
||||
static const char msg_up_ignore[] =
|
||||
"Network buffer size exceeded ignore threshold (%d%%), ignoring PRIVMSGs";
|
||||
static const char msg_down_inactive[] =
|
||||
"Network buffer size dropped below ignore threshold (%d%%), not"
|
||||
" processing PRIVMSGs";
|
||||
static const char msg_down_normal[] =
|
||||
"Network buffer size dropped below inactive threshold (%d%%),"
|
||||
" processing PRIVMSGs normally";
|
||||
|
||||
static void m_privmsg(char *source, int ac, char **av)
|
||||
{
|
||||
/* PRIVMSG handling status based on NetBufferLimit settings */
|
||||
static enum {NORMAL,INACTIVE,IGNORE} netbuf_status = NORMAL;
|
||||
|
||||
uint32 start, stop; /* When processing started and finished */
|
||||
User *u = get_user(source);
|
||||
char *s;
|
||||
|
||||
|
||||
if (!*source) {
|
||||
log("Source missing from PRIVMSG message");
|
||||
return;
|
||||
} else if (ac != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If a server is specified (nick@server format), make sure it matches
|
||||
* us, and strip it off. */
|
||||
s = strchr(av[0], '@');
|
||||
if (s) {
|
||||
*s++ = 0;
|
||||
if (stricmp(s, ServerName) != 0)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check network buffer status. */
|
||||
if (NetBufferLimitInactive) {
|
||||
int bufstat = sock_bufstat(servsock, NULL, NULL, NULL, NULL);
|
||||
const char *message = NULL;
|
||||
int value = 0;
|
||||
switch (netbuf_status) {
|
||||
case NORMAL:
|
||||
if (NetBufferLimitIgnore && bufstat >= NetBufferLimitIgnore) {
|
||||
message = msg_up_ignore;
|
||||
value = NetBufferLimitIgnore;
|
||||
netbuf_status = IGNORE;
|
||||
} else if (bufstat >= NetBufferLimitInactive) {
|
||||
message = msg_up_inactive;
|
||||
value = NetBufferLimitInactive;
|
||||
netbuf_status = INACTIVE;
|
||||
}
|
||||
break;
|
||||
case INACTIVE:
|
||||
if (NetBufferLimitIgnore && bufstat >= NetBufferLimitIgnore) {
|
||||
message = msg_up_ignore;
|
||||
value = NetBufferLimitIgnore;
|
||||
netbuf_status = IGNORE;
|
||||
} else if (bufstat < NetBufferLimitInactive) {
|
||||
message = msg_down_normal;
|
||||
value = NetBufferLimitInactive;
|
||||
netbuf_status = NORMAL;
|
||||
}
|
||||
break;
|
||||
case IGNORE:
|
||||
if (bufstat < NetBufferLimitInactive) {
|
||||
message = msg_down_normal;
|
||||
value = NetBufferLimitInactive;
|
||||
netbuf_status = NORMAL;
|
||||
} else if (bufstat < NetBufferLimitIgnore) {
|
||||
message = msg_down_inactive;
|
||||
value = NetBufferLimitIgnore;
|
||||
netbuf_status = INACTIVE;
|
||||
}
|
||||
break;
|
||||
} /* switch (netbuf_status) */
|
||||
if (message) {
|
||||
log(message, value);
|
||||
wallops(NULL, message, value);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we should ignore. Operators always get through. */
|
||||
if (u) {
|
||||
ignore_update(u, 0);
|
||||
if (!is_oper(u)) {
|
||||
if (netbuf_status != NORMAL) {
|
||||
if (netbuf_status == INACTIVE) {
|
||||
if (u)
|
||||
notice_lang(av[0], u, SERVICES_IS_BUSY);
|
||||
else
|
||||
notice(av[0], source,
|
||||
getstring(NULL, SERVICES_IS_BUSY));
|
||||
}
|
||||
return;
|
||||
} else if (allow_ignore && IgnoreDecay && IgnoreThreshold) {
|
||||
if (u->ignore >= IgnoreThreshold) {
|
||||
log("Ignored message from %s: \"%s\"", source, inbuf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Not ignored; actually execute the command, and update ignore data. */
|
||||
start = time_msec();
|
||||
call_callback_3(cb_privmsg, source, av[0], av[1]);
|
||||
stop = time_msec();
|
||||
if (stop > start && u && !is_oper(u))
|
||||
ignore_update(u, stop-start);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_quit(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from QUIT message");
|
||||
return;
|
||||
} else if (ac != 1) {
|
||||
return;
|
||||
}
|
||||
do_quit(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_server(char *source, int ac, char **av)
|
||||
{
|
||||
do_server(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_squit(char *source, int ac, char **av)
|
||||
{
|
||||
do_squit(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_stats(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from STATS message");
|
||||
return;
|
||||
} else if (ac < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (*av[0]) {
|
||||
case 'u': {
|
||||
int uptime = time(NULL) - start_time;
|
||||
Module *module_operserv;
|
||||
typeof(get_operserv_data) *p_get_operserv_data;
|
||||
int32 maxusercnt;
|
||||
|
||||
send_cmd(NULL, "242 %s :Services up %d day%s, %02d:%02d:%02d",
|
||||
source, uptime/86400, (uptime/86400 == 1) ? "" : "s",
|
||||
(uptime/3600) % 24, (uptime/60) % 60, uptime % 60);
|
||||
if ((module_operserv = find_module("operserv/main")) != NULL
|
||||
&& (p_get_operserv_data =
|
||||
get_module_symbol(module_operserv, "get_operserv_data"))
|
||||
&& p_get_operserv_data(OSDATA_MAXUSERCNT, &maxusercnt)
|
||||
) {
|
||||
send_cmd(NULL, "250 %s :Current users: %d (%d ops); maximum %d",
|
||||
source, usercnt, opcnt, maxusercnt);
|
||||
} else {
|
||||
send_cmd(NULL, "250 %s :Current users: %d (%d ops)",
|
||||
source, usercnt, opcnt);
|
||||
}
|
||||
send_cmd(NULL, "219 %s u :End of /STATS report.", source);
|
||||
break;
|
||||
} /* case 'u' */
|
||||
|
||||
case 'l': {
|
||||
uint64 read, written;
|
||||
sock_rwstat(servsock, &read, &written);
|
||||
send_cmd(NULL, "211 %s Server SendBuf SentBytes SentMsgs RecvBuf "
|
||||
"RecvBytes RecvMsgs ConnTime", source);
|
||||
#if SIZEOF_LONG >= 8
|
||||
send_cmd(NULL, "211 %s %s %u %lu %d %u %lu %d %ld",
|
||||
source, RemoteServer,
|
||||
read_buffer_len(servsock), (unsigned long)read, -1,
|
||||
write_buffer_len(servsock), (unsigned long)written, -1,
|
||||
(long)start_time);
|
||||
#else // assume long long is available
|
||||
send_cmd(NULL, "211 %s %s %u %llu %d %u %llu %d %ld",
|
||||
source, RemoteServer,
|
||||
read_buffer_len(servsock), (unsigned long long)read, -1,
|
||||
write_buffer_len(servsock), (unsigned long long)written, -1,
|
||||
(long)start_time);
|
||||
#endif
|
||||
send_cmd(NULL, "219 %s l :End of /STATS report.", source);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'c':
|
||||
case 'h':
|
||||
case 'i':
|
||||
case 'k':
|
||||
case 'm':
|
||||
case 'o':
|
||||
case 'y':
|
||||
send_cmd(NULL, "219 %s %c :/STATS %c not applicable or not supported.",
|
||||
source, *av[0], *av[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_time(char *source, int ac, char **av)
|
||||
{
|
||||
time_t t;
|
||||
struct tm *tm;
|
||||
char buf[64];
|
||||
|
||||
if (!*source) {
|
||||
log("Source missing from TIME message");
|
||||
return;
|
||||
}
|
||||
|
||||
time(&t);
|
||||
tm = localtime(&t);
|
||||
strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y %Z", tm);
|
||||
send_cmd(NULL, "391 %s %s :%s", source, ServerName, buf);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_topic(char *source, int ac, char **av)
|
||||
{
|
||||
if (ac != 4)
|
||||
return;
|
||||
do_topic(source, ac, av);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_version(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from VERSION message");
|
||||
return;
|
||||
}
|
||||
send_cmd(ServerName, "351 %s %s-%s %s :%s", source,
|
||||
program_name, version_number, ServerName, version_build);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void m_whois(char *source, int ac, char **av)
|
||||
{
|
||||
if (!*source) {
|
||||
log("Source missing from WHOIS message");
|
||||
return;
|
||||
} else if (ac < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (call_callback_3(cb_whois, source, av[0],
|
||||
ac>1 ? av[1] : NULL) <= 0
|
||||
) {
|
||||
send_cmd(ServerName, "401 %s %s :No such service.", source, av[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Basic messages (defined above). Note that NICK and USER are left to the
|
||||
* protocol modules, since their usage varies widely between protocols. */
|
||||
|
||||
static Message base_messages[] = {
|
||||
|
||||
{ "401", NULL },
|
||||
{ "436", m_nickcoll },
|
||||
{ "AWAY", NULL },
|
||||
{ "INFO", m_info },
|
||||
{ "JOIN", m_join },
|
||||
{ "KICK", m_kick },
|
||||
{ "KILL", m_kill },
|
||||
{ "MODE", m_mode },
|
||||
{ "MOTD", m_motd },
|
||||
{ "NOTICE", NULL },
|
||||
{ "PART", m_part },
|
||||
{ "PASS", NULL },
|
||||
{ "PING", m_ping },
|
||||
{ "PONG", NULL },
|
||||
{ "PRIVMSG", m_privmsg },
|
||||
{ "QUIT", m_quit },
|
||||
{ "SERVER", m_server },
|
||||
{ "SQUIT", m_squit },
|
||||
{ "STATS", m_stats },
|
||||
{ "TIME", m_time },
|
||||
{ "TOPIC", m_topic },
|
||||
{ "VERSION", m_version },
|
||||
{ "WALLOPS", NULL },
|
||||
{ "WHOIS", m_whois },
|
||||
|
||||
{ NULL }
|
||||
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/******************** Message registration and lookup ********************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Structure to link tables together */
|
||||
typedef struct messagetable_ MessageTable;
|
||||
struct messagetable_ {
|
||||
MessageTable *next, *prev;
|
||||
Message *table;
|
||||
};
|
||||
static MessageTable *msgtable = NULL;
|
||||
|
||||
/* List of known messages (for speed-lookup list) */
|
||||
typedef struct messagenode_ MessageNode;
|
||||
struct messagenode_ {
|
||||
MessageNode *next, *prev;
|
||||
Message *msg;
|
||||
};
|
||||
static MessageNode *msglist = NULL;
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* (Re)generate the speed-lookup list. This list is used to reduce the
|
||||
* time spent searching for messages; every time a message is seen, its
|
||||
* entry in this list is moved one place closer to the head of the list,
|
||||
* allowing frequently-seen messages to "percolate" to the top of the list
|
||||
* so that they will be found more quickly by searches.
|
||||
*/
|
||||
|
||||
static void init_message_list(void)
|
||||
{
|
||||
MessageNode *mn, *mn2;
|
||||
MessageTable *mt;
|
||||
Message *m;
|
||||
|
||||
LIST_FOREACH_SAFE(mn, msglist, mn2)
|
||||
free(mn);
|
||||
msglist = NULL;
|
||||
|
||||
LIST_FOREACH (mt, msgtable) {
|
||||
for (m = mt->table; m->name; m++) {
|
||||
LIST_SEARCH(msglist, msg->name, m->name, stricmp, mn);
|
||||
if (!mn) {
|
||||
mn = smalloc(sizeof(*mn));
|
||||
mn->msg = m;
|
||||
LIST_INSERT(mn, msglist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Register the given table of messages. Returns 1 on success, 0 on
|
||||
* failure (`table' == NULL, `table' already registered, or out of memory).
|
||||
*/
|
||||
|
||||
int register_messages(Message *table)
|
||||
{
|
||||
MessageTable *mt;
|
||||
|
||||
if (!table)
|
||||
return 0;
|
||||
LIST_SEARCH_SCALAR(msgtable, table, table, mt);
|
||||
if (mt) /* if it's already on the list, abort */
|
||||
return 0;
|
||||
mt = malloc(sizeof(*mt));
|
||||
if (!mt) /* out of memory */
|
||||
return 0;
|
||||
mt->table = table;
|
||||
LIST_INSERT(mt, msgtable);
|
||||
init_message_list();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Unregister the given table of messages. Returns 1 on success, 0 on
|
||||
* failure (`table' not registered).
|
||||
*/
|
||||
|
||||
int unregister_messages(Message *table)
|
||||
{
|
||||
MessageTable *mt;
|
||||
|
||||
LIST_SEARCH_SCALAR(msgtable, table, table, mt);
|
||||
if (!mt)
|
||||
return 0;
|
||||
LIST_REMOVE(mt, msgtable);
|
||||
free(mt);
|
||||
init_message_list();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the Message structure for the given message name, or NULL if none
|
||||
* exists. If there are multiple tables with entries for the message,
|
||||
* returns the entry in the most recently registered table.
|
||||
*/
|
||||
|
||||
Message *find_message(const char *name)
|
||||
{
|
||||
MessageNode *mn;
|
||||
|
||||
LIST_SEARCH(msglist, msg->name, name, stricmp, mn);
|
||||
if (mn) {
|
||||
MessageNode *prev = mn->prev;
|
||||
if (prev) {
|
||||
MessageNode *pprev = prev->prev;
|
||||
MessageNode *next = mn->next;
|
||||
/* Current order: pprev -> prev -> mn -> next */
|
||||
/* New order: pprev -> mn -> prev -> next */
|
||||
if (pprev)
|
||||
pprev->next = mn;
|
||||
else
|
||||
msglist = mn;
|
||||
mn->prev = pprev;
|
||||
mn->next = prev;
|
||||
prev->prev = mn;
|
||||
prev->next = next;
|
||||
if (next)
|
||||
next->prev = prev;
|
||||
}
|
||||
return mn->msg;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/************************ Initialization/cleanup *************************/
|
||||
/*************************************************************************/
|
||||
|
||||
int messages_init(int ac, char **av)
|
||||
{
|
||||
if (!register_messages(base_messages)) {
|
||||
log("messages_init: Unable to register base messages\n");
|
||||
return 0;
|
||||
}
|
||||
cb_privmsg = register_callback("m_privmsg");
|
||||
cb_whois = register_callback("m_whois");
|
||||
if (cb_privmsg < 0 || cb_whois < 0) {
|
||||
log("messages_init: register_callback() failed\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void messages_cleanup(void)
|
||||
{
|
||||
unregister_callback(cb_whois);
|
||||
unregister_callback(cb_privmsg);
|
||||
unregister_messages(base_messages);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
41
messages.h
Normal file
41
messages.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* Declarations of IRC message structures, variables, and functions.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef MESSAGES_H
|
||||
#define MESSAGES_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Type of a message function: */
|
||||
typedef void (*MessageFunc)(char *source, int ac, char **av);
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
MessageFunc func;
|
||||
} Message;
|
||||
|
||||
extern int register_messages(Message *table);
|
||||
extern int unregister_messages(Message *table);
|
||||
extern Message *find_message(const char *name);
|
||||
extern int messages_init(int ac, char **av);
|
||||
extern void messages_cleanup(void);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* MESSAGES_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
280
modes.c
Normal file
280
modes.c
Normal file
@ -0,0 +1,280 @@
|
||||
/* Routines for handling mode flags and strings.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* List of user/channel modes and associated mode letters. */
|
||||
|
||||
ModeData usermodes[] = {
|
||||
['o'] = {0x00000001},
|
||||
['i'] = {0x00000002},
|
||||
['w'] = {0x00000004},
|
||||
};
|
||||
|
||||
ModeData chanmodes[] = {
|
||||
['i'] = {0x00000001, 0, 0},
|
||||
['m'] = {0x00000002, 0, 0},
|
||||
['n'] = {0x00000004, 0, 0},
|
||||
['p'] = {0x00000008, 0, 0},
|
||||
['s'] = {0x00000010, 0, 0},
|
||||
['t'] = {0x00000020, 0, 0},
|
||||
['k'] = {0x00000040, 1, 1},
|
||||
['l'] = {0x00000080, 1, 0},
|
||||
['b'] = {0x80000000, 1, 1, 0, MI_MULTIPLE},
|
||||
};
|
||||
|
||||
ModeData chanusermodes[] = {
|
||||
['o'] = {0x00000001, 1, 1, '@'},
|
||||
['v'] = {0x00000002, 1, 1, '+'},
|
||||
};
|
||||
|
||||
/* The following are initialized by mode_setup(): */
|
||||
int32 usermode_reg; /* Usermodes applied to registered nicks */
|
||||
int32 chanmode_reg; /* Chanmodes applied to registered chans */
|
||||
int32 chanmode_regonly; /* Chanmodes indicating regnick-only channels*/
|
||||
int32 chanmode_opersonly; /* Chanmodes indicating oper-only channels */
|
||||
char chanmode_multiple[257]; /* Chanmodes that can be set multiple times */
|
||||
|
||||
/* Flag tables, used internally to speed up flag lookups. 0 indicates a
|
||||
* flag with no associated mode. */
|
||||
static char userflags[31], chanflags[31], chanuserflags[31];
|
||||
|
||||
/* Table of channel user mode prefixes, used like flag tables above. */
|
||||
static int32 prefixtable[256];
|
||||
|
||||
/* Tables used for fast lookups. */
|
||||
static ModeData *modetable[] = {usermodes, chanmodes, chanusermodes};
|
||||
static char *flagtable[] = {userflags, chanflags, chanuserflags};
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialize flag tables and flag sets from mode tables. Must be called
|
||||
* before any other mode_* function, and should be called again if the mode
|
||||
* tables are modified.
|
||||
*/
|
||||
|
||||
void mode_setup(void)
|
||||
{
|
||||
int i;
|
||||
ModeData *modelist;
|
||||
char *flaglist;
|
||||
int multi_index = 0; /* index into chanmode_multiple[] */
|
||||
|
||||
modelist = usermodes;
|
||||
flaglist = userflags;
|
||||
for (i = 0; i < 256; i++) {
|
||||
if (modelist[i].flag) {
|
||||
int n = 0;
|
||||
uint32 tmp = (uint32) modelist[i].flag;
|
||||
if (modelist[i].info & MI_REGISTERED)
|
||||
usermode_reg |= tmp;
|
||||
while (tmp >>= 1)
|
||||
n++;
|
||||
if (n < 31)
|
||||
flaglist[n] = (char)i;
|
||||
}
|
||||
}
|
||||
|
||||
modelist = chanmodes;
|
||||
flaglist = chanflags;
|
||||
for (i = 0; i < 256; i++) {
|
||||
if (modelist[i].flag) {
|
||||
int n = 0;
|
||||
uint32 tmp = (uint32) modelist[i].flag;
|
||||
if (modelist[i].info & MI_REGISTERED)
|
||||
chanmode_reg |= tmp;
|
||||
if (modelist[i].info & MI_REGNICKS_ONLY)
|
||||
chanmode_regonly |= tmp;
|
||||
if (modelist[i].info & MI_OPERS_ONLY)
|
||||
chanmode_opersonly |= tmp;
|
||||
if (modelist[i].info & MI_MULTIPLE)
|
||||
chanmode_multiple[multi_index++] = i;
|
||||
while (tmp >>= 1)
|
||||
n++;
|
||||
if (n < 31)
|
||||
flaglist[n] = (char)i;
|
||||
}
|
||||
}
|
||||
chanmode_multiple[multi_index] = 0;
|
||||
|
||||
modelist = chanusermodes;
|
||||
flaglist = chanuserflags;
|
||||
for (i = 0; i < 256; i++) {
|
||||
if (modelist[i].flag) {
|
||||
int n = 0;
|
||||
uint32 tmp = (uint32) modelist[i].flag;
|
||||
prefixtable[ (uint8)modelist[i].prefix ] = tmp;
|
||||
while (tmp >>= 1)
|
||||
n++;
|
||||
if (n < 31)
|
||||
flaglist[n] = (char)i;
|
||||
if (modelist[i].plus_params!=1 || modelist[i].minus_params!=1) {
|
||||
log("modes: Warning: channel user mode `%c' takes %d/%d"
|
||||
" parameters (should be 1/1)",
|
||||
i, modelist[i].plus_params, modelist[i].minus_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the flag corresponding to the given mode character, or 0 if no
|
||||
* such mode exists. Return MODE_INVALID if the mode exists but has no
|
||||
* assigned flag. `which' indicates the mode set to be used: MODE_USER,
|
||||
* MODE_CHANNEL, or MODE_CHANUSER.
|
||||
*/
|
||||
|
||||
int32 mode_char_to_flag(char c, int which)
|
||||
{
|
||||
if (which != MODE_USER && which != MODE_CHANNEL && which != MODE_CHANUSER){
|
||||
log("mode_char_to_flag(): bad `which' value %d", which);
|
||||
return MODE_INVALID;
|
||||
}
|
||||
return modetable[which][(uint8)c].flag;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the number of parameters the given mode takes, as
|
||||
* (plus_params<<8) | (minus_params). Return -1 if there is no such mode.
|
||||
*/
|
||||
|
||||
int mode_char_to_params(char c, int which)
|
||||
{
|
||||
ModeData *ptr;
|
||||
|
||||
if (which != MODE_USER && which != MODE_CHANNEL && which != MODE_CHANUSER){
|
||||
log("mode_char_to_params(): bad `which' value %d", which);
|
||||
return -1;
|
||||
}
|
||||
ptr = &modetable[which][(uint8)c];
|
||||
return ptr->plus_params<<8 | ptr->minus_params;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the mode character corresponding to the given flag, or 0 if no
|
||||
* such mode exists. If more than one bit is set in `f', then the mode
|
||||
* character corresponding to the highest bit is used.
|
||||
*/
|
||||
|
||||
char mode_flag_to_char(int32 f, int which)
|
||||
{
|
||||
char *flaglist;
|
||||
int n = 0, tmp = f;
|
||||
|
||||
if (which != MODE_USER && which != MODE_CHANNEL && which != MODE_CHANUSER){
|
||||
log("mode_flag_to_char(): bad `which' value %d", which);
|
||||
return 0;
|
||||
}
|
||||
flaglist = flagtable[which];
|
||||
|
||||
while (tmp >>= 1)
|
||||
n++;
|
||||
if (n >= 31)
|
||||
return 0;
|
||||
return flaglist[n];
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the flag set corresponding to the given string of mode characters
|
||||
* in the given mode set. If MODE_NOERROR is set in `which', invalid mode
|
||||
* characters are ignored; if not set, (CMODE_INVALID | modechar) is
|
||||
* returned for the first invalid mode character found.
|
||||
*/
|
||||
|
||||
int32 mode_string_to_flags(const char *s, int which)
|
||||
{
|
||||
int noerror = (which & MODE_NOERROR);
|
||||
int32 flags = 0;
|
||||
const ModeData *modelist;
|
||||
|
||||
which &= ~MODE_NOERROR;
|
||||
if (which != MODE_USER && which != MODE_CHANNEL && which != MODE_CHANUSER){
|
||||
log("mode_string_to_flags(): bad `which' value %d", which|noerror);
|
||||
return 0;
|
||||
}
|
||||
modelist = modetable[which];
|
||||
|
||||
while (*s) {
|
||||
int f = modelist[(uint8)*s].flag;
|
||||
if (!f) {
|
||||
if (noerror) {
|
||||
s++;
|
||||
continue;
|
||||
}
|
||||
return MODE_INVALID | *s;
|
||||
}
|
||||
if (f != MODE_INVALID)
|
||||
flags |= f;
|
||||
s++;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the string of mode characters corresponding to the given flag
|
||||
* set. If the flag set has invalid flags in it, they are ignored.
|
||||
* The returned string is stored in a static buffer which will be
|
||||
* overwritten on the next call.
|
||||
*/
|
||||
|
||||
char *mode_flags_to_string(int32 flags, int which)
|
||||
{
|
||||
static char buf[32];
|
||||
char *s = buf;
|
||||
int n = 0;
|
||||
const char *flaglist;
|
||||
|
||||
if (which != MODE_USER && which != MODE_CHANNEL && which != MODE_CHANUSER){
|
||||
log("mode_flags_to_string(): bad `which' value %d", which);
|
||||
*buf = 0;
|
||||
return buf;
|
||||
}
|
||||
flaglist = flagtable[which];
|
||||
|
||||
flags &= ~MODE_INVALID;
|
||||
while (flags) {
|
||||
if ((flags & 1) && flaglist[n])
|
||||
*s++ = flaglist[n];
|
||||
n++;
|
||||
flags >>= 1;
|
||||
}
|
||||
*s = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return the flag corresponding to the given channel user mode prefix, or
|
||||
* 0 if no such mode exists.
|
||||
*/
|
||||
|
||||
int32 cumode_prefix_to_flag(char c)
|
||||
{
|
||||
return prefixtable[(uint8)c];
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
143
modes.h
Normal file
143
modes.h
Normal file
@ -0,0 +1,143 @@
|
||||
/* Mode flag definitions.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef MODES_H
|
||||
#define MODES_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Special flag constants. */
|
||||
|
||||
#define MODE_INVALID 0x80000000 /* Used as error flag, or as "this
|
||||
* isn't an on/off mode" flag */
|
||||
#define MODE_ALL (~MODE_INVALID) /* All possible modes */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Data for mode characters. If a mode character is given the MODE_INVALID
|
||||
* flag, it indicates that that mode is a valid mode but is assigned no
|
||||
* flag. (For example, channel mode +b uses this.) */
|
||||
|
||||
typedef struct {
|
||||
int32 flag;
|
||||
uint8 plus_params; /* Number of parameters when adding */
|
||||
uint8 minus_params; /* Number of characters when deleting */
|
||||
char prefix; /* Prefix for channel user mode (e.g. +o -> '@') */
|
||||
uint32 info; /* What kind of mode this is (MI_* below) */
|
||||
} ModeData;
|
||||
|
||||
#define MI_MULTIPLE 0x01 /* Can be set multiple times (+b etc) */
|
||||
#define MI_REGISTERED 0x02 /* [UC] Applied to all registered nicks/chans */
|
||||
#define MI_OPERS_ONLY 0x04 /* [ C] Only opers may join */
|
||||
#define MI_REGNICKS_ONLY 0x08 /* [ C] Only registered/ID'd nicks may join */
|
||||
|
||||
/* These bits are available for private use by protocol modules: (see
|
||||
* Unreal module for an example of usage) */
|
||||
#define MI_LOCAL_MASK 0xFF000000
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Arrays of mode characters--one entry for each possible character. These
|
||||
* are declared extern to allow modules to add entries; read access should
|
||||
* be done through the functions below.
|
||||
*
|
||||
* The following modes are predefined:
|
||||
* User: o, i, w
|
||||
* Channel: i, k, l, m, n, p, s, t, b
|
||||
* Channel user (modes applied to individual users on a channel): o, v
|
||||
*/
|
||||
extern ModeData usermodes[256], chanmodes[256], chanusermodes[256];
|
||||
|
||||
/* The following are initialized by mode_setup(): */
|
||||
extern int32 usermode_reg; /* Usermodes applied to registered nicks */
|
||||
extern int32 chanmode_reg; /* Chanmodes applied to registered chans */
|
||||
extern int32 chanmode_regonly; /* Chanmodes indicating regnick-only channels*/
|
||||
extern int32 chanmode_opersonly;/* Chanmodes indicating oper-only channels */
|
||||
extern char chanmode_multiple[];/* Chanmodes that can be set multiple times */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialize flag tables and flag sets from mode tables. Must be called
|
||||
* before any other mode_* function. */
|
||||
extern void mode_setup(void);
|
||||
|
||||
/* Return the flag corresponding to the given mode character, or 0 if no
|
||||
* such mode exists. Return MODE_INVALID if the mode exists but has no
|
||||
* assigned flag. See below for the meaning of "which". */
|
||||
extern int32 mode_char_to_flag(char c, int which);
|
||||
|
||||
/* Return the number of parameters the given mode takes, as
|
||||
* (plus_params<<8) | (minus_params). Return -1 if there is no such mode. */
|
||||
extern int mode_char_to_params(char c, int which);
|
||||
|
||||
/* Return the mode character corresponding to the given flag, or 0 if no
|
||||
* such mode exists. */
|
||||
extern char mode_flag_to_char(int32 f, int which);
|
||||
|
||||
/* Return the flag set corresponding to the given string of mode
|
||||
* characters, or (CMODE_INVALID | modechar) if an invalid mode character
|
||||
* is found. */
|
||||
extern int32 mode_string_to_flags(const char *s, int which);
|
||||
|
||||
/* Return the string of mode characters corresponding to the given flag
|
||||
* set. If the flag set has invalid flags in it, they are ignored.
|
||||
* The returned string is stored in a static buffer which will be
|
||||
* overwritten on the next call. */
|
||||
extern char *mode_flags_to_string(int32 flags, int which);
|
||||
|
||||
/* Return the flag corresponding to the given channel user mode prefix, or
|
||||
* 0 if no such mode exists. */
|
||||
extern int32 cumode_prefix_to_flag(char c);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Values for "which" parameter to mode_* functions: */
|
||||
|
||||
#define MODE_USER 0 /* UMODE_* (user modes) */
|
||||
#define MODE_CHANNEL 1 /* CMODE_* (binary channel modes) */
|
||||
#define MODE_CHANUSER 2 /* CUMODE_* (channel modes for users) */
|
||||
|
||||
#define MODE_NOERROR 0x8000 /* Ignore bad chars in string_to_flags() */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* User modes: */
|
||||
#define UMODE_o 0x00000001
|
||||
#define UMODE_i 0x00000002
|
||||
#define UMODE_w 0x00000004
|
||||
|
||||
/* Channel modes: */
|
||||
#define CMODE_i 0x00000001
|
||||
#define CMODE_m 0x00000002
|
||||
#define CMODE_n 0x00000004
|
||||
#define CMODE_p 0x00000008
|
||||
#define CMODE_s 0x00000010
|
||||
#define CMODE_t 0x00000020
|
||||
#define CMODE_k 0x00000040
|
||||
#define CMODE_l 0x00000080
|
||||
|
||||
/* Modes for users on channels: */
|
||||
#define CUMODE_o 0x00000001
|
||||
#define CUMODE_v 0x00000002
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* MODES_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
276
modules.h
Normal file
276
modules.h
Normal file
@ -0,0 +1,276 @@
|
||||
/* Module support include file (interface definition).
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_H
|
||||
#define MODULES_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Version code macro. Parameters are:
|
||||
* major: Major version number (the "5" in 5.1a0).
|
||||
* minor: Minor version number (the "1" in 5.1a0).
|
||||
* status: Release status (alpha, prerelease, or final). Use one of
|
||||
* the MODULE_VERSION_{ALPHA,PRE,FINAL} macros:
|
||||
* 5.1a0 -> MODULE_VERSION_ALPHA
|
||||
* 5.1pre0 -> MODULE_VERSION_PRE
|
||||
* 5.1.0 -> MODULE_VERSION_FINAL
|
||||
* release: Release number (the "0" in 5.1a0).
|
||||
*/
|
||||
#define MODULE_VERSION(major,minor,status,release) ( \
|
||||
((major) > 5 || ((major) == 5 && (minor) >= 1)) \
|
||||
? ((major)<<24 | (minor)<<16 | (status)<<12 | (release))\
|
||||
: (0x050000 | (release)) \
|
||||
)
|
||||
#define MODULE_VERSION_ALPHA 0xA
|
||||
#define MODULE_VERSION_PRE 0xB
|
||||
#define MODULE_VERSION_FINAL 0xF
|
||||
|
||||
/* Version code for modules. This will be updated whenever a change to the
|
||||
* program (structures, callbacks, etc.) makes existing binary modules
|
||||
* incompatible. This is stored in the compiled modules and used to let
|
||||
* Services determine whether a given module is compatible with the current
|
||||
* binary; it can also be used with #if to make source code compatible with
|
||||
* multiple versions of Services.
|
||||
*/
|
||||
#define MODULE_VERSION_CODE MODULE_VERSION(5,1,MODULE_VERSION_FINAL,0)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Module information. The actual structure is defined in module.c, and is
|
||||
* opaque to the caller. */
|
||||
struct Module_;
|
||||
typedef struct Module_ Module;
|
||||
|
||||
/* Callback function prototype. */
|
||||
typedef int (*callback_t)();
|
||||
|
||||
/* Callback priority limits. */
|
||||
#define CBPRI_MIN -10000
|
||||
#define CBPRI_MAX 10000
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Macro to rename a symbol based on the module's ID (MODULE_ID) in order
|
||||
* to avoid symbol clashes. This roundabout construction is necessary to
|
||||
* force the preprocessor to substitute the value of MODULE_ID instead of
|
||||
* appending "MODULE_ID" literally. */
|
||||
|
||||
#define RENAME_SYMBOL(symbol) RENAME_SYMBOL_2(symbol,MODULE_ID)
|
||||
#define RENAME_SYMBOL_2(symbol,id) RENAME_SYMBOL_3(symbol,id)
|
||||
#define RENAME_SYMBOL_3(symbol,id) symbol##_##id
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Macros to retrieve a pointer to the current module or its name. */
|
||||
|
||||
#ifdef MODULE
|
||||
# define THIS_MODULE RENAME_SYMBOL(_this_module)
|
||||
#else
|
||||
# define THIS_MODULE NULL
|
||||
#endif
|
||||
|
||||
#define MODULE_NAME (get_module_name(THIS_MODULE))
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* External variable/function declarations. Note that many of the
|
||||
* functions below are macroized to automatically pass a "current-module"
|
||||
* parameter; functions whose names begin with "_" should not be called
|
||||
* directly. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialization and cleanup: */
|
||||
|
||||
extern int modules_init(int ac, char **av);
|
||||
extern void modules_cleanup(void);
|
||||
extern void unload_all_modules(void);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Module-level functions: */
|
||||
|
||||
/* Load a new module and return the Module pointer, or NULL on error. */
|
||||
extern Module *load_module(const char *modulename);
|
||||
|
||||
/* Remove a module from memory. Return nonzero on success, zero on
|
||||
* failure. */
|
||||
extern int unload_module(Module *module);
|
||||
|
||||
/* Return the Module pointer for the named module, or NULL if no such
|
||||
* module exists. */
|
||||
extern Module *find_module(const char *modulename);
|
||||
|
||||
/* Increment or decrement the use count for the given module. A module
|
||||
* cannot be unloaded while its use count is nonzero. */
|
||||
#define use_module(mod) _use_module(mod,THIS_MODULE)
|
||||
#define unuse_module(mod) _unuse_module(mod,THIS_MODULE)
|
||||
extern void _use_module(Module *module, const Module *caller);
|
||||
extern void _unuse_module(Module *module, const Module *caller);
|
||||
|
||||
/* Re-read configuration files for all modules. Return nonzero on success,
|
||||
* zero on failure. */
|
||||
int reconfigure_modules(void);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Module symbol/information retrieval: */
|
||||
|
||||
/* Retrieve the value of the named symbol in the given module. Return NULL
|
||||
* if no such symbol exists. Note that this function should not be used
|
||||
* for symbols whose value might be NULL, because there is no way to
|
||||
* distinguish a symbol value of NULL from an error return. For such
|
||||
* symbols, or for cases where a symbol might legitimately not exist and
|
||||
* no error should be printed for nonexistence, use check_module_symbol(). */
|
||||
#define get_module_symbol(mod,symname) \
|
||||
_get_module_symbol(mod,symname,THIS_MODULE)
|
||||
extern void *_get_module_symbol(Module *module, const char *symname,
|
||||
const Module *caller);
|
||||
|
||||
/* Check whether the given symbol exists in the given module; return 1 if
|
||||
* so, 0 otherwise. If `resultptr' is non-NULL and the symbol exists, the
|
||||
* value is stored in the variable it points to. If `errorptr' is non-NULL
|
||||
* and the symbol does not exist, a human-readable error message is stored
|
||||
* in the variable it points to. */
|
||||
extern int check_module_symbol(Module *module, const char *symname,
|
||||
void **resultptr, const char **errorptr);
|
||||
|
||||
/* Retrieve the name of the given module. */
|
||||
extern const char *get_module_name(const Module *module);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Callback-related functions: (all functions except register_callback()
|
||||
* and call_callback() return nonzero on success and zero on error)
|
||||
*/
|
||||
|
||||
/* Register a new callback list. */
|
||||
#define register_callback(name) _register_callback(THIS_MODULE,name)
|
||||
extern int _register_callback(Module *module, const char *name);
|
||||
|
||||
/* Call all functions in a callback list. Return 1 if a callback returned
|
||||
* nonzero, 0 if all callbacks returned zero, or -1 on error. The _N
|
||||
* formats allow passing parameters. */
|
||||
#define call_callback(id) \
|
||||
call_callback_1(id, NULL)
|
||||
#define call_callback_1(id,arg1) \
|
||||
call_callback_2(id, arg1, NULL)
|
||||
#define call_callback_2(id,arg1,arg2) \
|
||||
call_callback_3(id, arg1, arg2, NULL)
|
||||
#define call_callback_3(id,arg1,arg2,arg3) \
|
||||
call_callback_4(id, arg1, arg2, arg3, NULL)
|
||||
#define call_callback_4(id,arg1,arg2,arg3,arg4) \
|
||||
call_callback_5(id, arg1, arg2, arg3, arg4, NULL)
|
||||
#define call_callback_5(id,arg1,arg2,arg3,arg4,arg5) \
|
||||
_call_callback_5(THIS_MODULE, id, (void *)(long)(arg1), \
|
||||
(void *)(long)(arg2), (void *)(long)(arg3), \
|
||||
(void *)(long)(arg4), (void *)(long)(arg5))
|
||||
extern int _call_callback_5(Module *module, int id, void *arg1, void *arg2,
|
||||
void *arg3, void *arg4, void *arg5);
|
||||
|
||||
/* Delete a callback list. */
|
||||
#define unregister_callback(name) _unregister_callback(THIS_MODULE,name)
|
||||
extern int _unregister_callback(Module *module, int id);
|
||||
|
||||
/* Add a function to a callback list with the given priority (higher
|
||||
* priority value = called sooner). Callbacks with the same priority are
|
||||
* called in the order they were added. */
|
||||
#define add_callback_pri(module,name,callback,priority) \
|
||||
_add_callback_pri(module,name,callback,priority,THIS_MODULE)
|
||||
int _add_callback_pri(Module *module, const char *name, callback_t callback,
|
||||
int priority, const Module *caller);
|
||||
|
||||
/* Add a function to a callback list with priority 0. */
|
||||
#define add_callback(module,name,callback) \
|
||||
add_callback_pri(module,name,callback,0)
|
||||
|
||||
/* Remove a function from a callback list. */
|
||||
#define remove_callback(module,name,callback) \
|
||||
_remove_callback(module,name,callback,THIS_MODULE)
|
||||
extern int _remove_callback(Module *module, const char *name,
|
||||
callback_t callback, const Module *caller);
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Module functions: */
|
||||
|
||||
int init_module(void);
|
||||
int exit_module(int shutdown);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Macros to declare a symbol to be exported. Only one may be used per
|
||||
* line, and it must be placed at the beginning of the line and be the only
|
||||
* thing on the line (no semicolon at the end). This does not have any
|
||||
* actual effect on compilation, but such lines are extracted to create
|
||||
* module symbol lists. Note that it is not necessary to explicitly list
|
||||
* the init_module, exit_module, and module_config symbols (and in fact,
|
||||
* doing so will cause an error when linking the final executable).
|
||||
*
|
||||
* Also note that typedefs cannot be used here; use struct tags instead.
|
||||
*
|
||||
* Examples:
|
||||
* EXPORT_VAR(const char *,s_NickServ)
|
||||
* EXPORT_ARRAY(some_array)
|
||||
* EXPORT_FUNC(create_akill)
|
||||
*/
|
||||
|
||||
#define EXPORT_VAR(type,symbol)
|
||||
#define EXPORT_ARRAY(symbol)
|
||||
#define EXPORT_FUNC(symbol)
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Internal-use stuff. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Pointer to the current module. This is defined in each module's main
|
||||
* source file, and automatically initialized by load_module(). Modules
|
||||
* should use the THIS_MODULE macro to retrieve their own module pointer
|
||||
* rather than accessing this variable directly.
|
||||
*/
|
||||
#ifdef MODULE
|
||||
# ifndef MODULE_MAIN_FILE
|
||||
extern
|
||||
# endif
|
||||
Module *RENAME_SYMBOL(_this_module);
|
||||
# ifdef MODULE_MAIN_FILE
|
||||
Module **_this_module_ptr = &RENAME_SYMBOL(_this_module); /* used by loader */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* If the preprocessor symbol MODULE_MAIN_FILE is defined, the following
|
||||
* required variable is automatically defined. This symbol should be
|
||||
* defined for one (and only one) file per module. (Normally, this symbol
|
||||
* is defined automatically by modules/Makerules for the main source file
|
||||
* for each module, and does not need to be defined manually.)
|
||||
*/
|
||||
#if defined(MODULE_MAIN_FILE)
|
||||
const int32 module_version = MODULE_VERSION_CODE;
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* MODULES_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
159
process.c
Normal file
159
process.c
Normal file
@ -0,0 +1,159 @@
|
||||
/* Main processing code for Services.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "modules.h"
|
||||
#include "messages.h"
|
||||
|
||||
static int cb_recvmsg = -1;
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* split_buf: Split a buffer into arguments and store a pointer to the
|
||||
* argument vector in argv_ptr; return the argument count.
|
||||
* The argument vector will point to a static buffer;
|
||||
* subsequent calls will overwrite this buffer.
|
||||
* If colon_special is non-zero, then treat a parameter with a
|
||||
* leading ':' as the last parameter of the line, per the IRC
|
||||
* RFC. Destroys the buffer by side effect.
|
||||
*/
|
||||
|
||||
static char **sbargv = NULL; /* File scope so process_cleanup() can free it */
|
||||
|
||||
int split_buf(char *buf, char ***argv_ptr, int colon_special)
|
||||
{
|
||||
static int argvsize = 8;
|
||||
int argc;
|
||||
char *s;
|
||||
|
||||
if (!sbargv)
|
||||
sbargv = smalloc(sizeof(char *) * argvsize);
|
||||
argc = 0;
|
||||
while (*buf) {
|
||||
if (argc == argvsize) {
|
||||
argvsize += 8;
|
||||
sbargv = srealloc(sbargv, sizeof(char *) * argvsize);
|
||||
}
|
||||
if (*buf == ':' && colon_special) {
|
||||
sbargv[argc++] = buf+1;
|
||||
*buf = 0;
|
||||
} else {
|
||||
s = strpbrk(buf, " ");
|
||||
if (s) {
|
||||
*s++ = 0;
|
||||
while (*s == ' ')
|
||||
s++;
|
||||
} else {
|
||||
s = buf + strlen(buf);
|
||||
}
|
||||
sbargv[argc++] = buf;
|
||||
buf = s;
|
||||
}
|
||||
}
|
||||
*argv_ptr = sbargv;
|
||||
return argc;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
int process_init(int ac, char **av)
|
||||
{
|
||||
cb_recvmsg = register_callback("receive message");
|
||||
if (cb_recvmsg < 0) {
|
||||
log("process_init: register_callback() failed\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void process_cleanup(void)
|
||||
{
|
||||
unregister_callback(cb_recvmsg);
|
||||
free(sbargv);
|
||||
sbargv = NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* process: Main processing routine. Takes the string in inbuf (global
|
||||
* variable) and does something appropriate with it. */
|
||||
|
||||
void process(void)
|
||||
{
|
||||
char source[64];
|
||||
char cmd[64];
|
||||
char buf[512]; /* Longest legal IRC command line */
|
||||
char *s;
|
||||
int ac; /* Parameters for the command */
|
||||
char **av;
|
||||
|
||||
|
||||
/* If debugging, log the buffer. */
|
||||
log_debug(1, "Received: %s", inbuf);
|
||||
|
||||
/* First make a copy of the buffer so we have the original in case we
|
||||
* crash - in that case, we want to know what we crashed on. */
|
||||
strbcpy(buf, inbuf);
|
||||
|
||||
/* Split the buffer into pieces. */
|
||||
if (*buf == ':') {
|
||||
s = strpbrk(buf, " ");
|
||||
if (!s)
|
||||
return;
|
||||
*s = 0;
|
||||
while (isspace(*++s))
|
||||
;
|
||||
strbcpy(source, buf+1);
|
||||
strmove(buf, s);
|
||||
} else {
|
||||
*source = 0;
|
||||
}
|
||||
if (!*buf)
|
||||
return;
|
||||
s = strpbrk(buf, " ");
|
||||
if (s) {
|
||||
*s = 0;
|
||||
while (isspace(*++s))
|
||||
;
|
||||
} else
|
||||
s = buf + strlen(buf);
|
||||
strbcpy(cmd, buf);
|
||||
ac = split_buf(s, &av, 1);
|
||||
|
||||
/* Do something with the message. */
|
||||
if (call_callback_4(cb_recvmsg, source, cmd, ac, av) <= 0) {
|
||||
Message *m = find_message(cmd);
|
||||
if (m) {
|
||||
if (m->func)
|
||||
m->func(source, ac, av);
|
||||
} else {
|
||||
log("unknown message from server (%s)", inbuf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, clear the first byte of `inbuf' to signal that we're
|
||||
* finished processing. */
|
||||
*inbuf = 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
363
send.c
Normal file
363
send.c
Normal file
@ -0,0 +1,363 @@
|
||||
/* Routines for sending stuff to the network.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#define IN_SEND_C
|
||||
#include "services.h"
|
||||
#include "modules.h"
|
||||
#include "language.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
time_t last_send; /* Time last data was sent to server */
|
||||
|
||||
|
||||
/* Modes to send for Services users. */
|
||||
const char *pseudoclient_modes = "";
|
||||
const char *enforcer_modes = "";
|
||||
/* Do "oper" pseudoclients really need oper privileges? (1 or 0) */
|
||||
int pseudoclient_oper = 1;
|
||||
|
||||
|
||||
/* Default handler for module-implemented functions. */
|
||||
static void unimplemented(void);
|
||||
|
||||
/* Functions which are to be implemented by protocol modules. See
|
||||
* documentation for details. */
|
||||
FUNCPTR(void, send_nick, (const char *nick, const char *user,
|
||||
const char *host, const char *server,
|
||||
const char *name, const char *modes))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, send_nickchange, (const char *nick, const char *newnick))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, send_namechange, (const char *nick, const char *newname))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, send_server, (void))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, send_server_remote, (const char *server, const char *desc))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, wallops, (const char *source, const char *fmt, ...)
|
||||
FORMAT(printf,2,3))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, notice_all, (const char *source, const char *fmt, ...)
|
||||
FORMAT(printf,2,3))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, send_channel_cmd, (const char *source, const char *fmt, ...)
|
||||
FORMAT(printf,2,3))
|
||||
= (void *)unimplemented;
|
||||
FUNCPTR(void, send_nickchange_remote, (const char *nick, const char *newnick))
|
||||
= (void *)unimplemented;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Initialization: set up protocol_* variables, and verify on load that the
|
||||
* protocol module set everything up correctly.
|
||||
*/
|
||||
|
||||
const char *protocol_name = NULL;
|
||||
const char *protocol_version = NULL;
|
||||
uint32 protocol_features = PF_UNSET;
|
||||
int protocol_nickmax = 0;
|
||||
|
||||
#define PROTOCHK(var) \
|
||||
if (!var) \
|
||||
fatal("Variable `" #var "' not set by protocol module `%s'", name);
|
||||
#define FUNCCHK(var) \
|
||||
if ((void *)var == (void *)unimplemented) \
|
||||
fatal("Function `" #var "' not set by protocol module `%s'", name);
|
||||
|
||||
static int do_load_module(Module *mod, const char *name)
|
||||
{
|
||||
/* Assume the first module loaded is a protocol module */
|
||||
|
||||
PROTOCHK(protocol_name);
|
||||
if (protocol_features & PF_UNSET)
|
||||
fatal("Variable `protocol_features' not set by protocol module `%s'",
|
||||
name);
|
||||
PROTOCHK(protocol_nickmax);
|
||||
|
||||
FUNCCHK(send_nick);
|
||||
FUNCCHK(send_nickchange);
|
||||
FUNCCHK(send_namechange);
|
||||
FUNCCHK(send_server);
|
||||
FUNCCHK(send_server_remote);
|
||||
FUNCCHK(wallops);
|
||||
FUNCCHK(notice_all);
|
||||
FUNCCHK(send_channel_cmd);
|
||||
if (protocol_features & PF_CHANGENICK)
|
||||
FUNCCHK(send_nickchange_remote);
|
||||
|
||||
/* Make sure NICKMAX is large enough to hold the largest nickname
|
||||
* supported by the protocol plus a trailing NULL. */
|
||||
if (protocol_nickmax+1 > NICKMAX)
|
||||
fatal("NICKMAX is too small (%d)--increase to at least %d and"
|
||||
" recompile", NICKMAX, protocol_nickmax+1);
|
||||
|
||||
/* Remove the callback now that everything's checked. */
|
||||
remove_callback(NULL, "load module", do_load_module);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef FUNCCHK
|
||||
#undef PROTOCHK
|
||||
|
||||
|
||||
int send_init(int ac, char **av)
|
||||
{
|
||||
if (!add_callback(NULL, "load module", do_load_module)) {
|
||||
log("send.c: Unable to add load module callback");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Cleanup. */
|
||||
|
||||
void send_cleanup(void)
|
||||
{
|
||||
remove_callback(NULL, "load module", do_load_module);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Send a command to the server. The two forms here are like
|
||||
* printf()/vprintf() and friends. If not connected to a remote server,
|
||||
* these functions do nothing.
|
||||
*/
|
||||
|
||||
void send_cmd(const char *source, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vsend_cmd(source, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void vsend_cmd(const char *source, const char *fmt, va_list args)
|
||||
{
|
||||
char buf[BUFSIZE];
|
||||
|
||||
if (!servsock)
|
||||
return;
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
if (source) {
|
||||
if (servsock)
|
||||
sockprintf(servsock, ":%s %s\r\n", source, buf);
|
||||
log_debug(1, "Sent: :%s %s", source, buf);
|
||||
} else {
|
||||
if (servsock)
|
||||
sockprintf(servsock, "%s\r\n", buf);
|
||||
log_debug(1, "Sent: %s", buf);
|
||||
}
|
||||
last_send = time(NULL);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Send an ERROR message and close the connection to the server. */
|
||||
|
||||
void send_error(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[BUFSIZE];
|
||||
|
||||
snprintf(buf, sizeof(buf), "ERROR :%s", fmt);
|
||||
va_start(args, fmt);
|
||||
vsend_cmd(NULL, buf, args);
|
||||
va_end(args);
|
||||
disconn(servsock);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Send a command to change channel modes. (Needed to handle various
|
||||
* varieties of timestamping. We currently cheat and use a timestamp of
|
||||
* zero to force our modes through.)
|
||||
*/
|
||||
|
||||
void send_cmode_cmd(const char *source, const char *channel,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[BUFSIZE];
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
if (protocol_features & PF_MODETS_FIRST)
|
||||
send_channel_cmd(source, "MODE %s 0 %s", channel, buf);
|
||||
else
|
||||
send_channel_cmd(source, "MODE %s %s", channel, buf);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Introduce a pseudoclient nickname. `flags' includes PSEUDO_OPER if the
|
||||
* pseudoclient requires IRC operator privileges (however, the client may
|
||||
* not actually get +o if the server does not require it), and PSEUDO_INVIS
|
||||
* if the pseudoclient should be invisible (+i).
|
||||
*/
|
||||
|
||||
void send_pseudo_nick(const char *nick, const char *realname, int flags)
|
||||
{
|
||||
char modebuf[BUFSIZE];
|
||||
|
||||
snprintf(modebuf, sizeof(modebuf), "%s%s%s",
|
||||
pseudoclient_modes,
|
||||
(flags & PSEUDO_OPER) && pseudoclient_oper ? "o" : "",
|
||||
(flags & PSEUDO_INVIS) ? "i" : "");
|
||||
send_nick(nick, ServiceUser, ServiceHost, ServerName, realname, modebuf);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Send a NOTICE from the given source to the given nick. */
|
||||
|
||||
void notice(const char *source, const char *dest, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[BUFSIZE];
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
send_cmd(source, "NOTICE %s :%s", dest, buf);
|
||||
}
|
||||
|
||||
|
||||
/* Send a NULL-terminated array of text as NOTICEs. */
|
||||
|
||||
void notice_list(const char *source, const char *dest, const char **text)
|
||||
{
|
||||
while (*text) {
|
||||
/* Have to kludge around client/server silliness here: if a notice
|
||||
* includes no text, it is ignored, so we replace blank lines by
|
||||
* lines with a single space. */
|
||||
if (**text)
|
||||
notice(source, dest, *text);
|
||||
else
|
||||
notice(source, dest, " ");
|
||||
text++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Send a message in the user's selected language to the user using NOTICE. */
|
||||
|
||||
void notice_lang(const char *source, const User *dest, int message, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[4096]; /* because messages can be really big */
|
||||
char *s, *t;
|
||||
const char *fmt;
|
||||
|
||||
if (!dest)
|
||||
return;
|
||||
fmt = getstring(dest->ngi, message);
|
||||
if (!fmt)
|
||||
return;
|
||||
va_start(args, message);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
s = buf;
|
||||
while (*s) {
|
||||
char c;
|
||||
t = s;
|
||||
s += strcspn(s, "\n");
|
||||
c = *s;
|
||||
*s = 0;
|
||||
send_cmd(source, "NOTICE %s :%s", dest->nick, *t ? t : " ");
|
||||
*s = c;
|
||||
if (c)
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Like notice_lang(), but replace %S by the source. This is an ugly hack
|
||||
* to simplify letting help messages display the name of the pseudoclient
|
||||
* that's sending them.
|
||||
*/
|
||||
void notice_help(const char *source, const User *dest, int message, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[4096], buf2[4096], outbuf[BUFSIZE];
|
||||
char *s, *t;
|
||||
const char *fmt;
|
||||
|
||||
if (!dest)
|
||||
return;
|
||||
fmt = getstring(dest->ngi, message);
|
||||
if (!fmt)
|
||||
return;
|
||||
/* Some sprintf()'s eat %S or turn it into just S, so change all %S's
|
||||
* into \1\1... we assume this doesn't occur anywhere else in the
|
||||
* string. */
|
||||
strbcpy(buf2, fmt);
|
||||
strnrepl(buf2, sizeof(buf2), "%S", "\1\1");
|
||||
va_start(args, message);
|
||||
vsnprintf(buf, sizeof(buf), buf2, args);
|
||||
va_end(args);
|
||||
s = buf;
|
||||
while (*s) {
|
||||
char c;
|
||||
t = s;
|
||||
s += strcspn(s, "\n");
|
||||
c = *s;
|
||||
*s = 0;
|
||||
strbcpy(outbuf, t);
|
||||
*s = c;
|
||||
if (c)
|
||||
s++;
|
||||
strnrepl(outbuf, sizeof(outbuf), "\1\1", source);
|
||||
send_cmd(source, "NOTICE %s :%s", dest->nick, *outbuf ? outbuf : " ");
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Send a PRIVMSG from the given source to the given nick. */
|
||||
|
||||
void privmsg(const char *source, const char *dest, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char buf[BUFSIZE];
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
send_cmd(source, "PRIVMSG %s :%s", dest, buf);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handler for unimplemented functions. */
|
||||
|
||||
static void unimplemented(void)
|
||||
{
|
||||
fatal("send.c: No (or bad) protocol module loaded.");
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
130
send.h
Normal file
130
send.h
Normal file
@ -0,0 +1,130 @@
|
||||
/* Common routines and constants for message sending.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef SEND_H
|
||||
#define SEND_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Protocol information (set by protocol module): */
|
||||
|
||||
extern const char *protocol_name;
|
||||
extern const char *protocol_version;
|
||||
extern uint32 protocol_features;
|
||||
extern int protocol_nickmax;
|
||||
extern const char *pseudoclient_modes;
|
||||
extern const char *enforcer_modes;
|
||||
extern int pseudoclient_oper;
|
||||
|
||||
|
||||
/* Constants for protocol_features: */
|
||||
|
||||
/* Has a "halfop" (+h) channel user mode (Unreal, etc.) */
|
||||
#define PF_HALFOP 0x00000001
|
||||
/* Has "protect" (+a) and "owner" (+q) channel user modes (Unreal, etc.) */
|
||||
#define PF_CHANPROT 0x00000002
|
||||
/* Has channel ban exceptions (+e) */
|
||||
#define PF_BANEXCEPT 0x00000004
|
||||
/* Has SZLINE command or equivalent (ban IP address/mask from all servers) */
|
||||
#define PF_SZLINE 0x00000008
|
||||
/* Suppresses QUIT messages from clients on server disconnects */
|
||||
#define PF_NOQUIT 0x00000010
|
||||
/* Supports some method to make a client join a channel */
|
||||
#define PF_SVSJOIN 0x00000020
|
||||
/* Supports some method of forcibly changing a client's nickname */
|
||||
#define PF_CHANGENICK 0x00000040
|
||||
/* Supports autokill exclusions */
|
||||
#define PF_AKILL_EXCL 0x00000080
|
||||
/* Timestamp in MODE message comes right after channel name */
|
||||
#define PF_MODETS_FIRST 0x00000100
|
||||
/* Has channel invite masks (+I) */
|
||||
#define PF_INVITEMASK 0x00000200
|
||||
|
||||
/* Invalid flag, used to check whether protocol_features was set */
|
||||
#define PF_UNSET 0x80000000
|
||||
|
||||
|
||||
/* Routines to be implemented by protocol modules: */
|
||||
|
||||
E_FUNCPTR(void, send_nick, (const char *nick, const char *user,
|
||||
const char *host, const char *server,
|
||||
const char *name, const char *modes));
|
||||
E_FUNCPTR(void, send_nickchange, (const char *nick, const char *newnick));
|
||||
E_FUNCPTR(void, send_namechange, (const char *nick, const char *newname));
|
||||
E_FUNCPTR(void, send_server, (void));
|
||||
E_FUNCPTR(void, send_server_remote, (const char *server, const char *desc));
|
||||
E_FUNCPTR(void, wallops, (const char *source, const char *fmt, ...)
|
||||
FORMAT(printf,2,3));
|
||||
E_FUNCPTR(void, notice_all, (const char *source, const char *fmt, ...)
|
||||
FORMAT(printf,2,3));
|
||||
E_FUNCPTR(void, send_channel_cmd, (const char *source, const char *fmt, ...)
|
||||
FORMAT(printf,2,3));
|
||||
E_FUNCPTR(void, send_nickchange_remote,
|
||||
(const char *nick, const char *newnick));
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Flags for send_pseudo_nick(): */
|
||||
|
||||
/* Pseudoclient requires oper privileges (note that depending on the
|
||||
* protocol, the pseudoclient may not actually get +o) */
|
||||
#define PSEUDO_OPER 0x01
|
||||
/* Pseudoclient should be invisible (+i) */
|
||||
#define PSEUDO_INVIS 0x02
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* External declarations. */
|
||||
|
||||
/* Last time at which data was sent. Used to check whether a PING needs
|
||||
* to be sent. */
|
||||
extern time_t last_send;
|
||||
|
||||
/* Initialization/cleanup */
|
||||
extern int send_init(int ac, char **av);
|
||||
extern void send_cleanup(void);
|
||||
|
||||
/* Basic message-sending routine ([v]printf-like) */
|
||||
extern void send_cmd(const char *source, const char *fmt, ...)
|
||||
FORMAT(printf,2,3);
|
||||
extern void vsend_cmd(const char *source, const char *fmt, va_list args)
|
||||
FORMAT(printf,2,0);
|
||||
|
||||
/* Shortcuts for sending miscellaneous messages */
|
||||
extern void send_error(const char *fmt, ...) FORMAT(printf,1,2);
|
||||
extern void send_cmode_cmd(const char *source, const char *channel,
|
||||
const char *fmt, ...);
|
||||
extern void send_pseudo_nick(const char *nick, const char *realname,
|
||||
int flags);
|
||||
|
||||
/* Routines for PRIVMSG/NOTICE sending */
|
||||
extern void notice(const char *source, const char *dest, const char *fmt, ...)
|
||||
FORMAT(printf,3,4);
|
||||
extern void notice_list(const char *source, const char *dest,
|
||||
const char **text);
|
||||
extern void notice_lang(const char *source, const User *dest, int message,
|
||||
...);
|
||||
extern void notice_help(const char *source, const User *dest, int message,
|
||||
...);
|
||||
extern void privmsg(const char *source, const char *dest, const char *fmt, ...)
|
||||
FORMAT(printf,3,4);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* SEND_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
270
servers.c
Normal file
270
servers.c
Normal file
@ -0,0 +1,270 @@
|
||||
/* Routines to maintain a list of online servers.
|
||||
* Based on code by Andrew Kempe (TheShadow)
|
||||
* E-mail: <theshadow@shadowfire.org>
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "modules.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#define add_server static add_server
|
||||
#define del_server static del_server
|
||||
#include "hash.h"
|
||||
DEFINE_HASH(server, Server, name)
|
||||
#undef add_server
|
||||
#undef del_server
|
||||
|
||||
static Server *root_server; /* Entry for root server (Services) */
|
||||
static int16 servercnt = 0; /* Number of online servers */
|
||||
|
||||
/* Callback IDs: */
|
||||
static int cb_create = -1;
|
||||
static int cb_delete = -1;
|
||||
|
||||
/*************************************************************************/
|
||||
/**************************** Internal functions *************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Allocate a new Server structure, fill in basic values, link it to the
|
||||
* overall list, and return it. Always successful.
|
||||
*/
|
||||
|
||||
static Server *new_server(const char *servername)
|
||||
{
|
||||
Server *server;
|
||||
|
||||
servercnt++;
|
||||
server = scalloc(sizeof(Server), 1);
|
||||
server->name = sstrdup(servername);
|
||||
add_server(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
/* Remove and free a Server structure. */
|
||||
|
||||
static void delete_server(Server *server)
|
||||
{
|
||||
del_server(server);
|
||||
servercnt--;
|
||||
free(server->name);
|
||||
free(server);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove the given server. This takes care of recursively removing child
|
||||
* servers, handling NOQUIT, calling the server delete callback, and
|
||||
* actually deleting the server data.
|
||||
*/
|
||||
|
||||
static void recursive_squit(Server *parent, const char *reason);
|
||||
|
||||
static void squit_server(Server *server, const char *reason)
|
||||
{
|
||||
User *user, *nextuser;
|
||||
|
||||
recursive_squit(server, reason);
|
||||
if (protocol_features & PF_NOQUIT) {
|
||||
#define next snext
|
||||
#define prev sprev
|
||||
LIST_FOREACH_SAFE (user, server->userlist, nextuser)
|
||||
quit_user(user, reason, 0);
|
||||
#undef next
|
||||
#undef prev
|
||||
}
|
||||
if (!server->fake)
|
||||
call_callback_2(cb_delete, server, reason);
|
||||
delete_server(server);
|
||||
}
|
||||
|
||||
|
||||
/* "SQUIT" all servers who are linked to us via the specified server by
|
||||
* deleting them from the server list. The parent server is not deleted,
|
||||
* so this must be done by the calling function.
|
||||
*/
|
||||
|
||||
static void recursive_squit(Server *parent, const char *reason)
|
||||
{
|
||||
Server *server, *nextserver;
|
||||
|
||||
server = parent->child;
|
||||
log_debug(2, "recursive_squit, parent: %s", parent->name);
|
||||
while (server) {
|
||||
nextserver = server->sibling;
|
||||
log_debug(2, "recursive_squit, child: %s", server->name);
|
||||
squit_server(server, reason);
|
||||
server = nextserver;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/**************************** External functions *************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a server SERVER command.
|
||||
* source = server's hub; an empty string indicates that this is our hub.
|
||||
* av[0] = server's name
|
||||
* If ac < 0, the server in av[0] is assumed to be a "dummy" server that
|
||||
* should not be passed to the callback (e.g. for JUPE).
|
||||
*
|
||||
* When called internally to add a server (from OperServ JUPE, etc.),
|
||||
* callers may assume that the contents of the argument strings will not be
|
||||
* modified.
|
||||
*/
|
||||
|
||||
void do_server(const char *source, int ac, char **av)
|
||||
{
|
||||
Server *server, *tmpserver;
|
||||
|
||||
server = new_server(av[0]);
|
||||
server->t_join = time(NULL);
|
||||
server->child = NULL;
|
||||
server->sibling = NULL;
|
||||
|
||||
if (source && *source) {
|
||||
server->hub = get_server(source);
|
||||
if (!server->hub) {
|
||||
/* PARANOIA: This should NEVER EVER happen, but we check anyway.
|
||||
*
|
||||
* I've heard that on older ircds it is possible for "source"
|
||||
* not to be the new server's hub. This will cause problems.
|
||||
* -TheShadow
|
||||
*/
|
||||
wallops(ServerName,
|
||||
"WARNING: Could not find server \2%s\2 which is supposed "
|
||||
"to be the hub for \2%s\2", source, av[0]);
|
||||
log("server: could not find hub %s for %s", source, av[0]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
server->hub = root_server;
|
||||
}
|
||||
if (!server->hub->child) {
|
||||
server->hub->child = server;
|
||||
} else {
|
||||
tmpserver = server->hub->child;
|
||||
while (tmpserver->sibling)
|
||||
tmpserver = tmpserver->sibling;
|
||||
tmpserver->sibling = server;
|
||||
}
|
||||
|
||||
if (ac > 0)
|
||||
call_callback_1(cb_create, server);
|
||||
else
|
||||
server->fake = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a server SQUIT command.
|
||||
* av[0] = server's name
|
||||
* av[1] = quit message
|
||||
*/
|
||||
|
||||
void do_squit(const char *source, int ac, char **av)
|
||||
{
|
||||
Server *server;
|
||||
|
||||
server = get_server(av[0]);
|
||||
|
||||
if (server) {
|
||||
if (server->hub) {
|
||||
if (server->hub->child == server) {
|
||||
server->hub->child = server->sibling;
|
||||
} else {
|
||||
Server *tmpserver;
|
||||
for (tmpserver = server->hub->child; tmpserver->sibling;
|
||||
tmpserver = tmpserver->sibling) {
|
||||
if (tmpserver->sibling == server) {
|
||||
tmpserver->sibling = server->sibling;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
squit_server(server, av[1]);
|
||||
|
||||
} else {
|
||||
wallops(ServerName,
|
||||
"WARNING: Tried to quit non-existent server: \2%s", av[0]);
|
||||
log("server: Tried to quit non-existent server: %s", av[0]);
|
||||
log("server: Input buffer: %s", inbuf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
int server_init(int ac, char **av)
|
||||
{
|
||||
Server *server;
|
||||
|
||||
cb_create = register_callback("server create");
|
||||
cb_delete = register_callback("server delete");
|
||||
if (cb_create < 0 || cb_delete < 0) {
|
||||
log("server_init: register_callback() failed\n");
|
||||
return 0;
|
||||
}
|
||||
server = new_server("");
|
||||
server->fake = 1;
|
||||
server->t_join = time(NULL);
|
||||
server->hub = server->child = server->sibling = NULL;
|
||||
root_server = server;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove all servers; this recursively takes out all users and channels,
|
||||
* as well.
|
||||
*/
|
||||
|
||||
void server_cleanup(void)
|
||||
{
|
||||
uint32 pf = protocol_features;
|
||||
protocol_features |= PF_NOQUIT;
|
||||
squit_server(root_server, "server_cleanup");
|
||||
protocol_features = pf;
|
||||
unregister_callback(cb_delete);
|
||||
unregister_callback(cb_create);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return information on memory use. Assumes pointers are valid. */
|
||||
|
||||
void get_server_stats(long *nservers, long *memuse)
|
||||
{
|
||||
Server *server;
|
||||
long mem;
|
||||
|
||||
mem = sizeof(Server) * servercnt;
|
||||
for (server = first_server(); server; server = next_server())
|
||||
mem += strlen(server->name)+1;
|
||||
|
||||
*nservers = servercnt;
|
||||
*memuse = mem;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
42
servers.h
Normal file
42
servers.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* Online server data.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef SERVERS_H
|
||||
#define SERVERS_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
struct server_ {
|
||||
Server *next, *prev; /* Use to navigate the entire server list */
|
||||
Server *hub; /* Server's hub from our point of view */
|
||||
Server *child, *sibling; /* Server's children from our P.O.V. */
|
||||
|
||||
int fake; /* Is this a "fake" (root/juped) server? */
|
||||
char *name; /* Server's name */
|
||||
time_t t_join; /* Time server joined us (0 == not here). */
|
||||
|
||||
User *userlist; /* List of users on server. NOTE: this is
|
||||
* linked via snext/sprev, not next/prev. */
|
||||
|
||||
ServerStats *stats;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* SERVERS_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
99
services.h
Normal file
99
services.h
Normal file
@ -0,0 +1,99 @@
|
||||
/* Main header for Services.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef SERVICES_H
|
||||
#define SERVICES_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* System configuration information (from "configure"): */
|
||||
#include "config.h"
|
||||
|
||||
/* User configuration and basic constants, macros, and includes: */
|
||||
#include "defs.h"
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Types corresponding to various structures. These have to come first
|
||||
* because some structures reference each other circularly. */
|
||||
|
||||
typedef struct user_ User;
|
||||
typedef struct channel_ Channel;
|
||||
typedef struct server_ Server;
|
||||
typedef struct serverstats_ ServerStats;
|
||||
|
||||
typedef struct nickinfo_ NickInfo;
|
||||
typedef struct nickgroupinfo_ NickGroupInfo;
|
||||
typedef struct channelinfo_ ChannelInfo;
|
||||
typedef struct memoinfo_ MemoInfo;
|
||||
|
||||
|
||||
/* Types for various name buffers, so we can make arrays of them. */
|
||||
|
||||
typedef char nickname_t[NICKMAX];
|
||||
typedef char channame_t[CHANMAX];
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Suspension info structure. */
|
||||
|
||||
typedef struct suspendinfo_ SuspendInfo;
|
||||
struct suspendinfo_ {
|
||||
char who[NICKMAX]; /* who added this suspension */
|
||||
char *reason;
|
||||
time_t suspended;
|
||||
time_t expires; /* 0 for no expiry */
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Constants for "what" parameter to clear_channel(). */
|
||||
|
||||
#define CLEAR_MODES 0x0001 /* Binary modes */
|
||||
#define CLEAR_BANS 0x0002 /* Bans */
|
||||
#define CLEAR_EXCEPTS 0x0004 /* Ban exceptions (no-op if not supported) */
|
||||
#define CLEAR_INVITES 0x0008 /* Invite masks (no-op if not supported) */
|
||||
#define CLEAR_UMODES 0x0010 /* User modes (+v, +o) */
|
||||
|
||||
#define CLEAR_USERS 0x8000 /* Kick all users and empty the channel */
|
||||
|
||||
/* All channel modes: */
|
||||
#define CLEAR_CMODES (CLEAR_MODES|CLEAR_BANS|CLEAR_EXCEPTS|CLEAR_INVITES)
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* All other "main" include files. */
|
||||
|
||||
#include "memory.h"
|
||||
#include "list-array.h"
|
||||
#include "log.h"
|
||||
#include "sockets.h"
|
||||
#include "send.h"
|
||||
#include "modes.h"
|
||||
#include "users.h"
|
||||
#include "channels.h"
|
||||
#include "servers.h"
|
||||
|
||||
#include "extern.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* SERVICES_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
235
signals.c
Normal file
235
signals.c
Normal file
@ -0,0 +1,235 @@
|
||||
/* Signal handling routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include <setjmp.h>
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* If we get a signal, use this to jump out of the main loop. */
|
||||
static sigjmp_buf *panic_ptr = NULL;
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Various signal handlers. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* SIGHUP = save databases and rehash configuration files */
|
||||
static void sighup_handler(int sig_unused)
|
||||
{
|
||||
signal(SIGHUP, SIG_IGN); /* in case we get double signalled */
|
||||
log("Received SIGHUP, saving data and rehashing.");
|
||||
wallops(NULL,
|
||||
"Received SIGHUP, saving data and rehashing configuration files");
|
||||
save_data_now();
|
||||
reconfigure();
|
||||
signal(SIGHUP, sighup_handler);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* SIGTERM = save databases and shut down */
|
||||
static void sigterm_handler(int sig_unused)
|
||||
{
|
||||
save_data = 1;
|
||||
delayed_quit = 1;
|
||||
signal(SIGTERM, SIG_IGN);
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
log("Received SIGTERM, exiting.");
|
||||
strbcpy(quitmsg, "Shutting down on SIGTERM");
|
||||
siglongjmp(*panic_ptr, 1);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* SIGUSR2 = close and reopen log file */
|
||||
static void sigusr2_handler(int sig_unused)
|
||||
{
|
||||
log("Received SIGUSR2, cycling log file.");
|
||||
if (log_is_open()) {
|
||||
close_log();
|
||||
open_log();
|
||||
}
|
||||
signal(SIGUSR2, sigusr2_handler);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* If we get a weird signal, come here. */
|
||||
static void weirdsig_handler(int signum)
|
||||
{
|
||||
static int dying = 0; /* Flag to avoid infinite recursion */
|
||||
|
||||
if (dying++) {
|
||||
/* Double signal, give up. Set `servsock' to NULL to avoid a
|
||||
* message going out that way, just in case the socket code is
|
||||
* confused/broken */
|
||||
servsock = NULL;
|
||||
if (signum == SIGUSR2) {
|
||||
fatal("Out of memory while shutting down");
|
||||
} else {
|
||||
#if HAVE_STRSIGNAL
|
||||
fatal("Caught signal %d (%s) while shutting down", signum,
|
||||
strsignal(signum));
|
||||
#else
|
||||
fatal("Caught signal %d while shutting down", signum);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* Avoid spurious keyboard signals killing us while shutting down */
|
||||
signal(SIGINT, SIG_IGN);
|
||||
signal(SIGQUIT, SIG_IGN);
|
||||
signal(SIGTSTP, SIG_IGN);
|
||||
|
||||
/* If we died processing a message, let people know about it */
|
||||
if (signum != SIGINT && signum != SIGQUIT) {
|
||||
if (*inbuf) {
|
||||
log("PANIC! signal %d, buffer = %s", signum, inbuf);
|
||||
/* Cut off if this would make IRC command >510 characters. */
|
||||
if (strlen(inbuf) > 448) {
|
||||
inbuf[446] = '>';
|
||||
inbuf[447] = '>';
|
||||
inbuf[448] = 0;
|
||||
}
|
||||
wallops(NULL, "PANIC! buffer = %s\r\n", inbuf);
|
||||
} else {
|
||||
log("PANIC! signal %d (no buffer)", signum);
|
||||
wallops(NULL, "PANIC! signal %d (no buffer)", signum);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pick an appropriate quit message */
|
||||
if (signum == SIGUSR1) {
|
||||
strbcpy(quitmsg, "Out of memory!");
|
||||
quitting = 1;
|
||||
} else {
|
||||
#if HAVE_STRSIGNAL
|
||||
snprintf(quitmsg, sizeof(quitmsg),
|
||||
"Services terminating: %s", strsignal(signum));
|
||||
#else
|
||||
snprintf(quitmsg, sizeof(quitmsg),
|
||||
"Services terminating on signal %d", signum);
|
||||
#endif
|
||||
quitting = 1;
|
||||
}
|
||||
|
||||
/* Actually quit */
|
||||
if (panic_ptr) {
|
||||
siglongjmp(*panic_ptr, 1);
|
||||
} else {
|
||||
log("%s", quitmsg);
|
||||
if (isatty(2))
|
||||
fprintf(stderr, "%s\n", quitmsg);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Set up signal handlers. Catch certain signals to let us do things or
|
||||
* panic as necessary, and ignore all others.
|
||||
*/
|
||||
|
||||
void init_signals(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Start out with special signals disabled */
|
||||
disable_signals();
|
||||
|
||||
/* Set all signals to "ignore" */
|
||||
for (i = 1; i <= NSIG; i++) {
|
||||
#if DUMPCORE
|
||||
if (i != SIGSEGV)
|
||||
#endif
|
||||
if (i != SIGPROF && i != SIGCHLD)
|
||||
signal(i, SIG_IGN);
|
||||
}
|
||||
|
||||
/* Specify particular signals we want to catch */
|
||||
|
||||
/* Signals that probably mean bad things have happened */
|
||||
#if !DUMPCORE
|
||||
signal(SIGSEGV, weirdsig_handler);
|
||||
#endif
|
||||
signal(SIGBUS, weirdsig_handler);
|
||||
signal(SIGILL, weirdsig_handler);
|
||||
signal(SIGTRAP, weirdsig_handler);
|
||||
signal(SIGFPE, weirdsig_handler);
|
||||
#ifdef SIGIOT
|
||||
signal(SIGIOT, weirdsig_handler);
|
||||
#endif
|
||||
|
||||
/* This is our "out-of-memory" panic switch */
|
||||
signal(SIGUSR1, weirdsig_handler);
|
||||
|
||||
/* Other special handlers */
|
||||
signal(SIGTERM, sigterm_handler);
|
||||
signal(SIGINT, weirdsig_handler);
|
||||
signal(SIGQUIT, weirdsig_handler);
|
||||
signal(SIGHUP, sighup_handler);
|
||||
signal(SIGUSR2, sigusr2_handler);
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Helper routine for main.c's DO_SIGSETJMP() macro; saves a pointer to the
|
||||
* environment buffer locally.
|
||||
*/
|
||||
|
||||
void do_sigsetjmp(void *bufptr)
|
||||
{
|
||||
panic_ptr = bufptr;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Enable or disable receipt of certain signals (in particular, those which
|
||||
* cause us to take actions other than simply terminating the program, to
|
||||
* avoid such signals happening at inopportune times and causing things to
|
||||
* break).
|
||||
*/
|
||||
|
||||
void enable_signals(void)
|
||||
{
|
||||
sigset_t sigs;
|
||||
sigemptyset(&sigs);
|
||||
sigaddset(&sigs, SIGHUP);
|
||||
sigaddset(&sigs, SIGTERM);
|
||||
sigaddset(&sigs, SIGUSR2);
|
||||
sigprocmask(SIG_UNBLOCK, &sigs, NULL);
|
||||
}
|
||||
|
||||
|
||||
void disable_signals(void)
|
||||
{
|
||||
sigset_t sigs;
|
||||
sigemptyset(&sigs);
|
||||
sigaddset(&sigs, SIGHUP);
|
||||
sigaddset(&sigs, SIGTERM);
|
||||
sigaddset(&sigs, SIGUSR2);
|
||||
sigprocmask(SIG_BLOCK, &sigs, NULL);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
191
sockets.h
Normal file
191
sockets.h
Normal file
@ -0,0 +1,191 @@
|
||||
/* Definitions/declarations for socket utility routines.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef SOCKETS_H
|
||||
#define SOCKETS_H
|
||||
|
||||
#include <sys/socket.h> /* for struct sockaddr */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Services implements an event-based socket management system using
|
||||
* callbacks for socket-related events (connect, disconnect, read).
|
||||
* Sockets are created using sock_new() and freed using sock_free();
|
||||
* callbacks are registered using sock_setcb(SCB_*,function). The
|
||||
* check_sockets() function does the actual checking for socket activity,
|
||||
* and should be called by the program's main loop.
|
||||
*
|
||||
* By default, sockets are non-blocking; if a socket is not ready for
|
||||
* writing and no more buffer space is available, write attempts will stop
|
||||
* at that point, setting errno to EAGAIN. sock_set_blocking() allows
|
||||
* sockets to be set to blocking mode, which causes write attempts to wait
|
||||
* until either buffer space becomes available or a hard error (such as the
|
||||
* connection being broken) occurs. sock_get_blocking() returns whether
|
||||
* the socket is set to blocking mode (nonzero return value, but not -1) or
|
||||
* not (zero).
|
||||
*
|
||||
* Since ordinary writes are buffered, the caller has no easy way to check
|
||||
* how much of the data has actually been transferred, or, consequently,
|
||||
* whether the remote host is actually accepting data or just sitting
|
||||
* around doing nothing. Since the latter case would cause socket slots to
|
||||
* be used up for long periods of time, the sock_set_wto() function allows
|
||||
* a write timeout to be set for a socket; if no data is accepted by the
|
||||
* remote side within the specified number of seconds, the socket will be
|
||||
* considered dead and processed as if the remote host had closed the
|
||||
* connection. The write timeout is disabled by default, but if enabled,
|
||||
* it can be disabled again by setting a timeout value of zero.
|
||||
*
|
||||
* Likewise, the program may wish to perform some periodic actions even if
|
||||
* no socket activity is occurring. The sock_set_rto() function can be
|
||||
* used to set a global read timeout in milliseconds; if no new data
|
||||
* arrives during this period, check_sockets() will return control to the
|
||||
* caller. sock_set_rto(-1) will revert to the default behavior of waiting
|
||||
* indefinitely for socket activity.
|
||||
*
|
||||
* When a callback function is called, it is passed two parameters: a
|
||||
* pointer to the socket structure (Socket *) for the socket on which the
|
||||
* event occurred, and a void * parameter whose meaning depends on the
|
||||
* particular callback (see below). Callbacks will never be called nested
|
||||
* unless a callback explicitly calls check_sockets(), which is not
|
||||
* recommended; thus it is safe to set new callbacks for a socket inside of
|
||||
* a callback function.
|
||||
*
|
||||
* There are currently six callbacks implemented:
|
||||
*
|
||||
* SCB_CONNECT
|
||||
* Called when a connection initiated with conn() completes. The
|
||||
* void * parameter is not used.
|
||||
*
|
||||
* SCB_DISCONNECT
|
||||
* Called when a connection is broken, either by the remote end or as
|
||||
* a result of calling disconn(). Also called when a connection
|
||||
* initiated with conn() fails. The void * parameter is set to either
|
||||
* DISCONN_LOCAL, DISCONN_REMOTE, or DISCONN_CONNFAIL to indicate the
|
||||
* cause of the disconnection. For DISCONN_REMOTE or DISCONN_CONNFAIL,
|
||||
* the `errno' variable will contain the cause of disconnection if
|
||||
* known, zero otherwise.
|
||||
*
|
||||
* SCB_ACCEPT
|
||||
* Called when a listener socket receives a connection. The void *
|
||||
* parameter, cast to Socket *, is the new socket created for the
|
||||
* incoming connection; the address of the remote connection can be
|
||||
* retrieved with sock_remote(). NOTE: a listener socket will not
|
||||
* accept any connections unless this callback is set.
|
||||
*
|
||||
* SCB_READ
|
||||
* Called when data is available for reading. The void * parameter,
|
||||
* cast to uint32, indicates the number of bytes available for reading.
|
||||
* If some, but not all, of the data is read by the routine (using
|
||||
* sread(), sgetc(), etc.), the remainder will be kept in the read
|
||||
* buffer and the callback will be called again immediately.
|
||||
*
|
||||
* SCB_READLINE
|
||||
* Like SCB_READ, called when data is available for reading; however,
|
||||
* this callback is only called when at least one full line of data is
|
||||
* available for reading. The void * parameter, cast to uint32,
|
||||
* indicates the number of bytes available for reading. If both
|
||||
* SCB_READ and SCB_READLINE are set, SCB_READ will be called first,
|
||||
* and if it leaves any data (including a newline) in the buffer,
|
||||
* SCB_READLINE will then be called. Likewise, if SCB_READLINE leaves
|
||||
* data in the buffer--which may include a partial line, if a
|
||||
* fractional number of lines was received--SCB_READ will be called
|
||||
* immediately following.
|
||||
*
|
||||
* SCB_TRIGGER
|
||||
* Called when a "write trigger" is encountered. These triggers are
|
||||
* set with swrite_trigger(), and are called when all data written to
|
||||
* the socket before the swrite_trigger() call has been sent to the
|
||||
* remote host, but before any data written after the trigger is sent.
|
||||
* The void * parameter is the parameter passed to swrite_trigger().
|
||||
*/
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Minimum (also initial) buffer size for socket; also serves as the
|
||||
* increment for buffer expansion. */
|
||||
#define SOCK_MIN_BUFSIZE 4096
|
||||
|
||||
/* Structure for socket data. This structure is not actually defined here
|
||||
* in order to hide it from the user. */
|
||||
struct socket_;
|
||||
|
||||
/* Typedef for socket structure. */
|
||||
typedef struct socket_ Socket;
|
||||
|
||||
/* Type of socket callback functions. */
|
||||
typedef void (*SocketCallback)(Socket *s, void *param);
|
||||
|
||||
/* Identifiers for callback functions (used with sock_setcb()). */
|
||||
typedef enum {
|
||||
SCB_CONNECT = 1,
|
||||
SCB_DISCONNECT,
|
||||
SCB_ACCEPT,
|
||||
SCB_READ,
|
||||
SCB_READLINE,
|
||||
SCB_TRIGGER,
|
||||
} SocketCallbackID;
|
||||
|
||||
/* Values of parameter to disconnect callback. */
|
||||
#define DISCONN_LOCAL ((void *)1) /* disconn() function called */
|
||||
#define DISCONN_REMOTE ((void *)2) /* Transmission error */
|
||||
#define DISCONN_CONNFAIL ((void *)3) /* Connection attempt failed */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
extern void sock_set_buflimits(uint32 per_conn, uint32 total);
|
||||
extern void sock_set_rto(int msec);
|
||||
|
||||
extern Socket *sock_new(void);
|
||||
extern void sock_free(Socket *s);
|
||||
extern void sock_setcb(Socket *s, SocketCallbackID which, SocketCallback func);
|
||||
extern int sock_isconn(const Socket *s);
|
||||
extern int sock_remote(const Socket *s, struct sockaddr *sa, int *lenptr);
|
||||
extern void sock_set_blocking(Socket *s, int blocking);
|
||||
extern int sock_get_blocking(const Socket *s);
|
||||
extern void sock_set_wto(Socket *s, int seconds);
|
||||
extern void sock_mute(Socket *s);
|
||||
extern void sock_unmute(Socket *s);
|
||||
extern uint32 read_buffer_len(const Socket *s);
|
||||
extern uint32 write_buffer_len(const Socket *s);
|
||||
extern int sock_rwstat(const Socket *s, uint64 *read_ret, uint64 *written_ret);
|
||||
extern int sock_bufstat(const Socket *s, uint32 *socksize_ret,
|
||||
uint32 *totalsize_ret, int *ratio1_ret,
|
||||
int *ratio2_ret);
|
||||
|
||||
extern void check_sockets(void);
|
||||
|
||||
extern int conn(Socket *s, const char *host, int port, const char *lhost,
|
||||
int lport);
|
||||
extern int disconn(Socket *s);
|
||||
extern int open_listener(Socket *s, const char *host, int port, int backlog);
|
||||
extern int close_listener(Socket *s);
|
||||
extern int32 sread(Socket *s, char *buf, int32 len);
|
||||
extern int32 swrite(Socket *s, const char *buf, int32 len);
|
||||
extern int32 swritemap(Socket *s, const char *buf, int32 len);
|
||||
extern int swrite_trigger(Socket *s, void *param);
|
||||
extern int sgetc(Socket *s);
|
||||
extern char *sgets(char *buf, int32 len, Socket *s);
|
||||
extern char *sgets2(char *buf, int32 len, Socket *s);
|
||||
extern int sputs(const char *str, Socket *s);
|
||||
extern int sockprintf(Socket *s, const char *fmt,...);
|
||||
extern int vsockprintf(Socket *s, const char *fmt, va_list args);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* SOCKETS_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
174
timeout.c
Normal file
174
timeout.c
Normal file
@ -0,0 +1,174 @@
|
||||
/* Routines for time-delayed actions.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#define IN_TIMEOUT_C
|
||||
#include "timeout.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static Timeout *timeouts = NULL;
|
||||
static int checking_timeouts = 0;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#ifdef DEBUG_COMMANDS
|
||||
|
||||
/* Send the timeout list to the given user. */
|
||||
|
||||
void send_timeout_list(User *u)
|
||||
{
|
||||
Timeout *to;
|
||||
uint32 now = time_msec();
|
||||
|
||||
notice(ServerName, u->nick, "Now: %u.%03u", now/1000, now%1000);
|
||||
LIST_FOREACH (to, timeouts) {
|
||||
notice(ServerName, u->nick, "%p: %u.%03u: %p (%p)",
|
||||
to, to->timeout/1000, to->timeout%1000, to->code, to->data);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DEBUG_COMMANDS */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Check the timeout list for any pending actions. */
|
||||
|
||||
void check_timeouts(void)
|
||||
{
|
||||
Timeout *to, *to2;
|
||||
uint32 now = time_msec();
|
||||
|
||||
if (checking_timeouts)
|
||||
fatal("check_timeouts() called recursively!");
|
||||
checking_timeouts = 1;
|
||||
log_debug(2, "Checking timeouts at time_msec = %u.%03u",
|
||||
now/1000, now%1000);
|
||||
|
||||
LIST_FOREACH_SAFE (to, timeouts, to2) {
|
||||
if (to->timeout) {
|
||||
if ((int32)(to->timeout - now) > 0)
|
||||
continue;
|
||||
log_debug(3, "Running timeout %p (code=%p repeat=%d)",
|
||||
to, to->code, to->repeat);
|
||||
to->code(to);
|
||||
if (to->repeat) {
|
||||
to->timeout = now + to->repeat;
|
||||
if (!to->timeout) /* watch out for zero! */
|
||||
to->timeout++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
LIST_REMOVE(to, timeouts);
|
||||
free(to);
|
||||
}
|
||||
|
||||
log_debug(2, "Finished timeout list");
|
||||
checking_timeouts = 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Add a timeout to the list to be triggered in `delay' seconds. If
|
||||
* `repeat' is nonzero, do not delete the timeout after it is triggered.
|
||||
* This must maintain the property that timeouts added from within a
|
||||
* timeout routine do not get checked during that run of the timeout list.
|
||||
*/
|
||||
|
||||
Timeout *add_timeout(int delay, void (*code)(Timeout *), int repeat)
|
||||
{
|
||||
if (delay < 0) {
|
||||
log("add_timeout(): called with a negative delay! (%d)", delay);
|
||||
return NULL;
|
||||
}
|
||||
if (!code) {
|
||||
log("add_timeout(): called with code==NULL!");
|
||||
return NULL;
|
||||
}
|
||||
if (delay > 2147483) { /* 2^31/1000 (watch out for difference overflow) */
|
||||
log("add_timeout(): delay (%ds) too long, shortening to 2147483s",
|
||||
delay);
|
||||
delay = 2147483;
|
||||
}
|
||||
return add_timeout_ms((uint32)delay*1000, code, repeat);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* The same thing, but using milliseconds instead of seconds. */
|
||||
|
||||
Timeout *add_timeout_ms(uint32 delay, void (*code)(Timeout *), int repeat)
|
||||
{
|
||||
Timeout *t;
|
||||
|
||||
if (!code) {
|
||||
log("add_timeout_ms(): called with code==NULL!");
|
||||
return NULL;
|
||||
}
|
||||
if (delay > 2147483647) {
|
||||
log("add_timeout_ms(): delay (%dms) too long, shortening to"
|
||||
" 2147483647ms", delay);
|
||||
delay = 2147483647;
|
||||
}
|
||||
t = malloc(sizeof(Timeout));
|
||||
if (!t)
|
||||
return NULL;
|
||||
t->settime = time(NULL);
|
||||
t->timeout = time_msec() + delay;
|
||||
/* t->timeout==0 is used to signal that the timeout should be deleted;
|
||||
* if the timeout value just happens to wrap around to 0, lengthen it
|
||||
* by a millisecond. */
|
||||
if (!t->timeout)
|
||||
t->timeout++;
|
||||
t->code = code;
|
||||
t->data = NULL;
|
||||
t->repeat = repeat ? delay : 0;
|
||||
LIST_INSERT(t, timeouts);
|
||||
return t;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove a timeout from the list (if it's there). */
|
||||
|
||||
void del_timeout(Timeout *t)
|
||||
{
|
||||
Timeout *ptr;
|
||||
|
||||
if (!t) {
|
||||
log("del_timeout(): called with t==NULL!");
|
||||
return;
|
||||
}
|
||||
LIST_FOREACH (ptr, timeouts) {
|
||||
if (ptr == t)
|
||||
break;
|
||||
}
|
||||
if (!ptr) {
|
||||
log("del_timeout(): attempted to remove timeout %p (not on list)", t);
|
||||
return;
|
||||
}
|
||||
if (checking_timeouts) {
|
||||
t->timeout = 0; /* delete it when we hit it in the list */
|
||||
return;
|
||||
}
|
||||
LIST_REMOVE(t, timeouts);
|
||||
free(t);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
69
timeout.h
Normal file
69
timeout.h
Normal file
@ -0,0 +1,69 @@
|
||||
/* Time-delay routine include stuff.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef TIMEOUT_H
|
||||
#define TIMEOUT_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Timeout type. */
|
||||
|
||||
typedef struct timeout_ Timeout;
|
||||
|
||||
struct timeout_ {
|
||||
void *data; /* Caller data; can be anything */
|
||||
time_t settime; /* Time timer was set (from time()) */
|
||||
/* Remainder is PRIVATE DATA! */
|
||||
#ifdef IN_TIMEOUT_C
|
||||
Timeout *next, *prev;
|
||||
uint32 timeout; /* In milliseconds (time_msec()) */
|
||||
uint32 repeat; /* Does this timeout repeat indefinitely?
|
||||
* (if nonzero, new value of `timeout') */
|
||||
void (*code)(Timeout *); /* This structure is passed to the code */
|
||||
#endif
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Check the timeout list for any pending actions. */
|
||||
extern void check_timeouts(void);
|
||||
|
||||
/* Add a timeout to the list to be triggered in `delay' seconds (`delay'
|
||||
* may be zero). Any timeout added from within a timeout routine will not
|
||||
* be checked during that run through the timeout list. Always succeeds. */
|
||||
extern Timeout *add_timeout(int delay, void (*code)(Timeout *), int repeat);
|
||||
|
||||
/* Add a timeout to the list to be triggered in `delay' milliseconds
|
||||
* (`delay' may be zero). */
|
||||
extern Timeout *add_timeout_ms(uint32 delay, void (*code)(Timeout *),
|
||||
int repeat);
|
||||
|
||||
/* Remove a timeout from the list (if it's there). */
|
||||
extern void del_timeout(Timeout *t);
|
||||
|
||||
#ifdef DEBUG_COMMANDS
|
||||
/* Send the list of timeouts to the given user. */
|
||||
extern void send_timeout_list(User *u);
|
||||
#endif
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* TIMEOUT_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
940
users.c
Normal file
940
users.c
Normal file
@ -0,0 +1,940 @@
|
||||
/* Routines to maintain a list of online users.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "services.h"
|
||||
#include "modules.h"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Maximum number of tries to randomly select a new guest nick when the
|
||||
* first one chosen is in use before giving up.
|
||||
*/
|
||||
#define MAKEGUESTNICK_TRIES 1000
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#define add_user static add_user
|
||||
#define del_user static del_user
|
||||
#include "hash.h"
|
||||
DEFINE_HASH(user, User, nick)
|
||||
#undef add_user
|
||||
#undef del_user
|
||||
|
||||
int32 usercnt = 0, opcnt = 0;
|
||||
|
||||
static int cb_check = -1;
|
||||
static int cb_create = -1;
|
||||
static int cb_servicestamp_change = -1;
|
||||
static int cb_nickchange1 = -1;
|
||||
static int cb_nickchange2 = -1;
|
||||
static int cb_delete = -1;
|
||||
static int cb_mode = -1;
|
||||
static int cb_chan_part = -1;
|
||||
static int cb_chan_kick = -1;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
int user_init(int ac, char **av)
|
||||
{
|
||||
cb_check = register_callback("user check");
|
||||
cb_create = register_callback("user create");
|
||||
cb_servicestamp_change = register_callback("user servicestamp change");
|
||||
cb_nickchange1 = register_callback("user nickchange (before)");
|
||||
cb_nickchange2 = register_callback("user nickchange (after)");
|
||||
cb_delete = register_callback("user delete");
|
||||
cb_mode = register_callback("user MODE");
|
||||
cb_chan_part = register_callback("channel PART");
|
||||
cb_chan_kick = register_callback("channel KICK");
|
||||
if (cb_check < 0 || cb_create < 0 || cb_servicestamp_change < 0
|
||||
|| cb_nickchange1 < 0 || cb_nickchange2 < 0 || cb_delete < 0
|
||||
|| cb_mode < 0 || cb_chan_part < 0 || cb_chan_kick < 0
|
||||
) {
|
||||
log("user_init: register_callback() failed\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void user_cleanup(void)
|
||||
{
|
||||
User *u;
|
||||
|
||||
for (u = first_user(); u; u = next_user())
|
||||
del_user(u);
|
||||
unregister_callback(cb_chan_kick);
|
||||
unregister_callback(cb_chan_part);
|
||||
unregister_callback(cb_mode);
|
||||
unregister_callback(cb_delete);
|
||||
unregister_callback(cb_nickchange2);
|
||||
unregister_callback(cb_nickchange1);
|
||||
unregister_callback(cb_servicestamp_change);
|
||||
unregister_callback(cb_create);
|
||||
unregister_callback(cb_check);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/************************* User list management **************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Allocate a new User structure, fill in basic values, link it to the
|
||||
* overall list, and return it. Always successful.
|
||||
*/
|
||||
|
||||
static User *new_user(const char *nick)
|
||||
{
|
||||
User *user;
|
||||
|
||||
user = scalloc(sizeof(User), 1);
|
||||
if (!nick)
|
||||
nick = "";
|
||||
strbcpy(user->nick, nick);
|
||||
add_user(user);
|
||||
usercnt++;
|
||||
return user;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Change the nickname of a user, and move pointers as necessary. */
|
||||
|
||||
static void change_user_nick(User *user, const char *nick)
|
||||
{
|
||||
del_user(user);
|
||||
strbcpy(user->nick, nick);
|
||||
add_user(user);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove and free a User structure. */
|
||||
|
||||
static void delete_user(User *user)
|
||||
{
|
||||
struct u_chanlist *c, *c2;
|
||||
struct u_chaninfolist *ci, *ci2;
|
||||
|
||||
usercnt--;
|
||||
if (is_oper(user))
|
||||
opcnt--;
|
||||
|
||||
free(user->username);
|
||||
free(user->host);
|
||||
free(user->ipaddr);
|
||||
free(user->realname);
|
||||
free(user->fakehost);
|
||||
free(user->id_nicks);
|
||||
LIST_FOREACH_SAFE (c, user->chans, c2) {
|
||||
chan_deluser(user, c->chan);
|
||||
free(c);
|
||||
}
|
||||
LIST_FOREACH_SAFE (ci, user->id_chans, ci2)
|
||||
free(ci);
|
||||
#define next snext
|
||||
#define prev sprev
|
||||
if (user->server)
|
||||
LIST_REMOVE(user, user->server->userlist);
|
||||
#undef next
|
||||
#undef prev
|
||||
del_user(user);
|
||||
free(user);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Remove a user on QUIT/KILL. Calls the user delete callback and then
|
||||
* deletes the User structure.
|
||||
*/
|
||||
|
||||
void quit_user(User *user, const char *quitmsg, int is_kill)
|
||||
{
|
||||
call_callback_3(cb_delete, user, quitmsg, is_kill);
|
||||
delete_user(user);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return statistics. Pointers are assumed to be valid. */
|
||||
|
||||
void get_user_stats(long *nusers, long *memuse)
|
||||
{
|
||||
long count = 0, mem = 0;
|
||||
User *user;
|
||||
struct u_chanlist *uc;
|
||||
struct u_chaninfolist *uci;
|
||||
|
||||
for (user = first_user(); user; user = next_user()) {
|
||||
count++;
|
||||
mem += sizeof(*user);
|
||||
if (user->username)
|
||||
mem += strlen(user->username)+1;
|
||||
if (user->host)
|
||||
mem += strlen(user->host)+1;
|
||||
if (user->realname)
|
||||
mem += strlen(user->realname)+1;
|
||||
LIST_FOREACH (uc, user->chans)
|
||||
mem += sizeof(*uc);
|
||||
LIST_FOREACH (uci, user->id_chans)
|
||||
mem += sizeof(*uci);
|
||||
}
|
||||
*nusers = count;
|
||||
*memuse = mem;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/************************* Internal routines *****************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Part a user from a channel given the user's u_chanlist entry for the
|
||||
* channel. */
|
||||
|
||||
static void part_channel_uc(User *user, struct u_chanlist *uc, int callback,
|
||||
const char *param, const char *source)
|
||||
{
|
||||
call_callback_4(callback, uc->chan, user, param, source);
|
||||
chan_deluser(user, uc->chan);
|
||||
LIST_REMOVE(uc, user->chans);
|
||||
free(uc);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/************************* Message handlers ******************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a server NICK command. Parameters must be in the following order.
|
||||
* av[0] = nick
|
||||
* If a new user:
|
||||
* av[1] = hop count
|
||||
* av[2] = signon time
|
||||
* av[3] = username
|
||||
* av[4] = hostname
|
||||
* av[5] = server
|
||||
* av[6] = real name
|
||||
* av[7] = services stamp (if ac >= 8; NULL if none)
|
||||
* av[8] = IP address (if ac >= 9; NULL if unknown)
|
||||
* av[9] = user modes (lf ac >= 10; NULL if unknown.
|
||||
* Leading + optional)
|
||||
* av[10..] available for protocol module use
|
||||
* Else:
|
||||
* av[1] = time of change
|
||||
* Return 1 if message was accepted, 0 if rejected (AKILL/session limit).
|
||||
*/
|
||||
|
||||
int do_nick(const char *source, int ac, char **av)
|
||||
{
|
||||
User *user;
|
||||
|
||||
if (!*source) {
|
||||
/* This is a new user; create a User structure for it. */
|
||||
|
||||
int reconnect = 0; /* Is user reconnecting after a split? */
|
||||
|
||||
log_debug(1, "new user: %s", av[0]);
|
||||
|
||||
/* We used to ignore the ~ which a lot of ircd's use to indicate no
|
||||
* identd response. That caused channel bans to break, so now we
|
||||
* just take what the server gives us. People are still encouraged
|
||||
* to read the RFCs and stop doing anything to usernames depending
|
||||
* on the result of an identd lookup. */
|
||||
|
||||
/* First check whether the user should be allowed on. */
|
||||
if (call_callback_2(cb_check, ac, av))
|
||||
return 0;
|
||||
|
||||
/* User was accepted; allocate User structure and fill it in. */
|
||||
user = new_user(av[0]);
|
||||
user->my_signon = time(NULL);
|
||||
user->signon = strtotime(av[2], NULL);
|
||||
user->username = sstrdup(av[3]);
|
||||
user->host = sstrdup(av[4]);
|
||||
user->server = get_server(av[5]);
|
||||
user->realname = sstrdup(av[6]);
|
||||
if (ac >= 8 && av[7]) {
|
||||
user->servicestamp = strtoul(av[7], NULL, 10);
|
||||
reconnect = (user->servicestamp != 0);
|
||||
} else {
|
||||
user->servicestamp = (uint32)user->signon;
|
||||
/* Unfortunately, we have no way to tell whether the user is
|
||||
* new or not */
|
||||
}
|
||||
if (ac >= 9 && av[8])
|
||||
user->ipaddr = sstrdup(av[8]);
|
||||
else
|
||||
user->ipaddr = NULL;
|
||||
#define next snext
|
||||
#define prev sprev
|
||||
if (user->server)
|
||||
LIST_INSERT(user, user->server->userlist);
|
||||
#undef next
|
||||
#undef prev
|
||||
ignore_init(user);
|
||||
|
||||
call_callback_4(cb_create, user, ac, av, reconnect);
|
||||
|
||||
if (ac >= 8 && av[7] && !user->servicestamp) {
|
||||
/* A servicestamp was provided, but it was zero, so assign one.
|
||||
* Note that we use a random value for the initial Services
|
||||
* stamp instead of the current time for the following reason:
|
||||
*
|
||||
* Suppose you have a network with an average of more than one
|
||||
* new user per second; for the sake of argument, assume there
|
||||
* are an average of 1.3 new users per second. If the initial
|
||||
* Services stamp is T, the current time, then in 100 seconds
|
||||
* (i.e. at T+100) the Services stamp will have gone to T+130.
|
||||
* (In reality, it would jump much higher on the initial net
|
||||
* burst when no users have Services stamps, but that does not
|
||||
* affect this argument.)
|
||||
*
|
||||
* If Services is now restarted, clearing the last used stamp
|
||||
* value, then assuming 5 seconds for restart, Services will
|
||||
* receive a network burst at T+105. However! While most of
|
||||
* the users will already have Services stamps, any new users
|
||||
* (as well as any users which connect after the network burst)
|
||||
* will be assigned new Services stamps starting with the
|
||||
* default value of the current time, in this case T+105. But
|
||||
* other users _already_ have Services stamp values in the
|
||||
* range T+105 to T+130 from the previous run--thus you have
|
||||
* Services stamp collisions, and all the security problems
|
||||
* that go with them.
|
||||
*
|
||||
* Obviously, this possibility does not disappear entirely by
|
||||
* using a random initial value, but it becomes much more
|
||||
* unlikely.
|
||||
*
|
||||
* Note that Unreal 3.1.1 (at least) upper-bounds values at
|
||||
* 2^31-1, so we limit ourselves to 31 bits here, even though
|
||||
* our field is unsigned.
|
||||
*/
|
||||
|
||||
static int32 servstamp = 0;
|
||||
|
||||
if (servstamp == 0)
|
||||
servstamp = (rand() & 0x7FFFFFFF) | 1;
|
||||
user->servicestamp = servstamp++;
|
||||
if (servstamp <= 0)
|
||||
servstamp = 1;
|
||||
call_callback_1(cb_servicestamp_change, user);
|
||||
}
|
||||
|
||||
if (ac >= 10 && av[9] && *av[9]) {
|
||||
/* Apply modes supplied in av[9]. Current protocol modules all
|
||||
* include a '+' before the mode letters, but allow strings
|
||||
* without the '+' for robustness. */
|
||||
char buf[BUFSIZE];
|
||||
char *newav[2];
|
||||
newav[0] = user->nick;
|
||||
if (*av[9] == '+') {
|
||||
newav[1] = av[9];
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "+%s", av[9]);
|
||||
newav[1] = buf;
|
||||
}
|
||||
do_umode(user->nick, 2, newav);
|
||||
}
|
||||
|
||||
} else {
|
||||
/* An old user changing nicks. */
|
||||
char oldnick[NICKMAX];
|
||||
|
||||
user = get_user(source);
|
||||
if (!user) {
|
||||
log_debug(1, "user: NICK from nonexistent nick %s: %s",
|
||||
source, merge_args(ac, av));
|
||||
return 0;
|
||||
}
|
||||
log_debug(1, "%s changes nick to %s", source, av[0]);
|
||||
|
||||
strbcpy(oldnick, user->nick);
|
||||
call_callback_2(cb_nickchange1, user, av[0]);
|
||||
/* Flush out all mode changes; necessary to avoid desynch (otherwise
|
||||
* we can't find the user when the mode goes out later). The IRC
|
||||
* servers will take care of translating the old nick to the new one */
|
||||
set_cmode(NULL, NULL);
|
||||
change_user_nick(user, av[0]);
|
||||
call_callback_2(cb_nickchange2, user, oldnick);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a JOIN command.
|
||||
* av[0] = channels to join
|
||||
*/
|
||||
|
||||
void do_join(const char *source, int ac, char **av)
|
||||
{
|
||||
User *user;
|
||||
char *s, *t;
|
||||
|
||||
user = get_user(source);
|
||||
if (!user) {
|
||||
log_debug(1, "user: JOIN from nonexistent user %s: %s",
|
||||
source, merge_args(ac, av));
|
||||
return;
|
||||
}
|
||||
t = av[0];
|
||||
while (*(s=t)) {
|
||||
t = s + strcspn(s, ",");
|
||||
if (*t)
|
||||
*t++ = 0;
|
||||
log_debug(1, "%s joins %s", source, s);
|
||||
|
||||
if (*s == '0')
|
||||
part_all_channels(user);
|
||||
else
|
||||
join_channel(user, s, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a PART command.
|
||||
* av[0] = channels to leave
|
||||
* av[1] = reason (optional)
|
||||
*/
|
||||
|
||||
void do_part(const char *source, int ac, char **av)
|
||||
{
|
||||
User *user;
|
||||
char *s, *t;
|
||||
|
||||
user = get_user(source);
|
||||
if (!user) {
|
||||
log_debug(1, "user: PART from nonexistent user %s: %s",
|
||||
source, merge_args(ac, av));
|
||||
return;
|
||||
}
|
||||
t = av[0];
|
||||
while (*(s=t)) {
|
||||
t = s + strcspn(s, ",");
|
||||
if (*t)
|
||||
*t++ = 0;
|
||||
log_debug(1, "%s leaves %s", source, s);
|
||||
if (!part_channel(user, s, cb_chan_part, av[1], source)) {
|
||||
log("user: do_part: no channel record for %s on %s (bug?)",
|
||||
user->nick, av[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a KICK command.
|
||||
* av[0] = channel
|
||||
* av[1] = nick(s) being kicked
|
||||
* av[2] = reason
|
||||
* When called internally to remove a single user (no "," in av[1]) from a
|
||||
* channel, callers may assume that the contents of the argument strings
|
||||
* will not be modified.
|
||||
*/
|
||||
|
||||
void do_kick(const char *source, int ac, char **av)
|
||||
{
|
||||
User *user;
|
||||
char *s, *t;
|
||||
|
||||
t = av[1];
|
||||
while (*(s=t)) {
|
||||
t = s + strcspn(s, ",");
|
||||
if (*t)
|
||||
*t++ = 0;
|
||||
user = get_user(s);
|
||||
if (!user) {
|
||||
log_debug(1, "user: KICK for nonexistent user %s on %s: %s",
|
||||
s, av[0], merge_args(ac-2, av+2));
|
||||
continue;
|
||||
}
|
||||
log_debug(1, "kicking %s from %s", s, av[0]);
|
||||
if (!part_channel(user, av[0], cb_chan_kick, av[2], source)) {
|
||||
log("user: do_kick: no channel record for %s on %s (bug?)",
|
||||
user->nick, av[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a MODE command for a user.
|
||||
* av[0] = nick to change mode for
|
||||
* av[1] = modes
|
||||
*/
|
||||
|
||||
void do_umode(const char *source, int ac, char **av)
|
||||
{
|
||||
User *user;
|
||||
char *modestr, *s;
|
||||
int add = 1; /* 1 if adding modes, 0 if deleting */
|
||||
|
||||
user = get_user(av[0]);
|
||||
if (!user) {
|
||||
log_debug(1, "user: MODE %s for nonexistent nick %s from %s: %s",
|
||||
av[1], av[0], source, merge_args(ac, av));
|
||||
return;
|
||||
}
|
||||
log_debug(1, "Changing mode for %s to %s", av[0], av[1]);
|
||||
modestr = s = av[1];
|
||||
av += 2;
|
||||
ac -= 2;
|
||||
|
||||
while (*s) {
|
||||
char modechar = *s++;
|
||||
int32 flag;
|
||||
int params;
|
||||
|
||||
if (modechar == '+') {
|
||||
add = 1;
|
||||
continue;
|
||||
} else if (modechar == '-') {
|
||||
add = 0;
|
||||
continue;
|
||||
} else if (add < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
flag = mode_char_to_flag(modechar, MODE_USER);
|
||||
if (!flag)
|
||||
continue;
|
||||
if (flag == MODE_INVALID)
|
||||
flag = 0;
|
||||
params = mode_char_to_params(modechar, MODE_USER);
|
||||
params = (params >> (add*8)) & 0xFF;
|
||||
if (ac < params) {
|
||||
log("user: MODE %s %s: missing parameter(s) for %c%c",
|
||||
user->nick, modestr, add ? '+' : '-', modechar);
|
||||
break;
|
||||
}
|
||||
|
||||
if (call_callback_4(cb_mode, user, modechar, add, av) <= 0) {
|
||||
if (modechar == 'o') {
|
||||
if (add)
|
||||
opcnt++;
|
||||
else
|
||||
opcnt--;
|
||||
}
|
||||
if (add)
|
||||
user->mode |= flag;
|
||||
else
|
||||
user->mode &= ~flag;
|
||||
}
|
||||
av += params;
|
||||
ac -= params;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a QUIT command.
|
||||
* av[0] = reason
|
||||
* When called internally, callers may assume that the contents of the
|
||||
* argument string will not be modified.
|
||||
*/
|
||||
|
||||
void do_quit(const char *source, int ac, char **av)
|
||||
{
|
||||
User *user;
|
||||
|
||||
user = get_user(source);
|
||||
if (!user) {
|
||||
log_debug(1, "user: QUIT from nonexistent user %s: %s",
|
||||
source, merge_args(ac, av));
|
||||
return;
|
||||
}
|
||||
log_debug(1, "%s quits", source);
|
||||
quit_user(user, av[0], 0);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Handle a KILL command.
|
||||
* av[0] = nick being killed
|
||||
* av[1] = reason
|
||||
* When called internally, callers may assume that the contents of the
|
||||
* argument strings will not be modified.
|
||||
*/
|
||||
|
||||
void do_kill(const char *source, int ac, char **av)
|
||||
{
|
||||
User *user;
|
||||
|
||||
user = get_user(av[0]);
|
||||
if (!user)
|
||||
return;
|
||||
log_debug(1, "%s killed", av[0]);
|
||||
quit_user(user, av[1], 1);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Join a user to a channel. Return a pointer to the channel record if the
|
||||
* join succeeded, NULL otherwise.
|
||||
*/
|
||||
|
||||
Channel *join_channel(User *user, const char *channel, int32 modes)
|
||||
{
|
||||
Channel *c = chan_adduser(user, channel, modes);
|
||||
struct u_chanlist *uc;
|
||||
|
||||
if (!c)
|
||||
return NULL;
|
||||
uc = smalloc(sizeof(*uc));
|
||||
LIST_INSERT(uc, user->chans);
|
||||
uc->chan = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Part a user from a channel. */
|
||||
|
||||
int part_channel(User *user, const char *channel, int callback,
|
||||
const char *param, const char *source)
|
||||
{
|
||||
struct u_chanlist *uc;
|
||||
LIST_SEARCH(user->chans, chan->name, channel, irc_stricmp, uc);
|
||||
if (uc)
|
||||
part_channel_uc(user, uc, callback, param, source);
|
||||
return uc != NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Part a user from all channels s/he is in. Assumes cb_chan_part, an
|
||||
* empty `param' string, and the user as source. */
|
||||
|
||||
void part_all_channels(User *user)
|
||||
{
|
||||
struct u_chanlist *uc, *nextuc;
|
||||
LIST_FOREACH_SAFE (uc, user->chans, nextuc)
|
||||
part_channel_uc(user, uc, cb_chan_part, "", user->nick);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Various check functions. All of these return false/NULL if any
|
||||
* parameter is NULL. */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Is the given user an oper? */
|
||||
|
||||
int is_oper(const User *user)
|
||||
{
|
||||
return user != NULL && (user->mode & UMODE_o);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Is the given user on the given channel? Return the Channel * for the
|
||||
* channel if so, NULL if not.
|
||||
*/
|
||||
|
||||
Channel *is_on_chan(const User *user, const char *chan)
|
||||
{
|
||||
struct u_chanlist *c;
|
||||
|
||||
if (!user || !chan)
|
||||
return NULL;
|
||||
LIST_SEARCH(user->chans, chan->name, chan, irc_stricmp, c);
|
||||
return c ? c->chan : NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Is the given user a channel operator on the given channel? */
|
||||
|
||||
int is_chanop(const User *user, const char *chan)
|
||||
{
|
||||
Channel *c = chan ? get_channel(chan) : NULL;
|
||||
struct c_userlist *cu;
|
||||
|
||||
if (!user || !chan || !c)
|
||||
return 0;
|
||||
LIST_SEARCH(c->users, user->nick, user->nick, irc_stricmp, cu);
|
||||
return cu != NULL && (cu->mode & CUMODE_o) != 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Is the given user voiced (channel mode +v) on the given channel? */
|
||||
|
||||
int is_voiced(const User *user, const char *chan)
|
||||
{
|
||||
Channel *c = chan ? get_channel(chan) : NULL;
|
||||
struct c_userlist *cu;
|
||||
|
||||
if (!user || !chan || !c)
|
||||
return 0;
|
||||
LIST_SEARCH(c->users, user->nick, user->nick, irc_stricmp, cu);
|
||||
return cu != NULL && (cu->mode & CUMODE_v) != 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Does the user's usermask match the given mask? The mask may be in
|
||||
* either nick!user@host or just user@host form. When "fakehosts" or IP
|
||||
* addresses are available in the user record, they are also checked
|
||||
* against the "host" part of the mask, and a match by any field (real
|
||||
* host, fakehost, or IP address) is treated as a match for the host part.
|
||||
* Note that CIDR matching on IP addresses is _not_ performed.
|
||||
*/
|
||||
|
||||
int match_usermask(const char *mask, const User *user)
|
||||
{
|
||||
char *mask2;
|
||||
char *nick, *username, *host;
|
||||
int match_user, match_host, result;
|
||||
|
||||
if (!mask || !user) {
|
||||
log_debug(1, "match_usermask: NULL %s!", !mask ? "mask" : "user");
|
||||
return 0;
|
||||
}
|
||||
mask2 = sstrdup(mask);
|
||||
if (strchr(mask2, '!')) {
|
||||
nick = strtok(mask2, "!");
|
||||
username = strtok(NULL, "@");
|
||||
} else {
|
||||
nick = NULL;
|
||||
username = strtok(mask2, "@");
|
||||
}
|
||||
host = strtok(NULL, "");
|
||||
if (!host) {
|
||||
free(mask2);
|
||||
return 0;
|
||||
}
|
||||
match_user = match_wild_nocase(username, user->username);
|
||||
match_host = match_wild_nocase(host, user->host);
|
||||
if (user->fakehost)
|
||||
match_host |= match_wild_nocase(host, user->fakehost);
|
||||
if (user->ipaddr)
|
||||
match_host |= match_wild_nocase(host, user->ipaddr);
|
||||
if (nick) {
|
||||
result = match_wild_nocase(nick, user->nick) &&
|
||||
match_user && match_host;
|
||||
} else {
|
||||
result = match_user && match_host;
|
||||
}
|
||||
free(mask2);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Split a usermask up into its constitutent parts. Returned strings are
|
||||
* malloc()'d, and should be free()'d when done with. Returns "*" for
|
||||
* missing parts. Assumes `mask' is a non-empty string.
|
||||
*/
|
||||
|
||||
void split_usermask(const char *mask, char **nick, char **user, char **host)
|
||||
{
|
||||
char *mask2 = sstrdup(mask);
|
||||
char *mynick, *myuser, *myhost;
|
||||
|
||||
mynick = mask2;
|
||||
myuser = strchr(mask2, '!');
|
||||
myhost = myuser ? strchr(myuser, '@') : NULL;
|
||||
if (myuser)
|
||||
*myuser++ = 0;
|
||||
if (myhost)
|
||||
*myhost++ = 0;
|
||||
/* Handle special case: mask == user@host */
|
||||
if (mynick && !myuser && strchr(mynick, '@')) {
|
||||
mynick = NULL;
|
||||
myuser = mask2;
|
||||
myhost = strchr(mask2, '@');
|
||||
if (myhost) /* Paranoia */
|
||||
*myhost++ = 0;
|
||||
}
|
||||
if (!mynick || !*mynick)
|
||||
mynick = (char *)"*";
|
||||
if (!myuser || !*myuser)
|
||||
myuser = (char *)"*";
|
||||
if (!myhost || !*myhost)
|
||||
myhost = (char *)"*";
|
||||
*nick = sstrdup(mynick);
|
||||
*user = sstrdup(myuser);
|
||||
*host = sstrdup(myhost);
|
||||
free(mask2);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Given a user, return a mask that will most likely match any address the
|
||||
* user will have from that location. For IP addresses, wildcards the last
|
||||
* octet of the address (e.g. 10.1.1.1 -> 10.1.1.*); for named addresses,
|
||||
* wildcards the leftmost part of the name unless the name only contains
|
||||
* two parts. The returned character string is malloc'd and should be
|
||||
* free'd when done with.
|
||||
*
|
||||
* Where supported, uses the fake host instead of the real one if
|
||||
* use_fakehost is nonzero.
|
||||
*/
|
||||
|
||||
char *create_mask(const User *user, int use_fakehost)
|
||||
{
|
||||
char *mask, *s, *end, *host;
|
||||
|
||||
host = user->host;
|
||||
if (use_fakehost && user->fakehost)
|
||||
host = user->fakehost;
|
||||
/* Get us a buffer the size of the username plus hostname. The result
|
||||
* will never be longer than this (and will often be shorter), thus we
|
||||
* can use strcpy() and sprintf() safely.
|
||||
*/
|
||||
end = mask = smalloc(strlen(user->username) + strlen(host) + 2);
|
||||
end += sprintf(end, "%s@", user->username);
|
||||
if (strspn(host, "0123456789.") == strlen(host)
|
||||
&& (s = strchr(host, '.'))
|
||||
&& (s = strchr(s+1, '.'))
|
||||
&& (s = strchr(s+1, '.'))
|
||||
&& ( !strchr(s+1, '.'))) { /* IP addr */
|
||||
s = sstrdup(host);
|
||||
*strrchr(s, '.') = 0;
|
||||
sprintf(end, "%s.*", s);
|
||||
free(s);
|
||||
} else {
|
||||
if ((s = strchr(host+1, '.')) && strchr(s+1, '.')) {
|
||||
s = sstrdup(s-1);
|
||||
*s = '*';
|
||||
} else {
|
||||
s = sstrdup(host);
|
||||
}
|
||||
strcpy(end, s); /* safe: see above */
|
||||
free(s);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
|
||||
/* Create a new guest nick using GuestNickPrefix and a unique series of
|
||||
* digits, and return it. The returned nick is stored in a static buffer
|
||||
* and will be overwritten at the next call.
|
||||
*
|
||||
* At present, we simply use a rollover counter attached to the nick,
|
||||
* initialized to a random value. This provides for uniqueness as long as
|
||||
* Services is not restarted and a reasonable chance of uniqueness even
|
||||
* when Services is restarted (unless you have a long prefix and a short
|
||||
* maximum nick length; however, this routine will make sure at least 4
|
||||
* digits are available, possibly by shortening the prefix).
|
||||
*
|
||||
* Note that initializing the counter based on the current time would be a
|
||||
* bad idea, since if more than one user per second (on average) connected
|
||||
* to the network, duplicate nicks would be almost guaranteed if Services
|
||||
* restarted.
|
||||
*/
|
||||
|
||||
char *make_guest_nick(void)
|
||||
{
|
||||
static char nickbuf[NICKMAX+1]; /* +1 to check for overrun */
|
||||
static uint32 counter = 0; /* Unique suffix counter */
|
||||
int tries; /* Tries to find an unused nick */
|
||||
int prefixlen; /* Length of nick prefix */
|
||||
uint32 suffixmod; /* Modulo for suffix counter */
|
||||
int i;
|
||||
|
||||
/* Sanity checks on nick prefix length */
|
||||
prefixlen = strlen(GuestNickPrefix);
|
||||
if (protocol_nickmax <= 4) {
|
||||
/* This violates RFC1459 as well as common sense, so just blow
|
||||
* ourselves out of the water. */
|
||||
fatal("make_guest_nick(): protocol_nickmax too small (%d)",
|
||||
protocol_nickmax);
|
||||
} else if (prefixlen+4 > protocol_nickmax) {
|
||||
/* Reserve at least 4 digits for the suffix */
|
||||
prefixlen = protocol_nickmax-4;
|
||||
log("warning: make_guest_nick(): GuestNickPrefix too long,"
|
||||
" shortening to %d characters", prefixlen);
|
||||
GuestNickPrefix[prefixlen] = 0;
|
||||
}
|
||||
|
||||
/* Calculate number of digits available for suffix -> suffix modulo */
|
||||
i = protocol_nickmax - prefixlen;
|
||||
if (i < 10) {
|
||||
suffixmod = 1;
|
||||
while (i-- > 0)
|
||||
suffixmod *= 10;
|
||||
} else {
|
||||
suffixmod = 0; /* no modulo */
|
||||
}
|
||||
|
||||
/* Actually generate the nick. If the nick already exists, generate a
|
||||
* new one, and repeat until finding an unused nick or trying too many
|
||||
* times. If we try too many times, we just kill the last one we end
|
||||
* up with. */
|
||||
tries = 0;
|
||||
for (;;) {
|
||||
if (counter == 0) /* initialize to random the first time */
|
||||
counter = rand();
|
||||
if (suffixmod) /* strip down to right number of digits */
|
||||
counter %= suffixmod;
|
||||
if (counter == 0) /* if rand() gave us 0 or N*suffixmod... */
|
||||
counter = 1; /* ... use 1 instead */
|
||||
i = snprintf(nickbuf, sizeof(nickbuf), "%s%u", GuestNickPrefix, counter);
|
||||
if (i > protocol_nickmax) {
|
||||
log("BUG: make_guest_nick() generated %s but nickmax == %d!",
|
||||
nickbuf, protocol_nickmax);
|
||||
nickbuf[protocol_nickmax] = 0;
|
||||
}
|
||||
if (!get_user(nickbuf)) /* done if user not found */
|
||||
break;
|
||||
/* Increment try count, and break out if it's too much */
|
||||
if (++tries >= MAKEGUESTNICK_TRIES) {
|
||||
kill_user(NULL, nickbuf, "Guest nicks may not be used");
|
||||
break;
|
||||
}
|
||||
/* Reset counter to 0 to use a new random number */
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
/* Increment the unique suffix counter modulo suffixmod, and avoid 0
|
||||
* (which would cause a reset to a random value) */
|
||||
counter++;
|
||||
if (counter == suffixmod) /* because suffixmod==0 if no modulo */
|
||||
counter = 1;
|
||||
|
||||
/* Return the nick */
|
||||
return nickbuf;
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Return nonzero if the given nickname can potentially be a guest nickname
|
||||
* (i.e. a nickname that could be generated by make_guest_nick()), zero if
|
||||
* not. Currently this means a nickname consisting of GuestNickPrefix
|
||||
* followed by between 1 and 10 digits inclusive.
|
||||
*/
|
||||
|
||||
int is_guest_nick(const char *nick)
|
||||
{
|
||||
int prefixlen = strlen(GuestNickPrefix);
|
||||
int nicklen = strlen(nick);
|
||||
return nicklen >= prefixlen+1 && nicklen <= prefixlen+10
|
||||
&& irc_strnicmp(nick, GuestNickPrefix, prefixlen) == 0
|
||||
&& strspn(nick+prefixlen, "1234567890") == nicklen-prefixlen;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
82
users.h
Normal file
82
users.h
Normal file
@ -0,0 +1,82 @@
|
||||
/* Online user data structure.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#ifndef USERS_H
|
||||
#define USERS_H
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
struct user_ {
|
||||
User *next, *prev;
|
||||
User *snext,*sprev; /* Next/previous for the user's server
|
||||
* (unordered) */
|
||||
|
||||
char nick[NICKMAX];
|
||||
NickInfo *ni; /* NickInfo for this nick */
|
||||
NickGroupInfo *ngi; /* NickGroupInfo for this nick; guaranteed
|
||||
* valid if `ni' is non-NULL */
|
||||
Server *server; /* Server user is on */
|
||||
char *username;
|
||||
char *host; /* User's hostname */
|
||||
char *realname;
|
||||
char *fakehost; /* Hostname seen by other users */
|
||||
char *ipaddr; /* User's IP address in string form (takes
|
||||
* more memory but saves CPU work and
|
||||
* gives free compatibility with IPv6) */
|
||||
|
||||
time_t signon; /* Timestamp sent (from remote server) with
|
||||
* user when they connected */
|
||||
time_t my_signon; /* Our local timestamp when the user
|
||||
* connected */
|
||||
uint32 servicestamp; /* ID value for user; used in split recovery */
|
||||
int32 mode; /* UMODE_* user modes */
|
||||
int16 flags; /* UF_* status flags */
|
||||
|
||||
double ignore; /* Ignore level */
|
||||
uint32 lastcmd; /* Time of last command, from time_msec() */
|
||||
time_t lastcmd_s; /* Same from time(), to check rollover */
|
||||
|
||||
int16 bad_pw_count; /* # of invalid password attempts */
|
||||
time_t bad_pw_time; /* Time of last invalid password */
|
||||
time_t lastmemosend; /* Last time MS SEND command used */
|
||||
time_t lastmemofwd; /* Last time MS FORWARD command used */
|
||||
time_t lastnickreg; /* Last time NS REGISTER command used */
|
||||
time_t last_nick_set_email; /* Last time NS SET EMAIL command used */
|
||||
|
||||
uint32 *id_nicks;
|
||||
int id_nicks_count;
|
||||
|
||||
struct u_chanlist { /* Channels user has joined */
|
||||
struct u_chanlist *next, *prev;
|
||||
Channel *chan;
|
||||
} *chans;
|
||||
|
||||
struct u_chaninfolist { /* Channels user has identified for */
|
||||
struct u_chaninfolist *next, *prev;
|
||||
char chan[CHANMAX];
|
||||
} *id_chans;
|
||||
};
|
||||
|
||||
|
||||
/* Status flags: */
|
||||
#define UF_SERVROOT 0x0001 /* User has Services root privileges */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#endif /* USERS_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
23
version.h
Normal file
23
version.h
Normal file
@ -0,0 +1,23 @@
|
||||
/* External declarations for version.c.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
extern const char program_name[];
|
||||
extern const char version_number[];
|
||||
extern const char version_build[];
|
||||
extern const char *info_text[];
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
80
version.sh
Normal file
80
version.sh
Normal file
@ -0,0 +1,80 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Build the version.c file which contains all the version related info and
|
||||
# needs to be updated on a per-build basis.
|
||||
|
||||
# $PROGRAM is the string returned as the first part of a /VERSION reply,
|
||||
# and must not contain spaces. It is not used anywhere else.
|
||||
PROGRAM=ircservices
|
||||
VERSION=5.1.24
|
||||
|
||||
# Increment Services build number
|
||||
if [ -f version.c ]; then
|
||||
BUILD=`fgrep '#define BUILD' version.c | sed 's/^#define BUILD.*"\([0-9]*\)".*$/\1/'`
|
||||
BUILD=`expr $BUILD + 1 2>/dev/null`
|
||||
else
|
||||
BUILD=1
|
||||
fi
|
||||
if [ ! "$BUILD" ]; then
|
||||
BUILD=1
|
||||
fi
|
||||
|
||||
DATE=`date`
|
||||
if [ $? -ne "0" ]; then
|
||||
DATE="\" __DATE__ \" \" __TIME__ \""
|
||||
fi
|
||||
|
||||
cat >version.c <<EOF
|
||||
/* Version information for IRC Services.
|
||||
* Note: this file is automatically generated.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#define BUILD "$BUILD"
|
||||
|
||||
const char program_name[] = "$PROGRAM";
|
||||
const char version_number[] = "$VERSION";
|
||||
const char version_build[] = "build #" BUILD ", compiled $DATE";
|
||||
|
||||
|
||||
/* Look folks, please leave this INFO reply intact and unchanged. If you do
|
||||
* have the urge to mention yourself, please simply add your name to the list.
|
||||
* The other people listed below have just as much right, if not more, to be
|
||||
* mentioned. Leave everything else untouched. Thanks.
|
||||
*/
|
||||
|
||||
const char *info_text[] = {
|
||||
"IRC Services developed by and copyright (c) 1996-2009",
|
||||
"Andrew Church <achurch@achurch.org>.",
|
||||
"Parts written by Andrew Kempe and others.",
|
||||
"IRC Services may be freely redistributed under the GNU",
|
||||
"General Public License, version 2 or later.",
|
||||
" ",
|
||||
"Many people have contributed to the ongoing development of",
|
||||
"IRC Services. Particularly noteworthy contributors include:",
|
||||
" Mauritz Antunes",
|
||||
" Holger Baust",
|
||||
" Elijah",
|
||||
" Yusuf Iskenderoglu",
|
||||
" Janos Kapitany",
|
||||
" Jacek Margos",
|
||||
" Craig McLure",
|
||||
" Martin Pels",
|
||||
" Krisztian Romek",
|
||||
" Alexander Zverev",
|
||||
"A full list of contributors and their contributions can be",
|
||||
"found in the manual and the Changes file included in the IRC",
|
||||
"Services distribution archive. Many thanks to all of them!",
|
||||
" ",
|
||||
"For more information and a list of distribution sites,",
|
||||
"please visit: http://www.ircservices.za.net/",
|
||||
0
|
||||
};
|
||||
EOF
|
557
vsnprintf.c
Normal file
557
vsnprintf.c
Normal file
@ -0,0 +1,557 @@
|
||||
/* An implementation of vsnprintf() for systems that don't have it.
|
||||
*
|
||||
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
||||
* E-mail: <achurch@achurch.org>
|
||||
* Parts written by Andrew Kempe and others.
|
||||
* This program is free but copyrighted software; see the file GPL.txt for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
typedef int
|
||||
(*_pfmt_writefunc_t)(const char *buf, size_t len, void *arg1, void *arg2);
|
||||
|
||||
int my_vsnprintf(char *string, size_t size, const char *format, va_list args);
|
||||
int my_snprintf(char *string, size_t size, const char *format, ...);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/* Basic format routine for *printf() interfaces. Takes a writing function
|
||||
* and two (optional) pointer parameters for that function which are passed
|
||||
* on unmodified. The function should return the number of bytes written,
|
||||
* or 0 (not -1!) on write failure.
|
||||
*/
|
||||
|
||||
static int _pfmt(const char *format, va_list args,
|
||||
_pfmt_writefunc_t writefunc, void *arg1, void *arg2)
|
||||
{
|
||||
int total = 0; /* Total bytes written */
|
||||
const char *startptr;/* Beginning of non-token text in format string.
|
||||
* Used for writing in bulk instead of
|
||||
* character-at-a-time. */
|
||||
int n; /* Bytes written in last writefunc() call */
|
||||
int valid; /* Was this a valid %-token? */
|
||||
int alt_form; /* "Alternate form"? (# flag) */
|
||||
int zero_pad; /* Zero-pad value? */
|
||||
int left_justify; /* Left-justify? (0 means right-justify) */
|
||||
int always_sign; /* Always add sign value? */
|
||||
int insert_blank; /* Insert blank before positive values for %d/%i? */
|
||||
int width; /* Field width */
|
||||
int precision; /* Precision */
|
||||
int argsize; /* Size of argument: 0=normal, 1=short, 2=long,
|
||||
* 3=long long */
|
||||
int what; /* What are we working on? 0=flags, 1=width,
|
||||
* 2=precision, 3=argsize, 4=argtype */
|
||||
long intval; /* Integer value */
|
||||
char *strval; /* String value */
|
||||
void *ptrval; /* Pointer value */
|
||||
char numbuf[64]; /* Temporary buffer for printing numbers; this MUST
|
||||
* be large enough to hold the longest possible
|
||||
* number (size is not checked in processing) */
|
||||
char *numptr; /* Pointer to start of printed number in `numbuf' */
|
||||
|
||||
|
||||
intval = 0;
|
||||
strval = NULL;
|
||||
ptrval = NULL;
|
||||
|
||||
startptr = format;
|
||||
while (*format) {
|
||||
if (*format != '%') {
|
||||
format++;
|
||||
continue;
|
||||
}
|
||||
if (startptr != format) {
|
||||
/* Write out accumulated text */
|
||||
n = writefunc(startptr, format-startptr, arg1, arg2);
|
||||
total += n;
|
||||
/* Abort on short write */
|
||||
if (n != format-startptr)
|
||||
break;
|
||||
/* Point to this token, in case it's a bad one */
|
||||
startptr = format;
|
||||
}
|
||||
|
||||
/* Begin %-token processing */
|
||||
valid = 0; /* 1 if valid, -1 if known not valid (syntax error) */
|
||||
alt_form = 0;
|
||||
left_justify = 0;
|
||||
always_sign = 0;
|
||||
insert_blank = 0;
|
||||
zero_pad = 0;
|
||||
width = -1;
|
||||
precision = -1;
|
||||
argsize = 0;
|
||||
what = 0;
|
||||
|
||||
while (!valid && *++format) { /* Broken out of by terminal chars */
|
||||
switch (*format) {
|
||||
|
||||
/* Flags */
|
||||
case '#':
|
||||
if (what != 0) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
alt_form = 1;
|
||||
break;
|
||||
case '-':
|
||||
if (what != 0) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
left_justify = 1;
|
||||
break;
|
||||
case '+':
|
||||
if (what != 0) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
always_sign = 1;
|
||||
break;
|
||||
case ' ':
|
||||
if (what != 0) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
insert_blank = 1;
|
||||
break;
|
||||
case '\'':
|
||||
/* Comma-grouping by locale; not supported */
|
||||
valid = -1;
|
||||
break;
|
||||
case '0':
|
||||
if (what == 0) {
|
||||
zero_pad = 1;
|
||||
break;
|
||||
}
|
||||
/* else fall through */
|
||||
|
||||
/* Field widths */
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
if (what == 0)
|
||||
what = 1;
|
||||
else if (what > 2) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
if (what == 1) {
|
||||
if (width < 0)
|
||||
width = 0;
|
||||
width = width*10 + (*format)-'0';
|
||||
} else {
|
||||
if (precision < 0)
|
||||
precision = 0;
|
||||
precision = precision*10 + (*format)-'0';
|
||||
}
|
||||
break;
|
||||
case '*':
|
||||
if (what == 0)
|
||||
what = 1;
|
||||
else if (what > 2) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
if (what == 1) {
|
||||
width = va_arg(args, int);
|
||||
if (width < 0) {
|
||||
width = -width;
|
||||
left_justify = 1;
|
||||
}
|
||||
} else {
|
||||
precision = va_arg(args, int);
|
||||
}
|
||||
break;
|
||||
case '.':
|
||||
if (what >= 2) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
what = 2;
|
||||
break;
|
||||
|
||||
/* Argument sizes */
|
||||
case 'h':
|
||||
if (what > 3) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
argsize = 1;
|
||||
what = 4;
|
||||
break;
|
||||
case 'l':
|
||||
if (what > 3) {
|
||||
valid = -1;
|
||||
break;
|
||||
}
|
||||
argsize = 2;
|
||||
what = 4;
|
||||
break;
|
||||
case 'L':
|
||||
/* Long long (64 bits); not supported */
|
||||
valid = -1;
|
||||
break;
|
||||
/* Argument types */
|
||||
case 'd':
|
||||
case 'i':
|
||||
case 'o':
|
||||
case 'u':
|
||||
case 'x':
|
||||
case 'X':
|
||||
if (argsize == 1)
|
||||
intval = va_arg(args, int);
|
||||
else if (argsize == 2)
|
||||
intval = va_arg(args, long);
|
||||
else
|
||||
intval = va_arg(args, int);
|
||||
valid = 1;
|
||||
break;
|
||||
case 'c':
|
||||
intval = va_arg(args, int);
|
||||
valid = 1;
|
||||
break;
|
||||
case 's':
|
||||
strval = va_arg(args, char *);
|
||||
valid = 1;
|
||||
break;
|
||||
case 'p':
|
||||
ptrval = va_arg(args, void *);
|
||||
valid = 1;
|
||||
break;
|
||||
case 'n':
|
||||
*((int *)va_arg(args, int *)) = total;
|
||||
valid = 1;
|
||||
break;
|
||||
case '%':
|
||||
valid = 1;
|
||||
break;
|
||||
|
||||
/* All other characters */
|
||||
default:
|
||||
valid = -1;
|
||||
break;
|
||||
} /* switch (*format) */
|
||||
}
|
||||
if (valid != 1) {
|
||||
/* Not a valid %-token; start loop over (token will get printed
|
||||
* out next time through). */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Don't zero-pad if a precision was given or left-justifying */
|
||||
if (precision != -1 || left_justify)
|
||||
zero_pad = 0;
|
||||
|
||||
/* For numbers, limit precision to the size of the print buffer */
|
||||
if ((*format=='d' || *format=='i' || *format=='o' || *format=='u'
|
||||
|| *format=='x' || *format=='X')
|
||||
&& precision > (signed) sizeof(numbuf))
|
||||
{
|
||||
precision = sizeof(numbuf);
|
||||
}
|
||||
|
||||
switch (*format++) { /* Do something with this token */
|
||||
case '%':
|
||||
(*writefunc)(format-1, 1, arg1, arg2);
|
||||
total++;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
/* Print the NULL value specially */
|
||||
if (ptrval == NULL) {
|
||||
strval = "(null)";
|
||||
goto handle_string;
|
||||
}
|
||||
/* For all other values, pretend it's really %#.8x */
|
||||
alt_form = 1;
|
||||
zero_pad = 0;
|
||||
precision = 8;
|
||||
intval = (long) ptrval;
|
||||
/* Fall through */
|
||||
|
||||
case 'x':
|
||||
case 'X': {
|
||||
static const char x_chars[] = "0123456789abcdef0x";
|
||||
static const char X_chars[] = "0123456789ABCDEF0X";
|
||||
const char *chars = (format[-1]=='X') ? X_chars : x_chars;
|
||||
const char *padstr = zero_pad ? "0" : " ";
|
||||
unsigned long uintval;
|
||||
int len;
|
||||
|
||||
uintval = (unsigned long) intval;
|
||||
if (alt_form && uintval != 0) {
|
||||
n = writefunc(chars+16, 2, arg1, arg2);
|
||||
total += n;
|
||||
if (n != 2)
|
||||
break;
|
||||
width -= 2;
|
||||
}
|
||||
if (precision < 1)
|
||||
precision = 1;
|
||||
numptr = numbuf + sizeof(numbuf);
|
||||
for (len = 0; len < precision || uintval != 0; len++) {
|
||||
*--numptr = chars[uintval%16];
|
||||
uintval /= 16;
|
||||
}
|
||||
if (left_justify) {
|
||||
n = writefunc(numptr, len, arg1, arg2);
|
||||
total += n;
|
||||
if (n != len)
|
||||
break;
|
||||
}
|
||||
while (len < width) {
|
||||
if (1 != writefunc(padstr, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
width--;
|
||||
}
|
||||
if (!left_justify)
|
||||
total += writefunc(numptr, len, arg1, arg2);
|
||||
break;
|
||||
} /* case 'x', 'X' */
|
||||
|
||||
case 'o': {
|
||||
const char *padstr = zero_pad ? "0" : " ";
|
||||
unsigned long uintval;
|
||||
int len;
|
||||
|
||||
uintval = (unsigned long) intval;
|
||||
if (precision < 1)
|
||||
precision = 1;
|
||||
numptr = numbuf + sizeof(numbuf);
|
||||
for (len = 0; len < precision || uintval != 0; len++) {
|
||||
*--numptr = '0' + uintval%8;
|
||||
uintval /= 8;
|
||||
}
|
||||
if (alt_form && *numptr != '0') {
|
||||
*--numptr = '0';
|
||||
len++;
|
||||
}
|
||||
if (left_justify) {
|
||||
n = writefunc(numptr, len, arg1, arg2);
|
||||
total += n;
|
||||
if (n != len)
|
||||
break;
|
||||
}
|
||||
while (len < width) {
|
||||
if (1 != writefunc(padstr, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
width--;
|
||||
}
|
||||
if (!left_justify)
|
||||
total += writefunc(numptr, len, arg1, arg2);
|
||||
break;
|
||||
} /* case 'o' */
|
||||
|
||||
case 'u': {
|
||||
const char *padstr = zero_pad ? "0" : " ";
|
||||
unsigned long uintval;
|
||||
int len;
|
||||
|
||||
uintval = (unsigned long) intval;
|
||||
if (precision < 1)
|
||||
precision = 1;
|
||||
numptr = numbuf + sizeof(numbuf);
|
||||
for (len = 0; len < precision || uintval != 0; len++) {
|
||||
*--numptr = '0' + uintval%10;
|
||||
uintval /= 10;
|
||||
}
|
||||
if (left_justify) {
|
||||
n = writefunc(numptr, len, arg1, arg2);
|
||||
total += n;
|
||||
if (n != len)
|
||||
break;
|
||||
}
|
||||
while (len < width) {
|
||||
if (1 != writefunc(padstr, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
width--;
|
||||
}
|
||||
if (!left_justify)
|
||||
total += writefunc(numptr, len, arg1, arg2);
|
||||
break;
|
||||
} /* case 'u' */
|
||||
|
||||
case 'd':
|
||||
case 'i': {
|
||||
const char *padstr = zero_pad ? "0" : " ";
|
||||
int len;
|
||||
char sign_char;
|
||||
|
||||
numptr = numbuf + sizeof(numbuf);
|
||||
len = 0;
|
||||
sign_char = 0;
|
||||
if (intval < 0) {
|
||||
sign_char = '-';
|
||||
intval = -intval;
|
||||
if (intval < 0) { /* true for 0x800...0 */
|
||||
*numptr-- = '0' - intval%10;
|
||||
len++;
|
||||
intval /= 10;
|
||||
intval = -intval;
|
||||
}
|
||||
}
|
||||
if (precision < 1)
|
||||
precision = 1;
|
||||
for (; len < precision || intval != 0; len++) {
|
||||
*--numptr = '0' + intval%10;
|
||||
intval /= 10;
|
||||
}
|
||||
if (!sign_char) {
|
||||
if (always_sign)
|
||||
sign_char = '+';
|
||||
else if (insert_blank)
|
||||
sign_char = ' ';
|
||||
}
|
||||
if (sign_char) {
|
||||
if (zero_pad) {
|
||||
if (1 != writefunc(&sign_char, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
width--;
|
||||
} else {
|
||||
*--numptr = sign_char;
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
if (left_justify) {
|
||||
n = writefunc(numptr, len, arg1, arg2);
|
||||
total += n;
|
||||
if (n != len)
|
||||
break;
|
||||
}
|
||||
while (len < width) {
|
||||
if (1 != writefunc(padstr, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
width--;
|
||||
}
|
||||
if (!left_justify)
|
||||
total += writefunc(numptr, len, arg1, arg2);
|
||||
break;
|
||||
} /* case 'd', 'i' */
|
||||
|
||||
case 'c': {
|
||||
const char *padstr = zero_pad ? "0" : " ";
|
||||
unsigned char c = (unsigned char) intval;
|
||||
|
||||
if (left_justify) {
|
||||
if (1 != writefunc((char *)&c, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
}
|
||||
while (width > 1) {
|
||||
if (1 != writefunc(padstr, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
width--;
|
||||
}
|
||||
if (!left_justify)
|
||||
total += writefunc((char *)&c, 1, arg1, arg2);
|
||||
break;
|
||||
} /* case 'c' */
|
||||
|
||||
case 's': {
|
||||
const char *padstr;
|
||||
int len;
|
||||
|
||||
/* Catch null strings */
|
||||
if (strval == NULL) {
|
||||
strval = "(null)";
|
||||
}
|
||||
handle_string: /* NULL pointers for %p come here */
|
||||
padstr = zero_pad ? "0" : " ";
|
||||
len = strlen(strval);
|
||||
if (precision < 0 || precision > len)
|
||||
precision = len;
|
||||
if (left_justify && precision > 0) {
|
||||
n = writefunc(strval, precision, arg1, arg2);
|
||||
total += n;
|
||||
if (n != precision)
|
||||
break;
|
||||
}
|
||||
while (width > precision) {
|
||||
if (1 != writefunc(padstr, 1, arg1, arg2))
|
||||
break;
|
||||
total++;
|
||||
width--;
|
||||
}
|
||||
if (!left_justify && precision > 0)
|
||||
total += writefunc(strval, precision, arg1, arg2);
|
||||
break;
|
||||
} /* case 's' */
|
||||
|
||||
} /* switch (*format++) */
|
||||
|
||||
startptr = format; /* Start again after this %-token */
|
||||
} /* while (*format) */
|
||||
|
||||
/* Write anything left over. */
|
||||
if (startptr != format)
|
||||
total += writefunc(startptr, format-startptr, arg1, arg2);
|
||||
|
||||
/* Return total bytes written. */
|
||||
return total;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static int writefunc(const char *buf, size_t len, char **string, size_t *size)
|
||||
{
|
||||
if ((*size) <= 0)
|
||||
return 0;
|
||||
if (len > (*size)-1)
|
||||
len = (*size)-1;
|
||||
if (len > 0)
|
||||
memcpy((*string), buf, len);
|
||||
(*string) += len;
|
||||
(*string)[0] = 0;
|
||||
(*size) -= len;
|
||||
return len;
|
||||
}
|
||||
|
||||
int my_vsnprintf(char *string, size_t size, const char *format, va_list args)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
ret = _pfmt(format, args, (_pfmt_writefunc_t) writefunc, &string, &size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int my_snprintf(char *string, size_t size, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
va_start(args, format);
|
||||
ret = my_vsnprintf(string, size, format, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-file-style: "stroustrup"
|
||||
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vim: expandtab shiftwidth=4:
|
||||
*/
|
Loading…
x
Reference in New Issue
Block a user